【问题标题】:Thread vs process: icmp packet creation fails in thread线程与进程:icmp 数据包创建在线程中失败
【发布时间】:2017-04-04 19:04:45
【问题描述】:

我想通过 C 程序在 linux 的 posix 线程中生成一个 ICMP 回显请求。

作为试用,我在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;
}

【问题讨论】:

  • 愚蠢的问题:你有同样的行为是你从main() 调用HandleFailoverStrategy 没有线程?你是怎么编译的?
  • @purplepsycho 没有线程,它按预期工作。编译是 gcc filename -o executable_name 。使用 -lpthread 作为线程代码。
  • $dhruv 可以试试-pthread而不是-lpthread,然后加上-Wall
  • @purplepsycho 忘了说。使用了 -Wall 和 -Wextra。并且没有链接问题。所以 -lphread 或 -pthread 无关紧要。
  • 如果您在strace 下运行这两个系统调用,您会发现系统调用有何不同?

标签: c linux multithreading sockets pthreads


【解决方案1】:

setsockopt IP_HDRINCL 调用中的 optval 值未初始化。因此,我怀疑进程版本正在从 pre-main 代码中获取一些非零剩余值,而线程版本正在从原始堆栈中获取零值。

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

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

【讨论】:

  • 成功了。将 optval 初始化为 1,代码按预期运行。将更深入地研究它并发布一些链接以获得确切的理由。关于字节顺序,做过那些试验。已发布此代码以显示最基本的代码,而不是在线程中工作。非常感谢您的帮助
猜你喜欢
  • 2013-04-06
  • 2017-03-01
  • 1970-01-01
  • 2019-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 2013-10-16
相关资源
最近更新 更多