[Linux]-原始套接字

目录

【原始套接字的创建】

【协议格式】

1.UDP数据格式

2.TCP数据格式

3.IP报文数据格式

4.MAC报文数据格式

【使用原始套接字捕获网络数据】

【使用原始套接字发送网络数据】

获取本地机的接口数据

【发送ARP报文获取未知的MAC地址】

1.实现原理

 2.ARP数据报文格式

 3.实现

3.1数据帧的组包

【ARP欺骗】

1.实现原理

 2.使用协议结构体组建数据

3.实现

【构建UDP报文】

1.协议结构体及实现

2.IP头校验及UDP头校验

2.1UDP的伪头部及校验

3.实现


原始套接字指的是在传输层下面使用的套接字,之前使用的流式套接字和数据报套接字是工作在传输层的,并且在接受和发送的时候只能对数据部分进行操作,如果想要自己组建一个报文,那么就需要使用原始套接字。并且原始套接字还可以监听所有经过本机网卡的数据帧或者数据包。


【原始套接字的创建】

原始套接字的创建也是使用socket函数,但是形参比起TCP或者UDP有所不同

当编译完成生成可执行文件的时候,执行需要加sudo

int socket(PF_PACKET,SOCK_RAW,protocol);

功能

        创建链路层的原始套接字

参数

        protocol:用来指定接受或者发送的数据包类型

                        ETH_P_IP:IPV4数据包

                        ETH_P_ARP:ARP数据包

                        ETH_P_ALL:任何协议类型的数据包

                        注意:protocol这个参数需要转为网络字节序,也就是使用htons()

返回值

        成功:>0,链路层套接字

        失败:<0,出错 


【协议格式】

[Linux]-原始套接字_第1张图片

1.UDP数据格式

UDP的报文头由64位8个字节组成,用来标识网络通信之前源进程与目的进程的端口号

[Linux]-原始套接字_第2张图片


2.TCP数据格式

TCP的报文头格式相比于UDP更为的复杂,首先为源端口号和目的端口号,因为TCP会进行确认,于是就会有序列号和确认号,因为TCP的头部长度至少为20字节(没有选项的情况下),且头部长度中仅为4位,最多只能表示15,不足以表示正确长度。所以头部长度为实际长度除以4,也就是如果头部实际长度为20,那么头部长度这个选项为5。

[Linux]-原始套接字_第3张图片


3.IP报文数据格式

版本这个选项是看使用的是IPV4还是IPV6,如果是前者为4。首部长度和UDP报文头的相同,选项为IP实际报文头长度除以4。服务类型一般为0,总长度为IP报文头的长度加上数据的长度。标识一般为0,标识和片偏移也一般为0。生存时间TTL表示此数据经过一个路由器递减的次数,可以大致填写,协议类型根据实际情况填写。头部校验需要使用特定的函数校验,然后填写进去,未校验前先填写0。

[Linux]-原始套接字_第4张图片


4.MAC报文数据格式

MAC报文的报文较为简单,只需要填写目的MAC,源MAC以及数据的类型即可 

[Linux]-原始套接字_第5张图片


【使用原始套接字捕获网络数据】

原始套接字可以捕获任何经过当前主机网卡的数据,那么使用原始套接字接受数据,然后通过组包把数据拆拆解到定义的相应数组中,就可以获取相应的想要获取的数据

通过对接受到的数据的拆解,我们可以得到很多报文中的数据

可见我们通过原始套接字成功捕获到各个数据的类型以及源MAC目的MAC源IP目的IP

#include 
#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    int socket_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
    if( socket_fd < 0 )
    {
        perror("socket:");
        return 0;
    }
    printf("socket = %d \n",socket_fd);


    unsigned char data[1500] = "";
    unsigned char src_mac[18] = "";
    unsigned char dst_mac[18] = "";
    unsigned char type[2] = "";
    unsigned char src_ip[16] = "";
    unsigned char dst_ip[16] = "";
    while(1)
    {
        recvfrom(socket_fd,data,sizeof(data),0,NULL,NULL);

        /*************       解析目的Mac和源Mac         ************/
        sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",\
                    data[0],data[1],data[2],data[3],data[4],data[5]);
        sprintf(dst_mac,"%02x:%02x:%02x:%02x:%02x:%02x",\
                    data[6],data[7],data[8],data[9],data[10],data[11]);

        /*************       解析数据类型         ************/
        sprintf(type,"0x%02x%02x",data[12],data[13]);
        if( data[13] == 0x00 )
        {
            printf("\tIP数据报--->%s\n",type);
            unsigned char* ip_head = data+14;
            inet_ntop(AF_INET,(unsigned int*)(ip_head+12),src_ip,16);
            inet_ntop(AF_INET,(unsigned int*)(ip_head+16),dst_ip,16);
            printf("\tsrc_ip%s ------> dst_ip%s \n",src_ip,dst_ip);
        }
        else if( data[13] == 0x06 )
        {
            printf("\tARP数据报--->%s\n",type);
        }
        if( data[13] == 0x35 )
        {
            printf("\tRARP数据报--->%s\n",type);           
        }

        printf("src_mac:%s --------> dst_mac%s \n",src_mac,dst_mac);
    }

    close(socket_fd);

    return 0;
}

