2016-11-21 72 views
2

我想通过C程序在linux中的posix线程中生成ICMP echo请求。线程vs进程:线程中的icmp数据包创建失败

作为一个试用,我写了一个示例代码main()ICMP回声及其答复按预期工作。数据包长度为28(20字节IP头+ 8字节ICMP头)。

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

但是,在线程,sendto()返回28,而在tcpdump观察时,这个数据包被示出具有长度48,以及如下面IP坏的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 [email protected]" 
     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 [email protected] 
     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 [email protected]@.Id..." 
     0x0010: c0a8 0b20 4500 1c00 4b0d 0000 4001 7c5d [email protected]|] 
     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; 
} 
+0

愚蠢的问题:你是否有同样的行为,你是否从main()调用'HandleFailoverStrategy'而不使用线程?你如何编译? – purplepsycho

+0

@purplepsycho没有线程,它按预期工作。 编译为gcc filename -o executable_name。用于线程代码的-lpthread。 – Dhruv

+0

$ dhruv也许尝试'-pthread'而不是'-lpthread',并添加'-Wall' – purplepsycho

回答

1

setsockoptIP_HDRINCL调用的optval值不被初始化。因此,我怀疑进程版本正在从main代码获得非零的剩余值,而线程版本正在从原始堆栈中获得零值。

如果在拨打setsockopt之前设置optval = 1;,它应该可以工作。

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

+0

它工作正常。将optval初始化为1,代码按预期行事。将挖掘更多,发布一些链接,以确切的理由。 关于字节顺序,已经完成了这些试用。已发布此代码以显示最基本的代码,而不是在线程中工作。 非常感谢您的帮助 – Dhruv