线程与进程:线程中的icmp数据包创建失败

时间:2016-11-21 11:39:35

标签: c linux multithreading sockets pthreads

我想通过C程序在linux中的posix线程中生成ICMP echo请求。

作为试用版,我在main()中编写了示例代码。 ICMP echo及其回复按预期工作。数据包长度为28(20字节IP标头+ 8字节ICMP标头)。

比我将代码转移到一个线程。现在main()创建线程并等待它退出。

但是,在线程中,sendto()返回28,而在tcpdump中观察时,此数据包显示长度为48,下面一行显示为IP bad-hlen 0,表示ECHO请求不合适。 IP标题中的总长度字段显示0x30(48字节)而不是0x1c(28字节)。以下是tcpdump快照。

使用流程代码

成功完成tcpdump
06:30:58.139476 IP (tos 0x0, ttl 64, id 19213, offset 0, flags [none], proto ICMP (1), length 28)
    192.168.11.34 > 192.168.11.32: ICMP echo request, id 0, seq 0, length 8
        0x0000:  4500 001c 4b0d 0000 4001 9841 c0a8 0b22  E...K...@..A..."
        0x0010:  c0a8 0b20 0800 f7ff 0000 0000            ............
06:30:58.139819 IP (tos 0x0, ttl 64, id 6830, offset 0, flags [none], proto ICMP (1), length 28)
    192.168.11.32 > 192.168.11.34: ICMP echo reply, id 0, seq 0, length 8
        0x0000:  4500 001c 1aae 0000 4001 c8a0 c0a8 0b20  E.......@.......
        0x0010:  c0a8 0b22 0000 ffff 0000 0000 0000 0000  ..."............
        0x0020:  0000 0000 0000 0000 0000 0000 0000       ..............

包头长度不正确的数据包/数据/一些重影。

06:33:14.513597 IP (tos 0x0, ttl 64, id 22998, offset 0, flags [DF], proto ICMP (1), length 48)
    192.168.11.34 > 192.168.11.32: ICMP type-#69, length 28
        IP bad-hlen 0
        0x0000:  4500 0030 59d6 4000 4001 4964 c0a8 0b22  E..0Y.@.@.Id..."
        0x0010:  c0a8 0b20 4500 1c00 4b0d 0000 4001 7c5d  ....E...K...@.|]
        0x0020:  c0a8 0b22 c0a8 0b20 0800 f7ff 0000 0000  ..."............

这会导致recv()失败。

作为故障排除的一部分,将用于发送的缓冲区转储到文件并通过hexdump进行验证。两个代码都生成相同的数据通过打印十六进制值进行验证。结果相同。尝试分叉,而不是创建线程。有效。

两个代码的唯一区别是线程和进程。耗尽可能的问题。

尝试的发行版是CentOS 7.1(内核3.10)和Fedora 13(内核2.6.39)。

这是流程代码。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <net/if.h>
#include <pthread.h>

unsigned short in_cksum(unsigned short *addr, int len)
{
    register int sum = 0;
    u_short answer = 0;
    register u_short *w = addr;
    register int nleft = len;
    /*
 *      * Our algorithm is simple, using a 32 bit accumulator (sum), we add
 *           * sequential 16 bit words to it, and at the end, fold back all the
 *                * carry bits from the top 16 bits into the lower 16 bits.
 *                     */
    while (nleft > 1)
    {
      sum += *w++;
      nleft -= 2;
    }
    /* mop up an odd byte, if necessary */
    if (nleft == 1)
    {
      *(u_char *) (&answer) = *(u_char *) w;
      sum += answer;
    }
    /* add back carry outs from top 16 bits to low 16 bits */
    sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
    sum += (sum >> 16);             /* add carry */
    answer = ~sum;              /* truncate to 16 bits */
    return (answer);
}

int main()
{
    struct iphdr *ip, *ip_reply;
    struct icmphdr *icmp, *icmp_reply;
    struct sockaddr_in connection;
    char *dst_addr="192.168.11.32";
    unsigned char *packet, *buffer;
    int sockfd, optval, ret=-1;
    socklen_t addrlen;

    /* open ICMP socket */
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    printf("Socket is %d\n", sockfd) ; 

    packet = (unsigned char*)malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));
    buffer = (unsigned char*)malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));

    if(packet == NULL || buffer == NULL)
    {
        perror("Error in malloc") ;
    }

    memset(packet, 0, sizeof(struct iphdr) + sizeof(struct icmphdr)); 
    memset(buffer, 0, sizeof(struct iphdr) + sizeof(struct icmphdr)); 

    ip = (struct iphdr*) packet;
    icmp = (struct icmphdr*) ((char*)packet + sizeof(struct iphdr));

    ip->ihl         = 5;
    ip->version     = 4;
    ip->tot_len     = sizeof(struct iphdr) + sizeof(struct icmphdr);
    //ip->tot_len     = 48;
    ip->id       = random()%5985;
    ip->protocol    = IPPROTO_ICMP;
    ip->saddr       = inet_addr("192.168.11.34");
    ip->daddr       = inet_addr(dst_addr);
