1. 程式人生 > >linux下簡單的原始套接字通訊

linux下簡單的原始套接字通訊

原始套接字程式設計分析

與普通socket套接字程式設計,區別在於,原始套接字可以自行組裝資料包(偽裝本地 IP,本地 MAC),可以接收本機網絡卡上所有的資料幀(資料包)。另外,必須在管理員許可權下才能使用原始套接字。

通常情況下所接觸到的套接字(Socket)為兩類:

  • 流式套接字(SOCK_STREAM):一種面向連線的Socket,針對於面向連線的TCP 服務應用;
  • 資料報式套接字(SOCK_DGRAM):一種無連線的Socket,對應於無連線的UDP 服務應用。

而原始套接字(SOCK_RAW)與標準套接字(SOCK_STREAM、SOCK_DGRAM)的區別在於原始套接字直接置“根”於作業系統網路核心(Network Core),而 SOCK_STREAM、SOCK_DGRAM 則“懸浮”於 TCP 和 UDP 協議的外圍,如圖,
這裡寫圖片描述


流式套接字只能收發 TCP 協議的資料,資料報套接字只能收發 UDP 協議的資料,原始套接字可以收發沒經過核心協議棧的資料包

原始套接字程式設計

int socket ( int family, int type, int protocol );

  • family:協議簇 寫 PF_PACKET(AF_PACKET)
  • type: 套接字類,寫 SOCK_RAW
  • protocol:協議類別,指定可以接收或傳送的資料包型別,不能寫 “0”,取值如下,注意,傳參時需要用 htons() 進行位元組序轉換。
    ETH_P_IP:IPV4資料包
    ETH_P_ARP:ARP資料包
    ETH_P_ALL:任何協議型別的資料包

  • 返回值:
    成功( >0 ):套接字,這裡為鏈路層的套接字
    失敗( <0 ):出錯

原始套接字demo

下面主要通過PF_PACKET和SOCK_RAW,實現傳送和接收自定義type乙太網資料包。
乙太網的偵結構如下:

------------------------------------------------------
| 目的地址   | 源地址   | 型別     | 資料              |
------------------------------------------------------
|   6 byte   | 6 byte   | 2 byte   | 46~1500 byte   |
/**
 * recv.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>

#define ETH_P_NP    0x0807
#define IFRNAME0    "eth0"
#define IFRNAME2    "eth2"

unsigned char dest_mac[6] = {0};

int main(int argc,char **argv)
{
    int i ,datalen;
    int sd ;
    unsigned char data[IP_MAXPACKET] = {0};
    unsigned char *buf = NULL;

    struct sockaddr_ll device;
    struct ifreq ifr;
    socklen_t sll_len = sizeof(struct sockaddr_ll);
    bzero(&device,sizeof(struct sockaddr_ll));
    bzero(&ifr,sizeof(struct ifreq));

    if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
        perror("socket() failed to get socket descriptor for using ioctl()");
        exit(EXIT_FAILURE);
    }
    memcpy(ifr.ifr_name,IFRNAME2,sizeof(struct ifreq));
    if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
        perror("ioctl() failed to get source MAC address");
        return (EXIT_FAILURE);
    }
    close(sd);

    memcpy(dest_mac,ifr.ifr_hwaddr.sa_data,6);

    memcpy(device.sll_addr,dest_mac,6);
    device.sll_ifindex = ifr.ifr_ifindex;
    device.sll_family = PF_PACKET;
    device.sll_halen = htons(6);
    device.sll_protocol = htons(ETH_P_NP);

    if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0) {
        perror("socket() failed");
        exit(EXIT_FAILURE);
    }
    datalen = recvfrom(sd,data,1024,0,(struct sockaddr *)&device,&sll_len);
    if (datalen < 0) { printf("error\n"); exit(-1); }
    buf = data + 14;
    printf("ip data : %s\n",buf);
    return 0;
}
/**
 * send.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <bits/ioctls.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <errno.h>

#define ETH_P_NP    0x0807
#define IFRNAME0    "eth0"
#define IFRNAME1    "eth1"

unsigned char source_mac[6] = {0};
unsigned char dest_mac[6] = {0};

int main(int argc,char **argv)
{
    int i ,datalen ,frame_length;
    int sd ,bytes;
    uint8_t data[IP_MAXPACKET];
    uint8_t ether_frame[IP_MAXPACKET];
    struct sockaddr_ll device;
    struct ifreq ifr;
    bzero(&device,sizeof(struct sockaddr_ll));
    bzero(&ifr,sizeof(struct ifreq));

    if((sd = socket(PF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL))) < 0) {
        perror("socket() failed to get socket descriptor for using ioctl()");
        exit(EXIT_FAILURE);
    }
    memcpy(ifr.ifr_name,IFRNAME1,sizeof(struct ifreq));
    if(ioctl(sd,SIOCGIFHWADDR,&ifr) < 0) {
        perror("ioctl() failed to get source MAC address");
        return (EXIT_FAILURE);
    }
    close(sd);

    //copy source MAC address
    memcpy(source_mac,ifr.ifr_hwaddr.sa_data,6);

    memcpy(device.sll_addr,source_mac,6);
    if((device.sll_ifindex = if_nametoindex(IFRNAME1)) == 0) {
        perror("if_nametoindex() failed to obtain interface index");
        exit(EXIT_FAILURE);
    }
    // /*or*/ device.sll_addr = ifr.ifr_ifindex;
    device.sll_family = PF_PACKET;
    device.sll_halen = htons(6);
    //device.sll_protocol = htons(ETH_P_NP);

    // ifconfig eth2 /*00:0c:29:78:65:12*/
    dest_mac[0] = 0x00;
    dest_mac[1] = 0x0c;
    dest_mac[2] = 0x29;
    dest_mac[3] = 0x78;
    dest_mac[4] = 0x65;
    dest_mac[5] = 0x12;

    datalen = 9;
    data[0] = 'h';
    data[1] = 'e';
    data[2] = 'l';
    data[3] = 'l';
    data[4] = 'o';
    data[5] = ' ';
    data[6] = 'z';
    data[7] = 'o';
    data[8] = 'u';
    frame_length = 6 + 6 + 2 + datalen;

    /* 如果希望方便的話,可以嘗試使用libnet */
    memcpy(ether_frame,dest_mac,6);
    memcpy(ether_frame + 6,source_mac,6);

    ether_frame[12] = ETH_P_NP / 256;
    ether_frame[13] = ETH_P_NP % 256;
    memcpy(ether_frame + 14,data,datalen);

    //one question : Here third parameters make me feel confused
    //if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_NP))) < 0)
    if((sd = socket (PF_PACKET,SOCK_RAW,htons(ETH_P_ALL))) < 0) {
        perror("socket() failed");
        exit(EXIT_FAILURE);
    }

    while(1) {
        if ((bytes = sendto (sd, ether_frame, frame_length, 0, (struct sockaddr *)&device, sizeof (device))) <= 0) {
            perror("sendto() failed");
            exit(EXIT_FAILURE);
        }
        sleep(4);
    }

    close(sd);

    return 0;
}

其他型別的原始套接字,可參考以下的博文