【使用原始套接字发送网络数据】

通过原始套接字发送组建好的数据,使用sendto发送完整的数据帧

sendto(sock_raw_fd,msg,msg_len,0,(struct sockaddr*)&sll,sizof(sll));

功能

        msg:完整的数据格式

        msg_len:帧的实际长度

        sll:本地主机上的帧数据,出去的网卡地址

        sll结构体的数据类型和头文件

                struct sockaddr_ll sll

                #include

[Linux]-原始套接字_第6张图片


获取本地机的接口数据

#include 

int ioctl(int fd, int request, void*);

具体的获得网络接口可以封装为一个函数,具体函数如下

如果想要使用原始套接字发送数据,那么就需要使用这个发送函数,第一个形参为套接字,第二个位发送的数据,第三个为数据长度,第四个为网络接口的名称(可以使用ifconfig查看) 

int my_send(int socket_fd , char* msg , int msg_len , char* net_name )
{
    struct ifreq ethreq;
    //把你要发送的网卡名称赋值进去
    strncpy(ethreq.ifr_name,net_name,IFNAMSIZ);
    if( -1 == ioctl(socket_fd, SIOCGIFINDEX ,ðreq) )
    {
        perror("sockfd");
        close(socket_fd);
        _exit(-1);
    }
    struct sockaddr_ll sll;
    memset(&sll,0,sizeof(sll));
    //通过网卡名获取网络接口
    sll.sll_ifindex = ethreq.ifr_ifindex;

    //发送
    int len = sendto(socket_fd,msg,msg_len,0,(struct sockaddr *)&sll,sizeof(sll));
    return len;

}

【发送ARP报文获取未知的MAC地址】

1.实现原理

如果我们想向一台主机发送报文,比如说A主机向B主机发送ping命令(ICMP),但是A主机只有B主机的IP地址,而没有B主机的MAC地址,此时需要使用ARP报文来获取B主机的MAC地址,才能成功ping通

[Linux]-原始套接字_第7张图片

 2.ARP数据报文格式

[Linux]-原始套接字_第8张图片

 3.实现

[Linux]-原始套接字_第9张图片

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int my_send(int socket_fd , unsigned char* msg , int msg_len , char* net_name )
{
    struct ifreq ethreq;
    //把你要发送的网卡名称赋值进去
    strncpy(ethreq.ifr_name,net_name,IFNAMSIZ);
    if( -1 == ioctl(socket_fd, SIOCGIFINDEX ,ðreq) )
    {
        perror("sockfd");
        close(socket_fd);
        _exit(-1);
    }
    struct sockaddr_ll sll;
    memset(&sll,0,sizeof(sll));
    //通过网卡名获取网络接口
    sll.sll_ifindex = ethreq.ifr_ifindex;

    //发送
    int len = sendto(socket_fd,msg,msg_len,0,(struct sockaddr *)&sll,sizeof(sll));
    return len;

}

