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;
}

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

linux原始套接字(1)-arp请求与接收:http://www.cnblogs.com/yuuyuu/p/5164685.html
linux原始套接字(2)-icmp请求与接收:http://www.cnblogs.com/yuuyuu/p/5167525.html
linux原始套接字(3)-构造IP_TCP发送与接收:http://www.cnblogs.com/yuuyuu/p/5169931.html
linux原始套接字(4)-构造IP_UDP:http://www.cnblogs.com/yuuyuu/p/5170056.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值