//    ip->daddr       = inet_addr("8.8.8.8");
    ip->ttl         = 64;
    ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); 

    icmp->type      = ICMP_ECHO;
    icmp->code           = 0;
    icmp->un.echo.id     = 0;
    icmp->un.echo.sequence   = 0;
    icmp->checksum       = 0;
    icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));

    //Dumping headers to a file, to be viewed using hexdump
    int ip_file = open("working_header",O_CREAT|O_RDWR);

    if(ip_file == -1)
    {
        perror("Error in file opening");
    }

    ret = write(ip_file, packet, sizeof(struct iphdr) + sizeof(struct icmphdr));

    if(ret == -1)
    {
        perror("Error in write"); 
    }
    else
    {
        printf("Wrote %d bytes\n", ret) ;
    }

    close(ip_file);

    //binding to a specific interface
    struct ifreq ifr;
    memset(&ifr, 0, sizeof (ifr));
    snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "enp1s0");
    if (ioctl (sockfd, SIOCGIFINDEX, &ifr) < 0)
    {
        //Failed to find interface on device
        printf("Failed to find interface on device\n");
        return -1;
    }

    if (setsockopt (sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)) < 0)
    {
        //Failed to bind to interface enp2s0
        printf("Failed to bind to interface %s\n",ifr.ifr_name);
        return -1;
    }
    struct timeval tv;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
    {
        printf("Unable to set timeout\n");
        return -1;
    }

     /* IP_HDRINCL must be set on the socket so that the kernel does not attempt 
 *      *  to automatically add a default ip header to the packet*/
    ret = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(int));

    if(ret == -1)                                  
    {                                           
        perror("Error in setsockopt");       
    }                 
    connection.sin_family       = AF_INET;
    connection.sin_addr.s_addr  = ip->daddr;

    printf("Packet length is: %d\n",ip->tot_len);

    //printing packet, byte by byte, in hex, before sending
    unsigned char ch = 0; 

    while ( ch<28)
    {
        //printf("%x ",packet[ch]);
        printf("0x%02x ", packet[ch]);
        ch++;
    }
    printf("\n"); 
        ret = sendto(sockfd, (void*)packet, ip->tot_len, 0, (struct sockaddr *)&connection, sizeof(struct sockaddr));

    printf("Sent %d byte packet to %s  ret = %d\n", ip->tot_len, dst_addr,  ret);
//  }
    addrlen = sizeof(connection);
    if (recvfrom(sockfd, buffer, sizeof(struct iphdr) + sizeof(struct icmphdr), 0, (struct sockaddr *)&connection, &addrlen) < 0)
        {
        perror("recv");
        }
    else
    {
        ip_reply = (struct iphdr*) buffer;
        icmp_reply = (struct icmphdr*) (buffer + sizeof(struct iphdr));
        printf("Received type %d\n", icmp_reply->type);
        printf("icmp code %d\n", icmp_reply->code);
        printf("TTL: %d\n", ip_reply->ttl);
        printf("CheckSum: %d,%d\n", ip_reply->check,icmp_reply->checksum);
    }
    free(packet);
    free(buffer);
    close(sockfd);  

    return 0 ;
}

以下是线程代码。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <net/if.h>
#include <pthread.h>

unsigned short in_cksum(unsigned short *addr, int len)
{
    register int sum = 0;
    u_short answer = 0;
    register u_short *w = addr;
    register int nleft = len;
    /*
 *      * Our algorithm is simple, using a 32 bit accumulator (sum), we add
 *           * sequential 16 bit words to it, and at the end, fold back all the
 *                * carry bits from the top 16 bits into the lower 16 bits.
 *                     */
    while (nleft > 1)
    {
      sum += *w++;
      nleft -= 2;
    }
    /* mop up an odd byte, if necessary */
    if (nleft == 1)
    {
      *(u_char *) (&answer) = *(u_char *) w;
      sum += answer;
    }
    /* add back carry outs from top 16 bits to low 16 bits */
    sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
    sum += (sum >> 16);             /* add carry */
    answer = ~sum;              /* truncate to 16 bits */
    return (answer);
}