int main(int argc, char const *argv[])
{
    //创建原始套接字
    int socket_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
    if( socket_fd < 0 )
    {
        perror("socket:");
        return 0;
    }
    printf("socket = %d \n",socket_fd);
    
    //创建发送的数据
    unsigned char msg[512] = {
    //-------------------------------   mac头
    0xff,0xff,0xff,0xff,0xff,0xff,  //目的mac
    0x00,0x0c,0x29,0xxx,0xxx,0xxx,  //源mac       <-----------------------
    0x08,0x06,                      //帧类型
    //-------------------------------   ARP报文
    0x00,0x01,                      //硬件类型
    0x08,0x00,                      //协议类型
    0x06,                           //硬件地址长度
    0x04,                           //协议地址长度
    0x00,0x01,                      //选项 ARP请求
    0x00,0x0c,0x29,0xxx,0xxx,0xxx,  //源mac        <-----------------------
    192,168,x,xxx,                  //源IP
    0x00,0x00,0x00,0x00,0x00,0x00,  //目的mac
    192,168,x,xxx,                  //目的IP
    };
    //通过网络接口发送数据
    int len = my_send(socket_fd,msg,42,"ens33");
    printf("发送报文长度为%d\n",len);

    //使用循环接受符合要求数据
    unsigned char rec_data[1500] = "";
    unsigned char src_mac[18] = "";
    unsigned char src_ip[16] = "";

    while(1)
    {
        recvfrom(socket_fd,rec_data,sizeof(rec_data),0,NULL,NULL);
        unsigned short mac_type = ntohs(*(unsigned short *)(rec_data + 12));
      
        //需要ARP数据报,其他过滤

        if( mac_type == 0x0806 )
        {
            unsigned short op = ntohs(*(unsigned short *)(rec_data + 20));
            if( 0x02 == op )
            {
                //获取需要的mac地址
                sprintf(src_mac,"%02x:%02x:%02x:%02x:%02x:%02x",\
                        rec_data[6],rec_data[7],rec_data[8],rec_data[9],rec_data[10],rec_data[11]);
                //获取IP
                inet_ntop(AF_INET,(unsigned int*)(rec_data+28),src_ip,16);
                printf("%s的mac地址为----->%s\n",src_ip,src_mac);
                break;
            }
    
        }
    }
    close(socket_fd);
    return 0;
}

3.1数据帧的组包

一般来说,进行网络通信定义的数组的类型为无符号字符类型,因为有时候接受或者发送的时候会接受到图片,而图片的RGB的范围为0-255,如果使用有符号字符会出错。

组包时注意要按照网络字节序也就是大端形式组包

    //创建发送的数据
    unsigned char msg[512] = {
    //-------------------------------   以太网头
    0xff,0xff,0xff,0xff,0xff,0xff,  //目的mac
                                    //全 ff 表示广播        
    0x00,0x0c,0x29,0xxx,0xxx,0xxx,  //源mac       <-----------------------
    0x08,0x06,                      //帧类型
    //-------------------------------   ARP报文
    0x00,0x01,                      //硬件类型
    0x08,0x00,                      //协议类型
    0x06,                           //硬件地址长度
    0x04,                           //协议地址长度
    0x00,0x01,                      //选项 ARP请求
    0x00,0x0c,0x29,0xxx,0xxx,0xxx,  //源mac        <-----------------------
    192,168,x,xxx,                  //源IP
    0x00,0x00,0x00,0x00,0x00,0x00,  //目的mac
                                    //此时不知道目的MAC地址,使用0
    192,168,x,xxx,                  //目的IP
    };

【ARP欺骗】

1.实现原理

一般来说,主机是不会判断自身是否发送过ARP请求报文的,所以另一台主机只需要伪装一个ARP应答报文,然后任意修改报文中源MAC的地址,此时在接受的主机那里,arp表中相应IP的MAC就会被修改。

此时在组建数据帧的时候,源MAC和op选项需要修改

[Linux]-原始套接字_第10张图片

 2.使用协议结构体组建数据

我们一开始在组建ARP报文获取已知IP未知MAC主机的MAC地址的时候,是直接定义一段连续的空间来人为一个字节一个字节组建的,这样是十分繁琐的,接下来使用结构体进行组包

具体的实现原理就是,定义一段连续的内存空间用来组建数据,然后通过不同结构体的指针操控的空间大小,来对这段连续的空间进行赋值

[Linux]-原始套接字_第11张图片

这里我们需要以太网头部结构体以及ARP数据结构体

以太网头部结构体

#include 

struct  ether_header                 

{
    u_int8_t    ether_dhost[ETH_ALEN];   //目的MAC地址
    u_int8_t    ether_shost[ETH_ALEN];   //源MAC地址
    u_int16_t  ether_type;               //帧类型
};

ARP数据结构体

#include 

/**********************      ARP头部            ***************************/
typedef struct 
  {
    unsigned short int ar_hrd;		//硬件类型
    unsigned short int ar_pro;		//协议类型
    unsigned char ar_hln;		//硬件地址长度
    unsigned char ar_pln;		//协议地址长度
    unsigned short int ar_op;		//op
#if 1
    unsigned char __ar_sha[ETH_ALEN];	//发送源mac
    unsigned char __ar_sip[4];		//发送IP
    unsigned char __ar_tha[ETH_ALEN];	//接受的mac
    unsigned char __ar_tip[4];		//发送IP
#endif
}ARPHDR;

3.实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/**********************      ARP头部            ***************************/
typedef struct 
  {
    unsigned short int ar_hrd;      /* Format of hardware address.  */
    unsigned short int ar_pro;      /* Format of protocol address.  */
    unsigned char ar_hln;       /* Length of hardware address.  */
    unsigned char ar_pln;       /* Length of protocol address.  */
    unsigned short int ar_op;       /* ARP opcode (command).  */
#if 1
    /* Ethernet looks like this : This bit is variable sized
       however...  */
    unsigned char __ar_sha[ETH_ALEN];   /* Sender hardware address.  */
    unsigned char __ar_sip[4];      /* Sender IP address.  */
    unsigned char __ar_tha[ETH_ALEN];   /* Target hardware address.  */
    unsigned char __ar_tip[4];      /* Target IP address.  */
#endif
}ARPHDR;
/*******************************************************************/

int my_send(int socket_fd , char* msg , int msg_len , char* net_name )
{
    struct ifreq ethreq;
    //把你要发送的网卡名称赋值进去
    strncpy(ethreq.ifr_name,net_name,IFNAMSIZ);
    if( -1 == ioctl(socket_fd, SIOCGIFINDEX ,ðreq) )
    {
        perror("sockfd");
        close(socket_fd);
        _exit(-1);
    }
    struct sockaddr_ll sll;
    memset(&sll,0,sizeof(sll));
    //通过网卡名获取网络接口
    sll.sll_ifindex = ethreq.ifr_ifindex;

    //发送
    int len = sendto(socket_fd,msg,msg_len,0,(struct sockaddr *)&sll,sizeof(sll));
    return len;

}

int main(int argc, char const *argv[])
{
    //创建原始套接字
    int socket_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
    if( socket_fd < 0 )
    {
        perror("socket:");
        return 0;
    }
    printf("socket = %d \n",socket_fd);
    
    //创建发送的数据
    unsigned char msg[512] = {0};
    //使用结构体来对数据进行填充
    unsigned char src_ip[4] = {192,168,xxx,xxx};                  //<-----------------
    unsigned char dst_ip[4] = {192,168,xxx,xxx};                  //<-----------------
    unsigned char dst_mac[6] = {0x62,0xa3,0x34,0xxx,0xxx,0xxx};   //<-----------------
    unsigned char src_mac[6] = {0xee,0xee,0xee,0xee,0xee,0xee};

    //以太网头部
    struct ether_header* mac_head = (struct ether_header*)msg;
    memcpy(mac_head->ether_dhost,dst_mac,6);//以太网头部的目的mac
    memcpy(mac_head->ether_shost,src_mac,6);//以太网头部的源mac
    mac_head->ether_type = htons(0x0806);   //以太网头部的帧类型

    //ARP报
    ARPHDR* arp_head = (ARPHDR*)(msg+14);
    arp_head->ar_hrd = htons(1);        //硬件类型
    arp_head->ar_pro = htons(0x0800);   //协议类型
    arp_head->ar_hln = 6;              //硬件地址长度
    arp_head->ar_pln = 4;              //协议地址长度
    arp_head->ar_op = htons(2);        //op
    memcpy(arp_head->__ar_sha,src_mac,6);//发送源mac
    memcpy(arp_head->__ar_sip,src_ip,4);//发送IP
    memcpy(arp_head->__ar_tha,dst_mac,6);//接受的mac
    memcpy(arp_head->__ar_tip,dst_ip,4);//发送IP

    int i = 0;
    for ( i = 0; i < 10; i++)
    {
        int len = my_send(socket_fd,msg,42,"ens33");
        printf("发送报文长度为%d\n",len);      
        sleep(1);
    }
    
    //通过网络接口发送数据

   
    close(socket_fd);
    return 0;
}

【构建UDP报文】

1.协议结构体及实现

构建UDP报文,我们需要以太网头部,IP头部,UDP报文头部以及发送的数据总共四个部分组成

[Linux]-原始套接字_第12张图片

 在这里我们需要IP的报文头以及UDP的报文头

#include 