void* thread_for_icmp(void* arg)
{
    struct iphdr *ip, *ip_reply;
    struct icmphdr *icmp, *icmp_reply;
    struct sockaddr_in connection;
    char *dst_addr="192.168.11.32";
    unsigned char *packet, *buffer;
    int sockfd, optval, ret=-1;
    socklen_t addrlen;

    arg = arg; 

    /* open ICMP socket */
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    printf("Socket is %d in thread\n", sockfd) ; 

    packet = (unsigned char*)malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));
    buffer = (unsigned char*)malloc(sizeof(struct iphdr) + sizeof(struct icmphdr));

    if(packet == NULL || buffer == NULL)
    {
        perror("Error in malloc") ;
    }

    memset(packet, 0, sizeof(struct iphdr) + sizeof(struct icmphdr)); 
    memset(buffer, 0, sizeof(struct iphdr) + sizeof(struct icmphdr)); 

    ip = (struct iphdr*) packet;
    icmp = (struct icmphdr*) ((char*)packet + sizeof(struct iphdr));

    ip->ihl         = 5;
    ip->version     = 4;
    ip->tot_len     = sizeof(struct iphdr) + sizeof(struct icmphdr);
    //ip->tot_len     = 48;
    ip->id       = random()%5985;
    ip->protocol    = IPPROTO_ICMP;
    ip->saddr       = inet_addr("192.168.11.34");
    ip->daddr       = inet_addr(dst_addr);
//    ip->daddr       = inet_addr("8.8.8.8");
    ip->ttl         = 64;
    ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); 

    icmp->type      = ICMP_ECHO;
    icmp->code           = 0;
    icmp->un.echo.id     = 0;
    icmp->un.echo.sequence   = 0;
    icmp->checksum       = 0;
    icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));

    //Dumping headers to a file, to be viewed using hexdump
    int ip_file = open("header",O_CREAT|O_RDWR);

    if(ip_file == -1)
    {
        perror("Error in file opening");
    }

    ret = write(ip_file, packet, sizeof(struct iphdr) + sizeof(struct icmphdr));

    if(ret == -1)
    {
        perror("Error in write"); 
    }
    else
    {
        printf("Wrote %d bytes\n", ret) ;
    }

    close(ip_file);

    //binding to a specific interface
    struct ifreq ifr;
    memset(&ifr, 0, sizeof (ifr));
    snprintf (ifr.ifr_name, sizeof (ifr.ifr_name), "enp1s0");
    if (ioctl (sockfd, SIOCGIFINDEX, &ifr) < 0)
    {
        //Failed to find interface on device
        printf("Failed to find interface on device\n");
        return NULL;
    }

    if (setsockopt (sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof (ifr)) < 0)
    {
        //Failed to bind to interface enp2s0
        printf("Failed to bind to interface %s\n",ifr.ifr_name);
        return NULL;
    }
    struct timeval tv;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
    {
        printf("Unable to set timeout\n");
        return NULL;
    }

     /* IP_HDRINCL must be set on the socket so that the kernel does not attempt 
 *      *  to automatically add a default ip header to the packet*/
    ret = setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(int));

    if(ret == -1)                                  
    {                                           
        perror("Error in setsockopt");       
    }                 
    connection.sin_family       = AF_INET;
    connection.sin_addr.s_addr  = ip->daddr;

    printf("Packet length is: %d\n",ip->tot_len);

    //printing packet, byte by byte, in hex, before sending
    unsigned char ch = 0; 

    while ( ch<28)
    {
        //printf("%x ",packet[ch]);
        printf("0x%02x ", packet[ch]);
        ch++;
    }
    printf("\n"); 
        ret = sendto(sockfd, (void*)packet, ip->tot_len, 0, (struct sockaddr *)&connection, sizeof(struct sockaddr));

    printf("Sent %d byte packet to %s  ret = %d\n", ip->tot_len, dst_addr,  ret);
//  }
    addrlen = sizeof(connection);
    if (recvfrom(sockfd, buffer, sizeof(struct iphdr) + sizeof(struct icmphdr), 0, (struct sockaddr *)&connection, &addrlen) < 0)
        {
        perror("recv");
        }
    else
    {
        ip_reply = (struct iphdr*) buffer;
        icmp_reply = (struct icmphdr*) (buffer + sizeof(struct iphdr));
        printf("Received type %d\n", icmp_reply->type);
        printf("icmp code %d\n", icmp_reply->code);
        printf("TTL: %d\n", ip_reply->ttl);
        printf("CheckSum: %d,%d\n", ip_reply->check,icmp_reply->checksum);
    }
    free(packet);
    free(buffer);
    close(sockfd);  

    pthread_exit(NULL);
}

int main()
{
    pthread_t thread;
    int ret; 

    ret = pthread_create(&thread, NULL, thread_for_icmp, NULL); 

    if(ret == -1)
    {
        perror("Error in thread create");
    }

    ret = pthread_join(thread,NULL);

    if(ret == -1)
    {
        perror("Error in thread join");
    }
    else
    {
        printf("Thread exited succesfully\n") ;
    }

    return 0;
}

1 个答案:

答案 0 :(得分:1)

optval setsockopt调用中的IP_HDRINCL值未初始化。因此,我怀疑流程版本从前main代码获得了一些非零的剩余值,而线程版本从原始堆栈获得零值。

如果您在致电optval = 1;之前设置了setsockopt,那么它应该有效。

另请注意,IP和ICMP标头中的多字节字段应按网络字节顺序构造。幸运的是,即使正在使用tot_len,内核也会填充IP_HDRINCL(请参阅raw(7)以供参考)。