***********************IP的结构***********************************
struct iphdr
{
    
#if __BYTE_ORDER == __LITTLE_ENDIAN    //小端存储
    unsigned int ihl:4;//首部长度
    unsigned int version:4;//版本
#elif __BYTE_ORDER == __BIG_ENDIAN     //大端存储
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix "
#endif
    u_int8_t tos;//服务类型
    u_int16_t tot_len;//总长度为ip头+UDP头+发送数据长度
    u_int16_t id;//标识
    u_int16_t frag_off;//标志,片偏移
    u_int8_t ttl;//生存时间
    u_int8_t protocol;//上层协议udp
    u_int16_t check;//校验,等数据填充完毕在校验,这里为0
    u_int32_t saddr;//源IP地址,转为32无符号
    u_int32_t daddr;//目的IP地址,转为32无符号
};
#include 

***********************UDP的结构*****************************
struct udphdr
{
    u_int16_t source;//源端口号
    u_int16_t dest;//目的端口号
    u_int16_t len;//UDP长度为头部加上数据长度
    u_int16_t check;//校验
};

2.IP头校验及UDP头校验

IP头以及UDP头中的校验位我们需要用到如下函数,然后通过该函数的返回值完成校验

IP头只需要把整个头部放进去,然后通过校验位接受返回值就行

ip_head->check = checksum((unsigned short*)ip_head,20);//校验

UDP校验需要用到伪头部

//*********************  校验函数  ************************
unsigned short checksum(unsigned short *buf, int len)
{
    int nword = len / 2;
    unsigned long sum;
    if (len % 2 == 1)
        nword++;
    for (sum = 0; nword > 0; nword--)
    {
        sum += *buf;
        buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return ~sum;
}

2.1UDP的伪头部及校验

想要获得UDP头的校验位,那么我们需要构建一个UDP的伪头部进行校验

[Linux]-原始套接字_第13张图片

//*********************  udp伪头部结构体  ************************
typedef struct
{
    u_int32_t src_ip;   //源IP
    u_int32_t dst_ip;   //目的IP
    u_int8_t  flag;     //标志位0
    u_int8_t  proto;    //协议17表示UDP
    u_int16_t len;      //UDP长度

}Fake_UDP;

//*********************  具体实现  ************************
    //udp的校验需要伪头部
    unsigned char fake_head[512] = "";
    Fake_UDP *fake_udp_head = (Fake_UDP *)fake_head;
    fake_udp_head->src_ip = inet_addr("192.168.x.xxx");//源IP地址,转为32无符号
    fake_udp_head->dst_ip = inet_addr("192.168.x.xxx");//目的IP地址,转为32无符号
    fake_udp_head->flag = 0;//通常为0
    fake_udp_head->proto = 17;//协议UDP
    fake_udp_head->len = htons(8+send_data_len);

    //把剩下的内容拷贝到伪头部的连续内存中-------------->拷贝的是UDP伪头部下面的内容
    memcpy(fake_head+12,udp_head,8+send_data_len);
    //校验-------------------------------------------->通过伪头部校验,获得校验值放于真UDP头中
    udp_head->check = checksum((unsigned short*)fake_head,20+send_data_len);//校验

3.实现

我们这里通过虚拟机组建UDP报文向主机中的网络调试助手发送信息

[Linux]-原始套接字_第14张图片

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//*********************  udp伪头部结构体  ************************
typedef struct
{
    u_int32_t src_ip;   //源IP
    u_int32_t dst_ip;   //目的IP
    u_int8_t  flag;     //标志位0
    u_int8_t  proto;    //协议17表示UDP
    u_int16_t len;      //UDP长度

}Fake_UDP;
//*********************  校验函数  ************************
unsigned short checksum(unsigned short *buf, int len)
{
    int nword = len / 2;
    unsigned long sum;
    if (len % 2 == 1)
        nword++;
    for (sum = 0; nword > 0; nword--)
    {
        sum += *buf;
        buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return ~sum;
}
//*********************  原始套接字发送函数  ************************
int my_send(int socket_fd , char* msg , int msg_len , char* net_name )
{
    struct ifreq ethreq;
    //把你要发送的网卡名称赋值进去
    strncpy(ethreq.ifr_name,net_name,IFNAMSIZ);
    if( -1 == ioctl(socket_fd, SIOCGIFINDEX ,ðreq) )
    {
        perror("sockfd");
        close(socket_fd);
        _exit(-1);
    }
    struct sockaddr_ll sll;
    memset(&sll,0,sizeof(sll));
    //通过网卡名获取网络接口
    sll.sll_ifindex = ethreq.ifr_ifindex;

    //发送
    int len = sendto(socket_fd,msg,msg_len,0,(struct sockaddr *)&sll,sizeof(sll));
    return len;

}

//*********************  主函数  ************************
int main(int argc, char const *argv[])
{
    //创建原始套接字
    int socket_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
    if( socket_fd < 0 )
    {
        perror("socket:");
        return 0;
    }
    printf("socket = %d \n",socket_fd);
    

    //通过udp发送的数据
    unsigned char send_data[512] = "";
    printf("请输入发送的信息:\n");
    scanf("%s",send_data);

    int send_data_len = strlen(send_data);
    //数据长度必须为偶数
    send_data_len = send_data_len + send_data_len%2;

    //创建发送的数据
    unsigned char msg[1500] = {0};
    //使用结构体来对数据进行填充
    unsigned char src_ip[4] = {192,168,x,xxx};//源IP     <----------------------
    unsigned char dst_ip[4] = {192,168,x,xxx};//目的IP    <----------------------
    unsigned char dst_mac[6] = {0x62,0xa3,0x34,0xxx,0xxx,0xxx};//目的mac <--------------- 
    unsigned char src_mac[6] = {0x00,0x0c,0x29,0xxx,0xxx,0xxx};//源mac<------------------

    //*********************  以太网头部  ************************
    struct ether_header* mac_head = (struct ether_header*)msg;
    memcpy(mac_head->ether_dhost,dst_mac,6);//以太网头部的目的mac
    memcpy(mac_head->ether_shost,src_mac,6);//以太网头部的源mac
    mac_head->ether_type = htons(0x0800);   //以太网头部的帧类型


    //*********************  IP报文头  ************************
    struct iphdr* ip_head = (struct iphdr*)(msg+14);
    ip_head->version = 4;//版本
    ip_head->ihl = 20/4;//长度为总长度除以4
    ip_head->tos = 0;//服务类型
    ip_head->tot_len = htons(20+8+send_data_len);//总长度为ip头+UDP头+发送数据长度
    ip_head->id = htons(0);//标识
    ip_head->frag_off = htons(0); //标志,片偏移
    ip_head->ttl = 128;//生存时间
    ip_head->protocol = 17;//上层协议udp
    ip_head->check = htons(0);//校验,等数据填充完毕在校验,这里为0
    ip_head->saddr = inet_addr("192.168.x.xxx");//源IP地址,转为32无符号<-----------------
    ip_head->daddr = inet_addr("192.168.x.xxx");//目的IP地址,转为32无符号<---------------
    ip_head->check = checksum((unsigned short*)ip_head,20);//校验


    //*********************  UDP报文头  ************************
    struct udphdr* udp_head = (struct udphdr*)(msg+14+20);
    udp_head->source = htons(8000);//源端口号
    udp_head->dest = htons(8000);//目的端口号
    udp_head->len = htons(8+send_data_len);//UDP长度为头部加上数据长度
    udp_head->check = htons(0);//校验

    /*******   犯错点     *******/
    /*******   udp_head是struct udphdr*类型,+8跳过不是八个字节     *******/
    //memcpy(udp_head+8, send_data, send_data_len);//将数据拷到报文头后面
    memcpy(msg+14+20+8, send_data, send_data_len);//将数据拷到报文头后面

    //udp的校验需要伪头部
    unsigned char fake_head[512] = "";
    Fake_UDP *fake_udp_head = (Fake_UDP *)fake_head;
    fake_udp_head->src_ip = inet_addr("192.168.x.xxx");//源IP地址,转为32无符号<----------
    fake_udp_head->dst_ip = inet_addr("192.168.x.xxx");//目的IP地址,转为32无符号<--------
    fake_udp_head->flag = 0;//通常为0
    fake_udp_head->proto = 17;//协议UDP
    fake_udp_head->len = htons(8+send_data_len);
    //把剩下的内容拷贝到伪头部的连续内存中
    memcpy(fake_head+12,udp_head,8+send_data_len);
    //校验
    udp_head->check = checksum((unsigned short*)fake_head,20+send_data_len);//校验

    //通过网络接口发送数据
    int len = my_send(socket_fd,msg,14+20+8+send_data_len,"ens33");
    printf("成功发送%d字节\n",len);
   
    close(socket_fd);
    return 0;
}

你可能感兴趣的:(Linux网络编程,linux,计算机网络,网络协议,udp,tcp/ip)