用于接收 ip/tcp 数据包但也在其中响应的接收器线程不太工作

Receiver thread for receiving ip/tcp packets but also responding in it not quite working

这是我几乎完整的代码,我认为它为 TCP 数据包发送有效的 IP header。但是我的代码没有通过 Wireshark 测试,它没有被识别为 TCP 数据包;相反,Wireshark 只是将其标记为没有任何源和目标 IP 的可怕原始数据包。这确实是一个简单的 IP 数据包,我确信它可以通过 Wireshark 测试,但它没有。

我做错了什么?目前没有进一步了解如何调试它的线索,我还可以在我的 tcp 或 ip header 中设置什么。之前它在 Wireshark 碎片化 TCP 数据包中给我消息这甚至意味着什么,然后开始说它的简单格式错误的数据包现在它给我消息这些响应数据包响应只是从我的代码发送的原始数据包没有源或目标 ips,考虑因为 TCP 甚至没有显示该窗格

void * receiver(void *data)
{

    
    int recvlen = -1;
    int writelen = -1;
    

    while (!_do_exit) {
        char buf[VPN_MAX_MTU] = {0};
        char buf_1[VPN_MAX_MTU] = {0};
        memset(buf,0,VPN_MAX_MTU);
        memset(buf_1,0,VPN_MAX_MTU);
        char *str_source=malloc(18);
        char *str_dest=malloc(18);
        memset(str_source,0,18);
        memset(str_dest,0,18);
       
        recvlen=read(_tun_fd,buf,VPN_MAX_MTU);
       debug("SR:%04d\n", recvlen);
       struct iphdr *iph=(struct iphdr *)buf;   
       struct iphdr *ip=(struct iphdr *)buf_1;
       char str_src[18]={0};
       char str_dest_t[18]={0};
       memcpy(&ip->saddr,&iph->daddr,sizeof(uint32_t));
       memcpy(&ip->daddr,&iph->saddr,sizeof(uint32_t));
       printf("IN %s %s\n",get_ip_str_1(iph->saddr,str_src),get_ip_str_1(iph->daddr,str_dest_t));
       //clear the str_str and str_dest_t with memset(,0)
       printf("OUT %s %s\n",get_ip_str_1(ip->saddr,str_src),get_ip_str_1(ip->daddr,str_dest_t));
        
    ip->ihl         = 5;
    ip->version     = 4;
    ip->tot_len     = sizeof(struct iphdr) + sizeof(struct tcphdr);
    ip->protocol    = IPPROTO_TCP;
    ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); 
    
       uint16_t k=csum(ip,sizeof(*ip));
       ip->check=k;
       printf("checksum %d | %d\n",iph->check,ip->check);
       int i=iph->ihl*4;
       struct tcphdr *tcph=(struct tcphdr *)(buf+i);
       int j=(struct iphdr *)(ip->ihl*4);
       struct tcphdr *tcp=(struct tcphdr *)(buf_1+j);
       populate_tcp_some(tcph,tcp);
       printf("received syn = %d\n",tcph->syn); 
       if(tcph->syn==1)
       {
               populate_tcp_some(tcph,tcp);
           tcp->syn=1;
           tcp->ack=1;
           tcp->dest=tcph->source;
           tcp->source=htons(80);
           printf("received tcp syn = %d\n",tcph->syn);
       }
       else{
               populate_tcp_some(tcph,tcp);
           tcp->syn=0;
           tcp->ack=1;
           tcp->dest=tcph->source;
           tcp->source=htons(80);
     
           printf("sending tcp syn = %d ack = %d\n",tcp->syn,tcp->ack);
       
       }
       tcp->check=0;
       tcp->check=tcp_chksum(ip,tcp);
       printf("checksums %d | %d\n",tcph->check,tcp->check);
       
        if (recvlen > 0) {

            writelen = write(_tun_fd, buf_1, sizeof(*ip));
            debug("TW:%04d\n", writelen);
            if (writelen < 0) {
                debug("%s: rwrite() %s [%d]\n", _progname, strerror(errno), errno);
            }
        } else if (recvlen < 0) {
            debug("%s: rrecvfrom() %s\n", _progname, strerror(errno));
 
        } else if (recvlen == 0) {
 
        }
    }

    debug("** Receiver ending.\n");
    pthread_exit(NULL);
}

部分答案

到目前为止的聊天讨论摘要。

原来是TUN接口打开的问题,在一段代码中,没有显示。

打开TUN界面如this link所示。在“基本用法”部分中,有以下代码片段

int fd = open("/dev/net/tun", O_RDWR);
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
strncpy(ifr.ifr_name, "tun0", IFNAMSIZ);
ioctl(fd, TUNSETIFF, &ifr);

IFF_NO_PI 的用法在下面的“TUN 接口”部分进行了说明。如果未设置此标志,则数据包(即写入 recvlen=read(_tun_fd,buf,VPN_MAX_MTU); 缓冲区中的内容)以 struct tun_pi (link) 为前缀,其中包含一些标志和协议。 Protocol是以太网中ethertype一般写的值header.

因为OP没有设置IFF_NO_PI。变量buf中的IPheader包含struct tun_pi的4个字节,从代码中可以看出被忽略了。由于 OP 试图在没有 IP header 的地方解析 IP header,因此代码不起作用。

写入描述符也忽略了 struct tun_pi,我认为这导致内核无法正确解释数据包。

调试过程

  • 这里有来自 wireshark 的屏幕截图。人们可以看到,它包含一些以几个零和随机数开头的奇怪结构,而不是 IP 数据包。 IP header 应以字节 45 开头。所以下一步是弄清楚 bufbuf_1.
  • 中的值是什么
  • 打印缓冲区的内容后,结果是 buf00 80 开始,然后是 45 ...(... - IP 数据包的其余部分)。显然,buf 中的数据包没有被正确解析,buf_1 中填充了一些来自 buf 的随机值,而不是预期的值。寻找 tun 描述符返回的格式,我找到了上面的 link。
  • 当前数据包在 wireshark 中被解析为 IP header,但是 IP header 的 protocol 字段在 wireshark 中被解析为 ICMP。

代码中的问题

(1),是解析包的代码没有检查代码是否正确。

首先,在recvlen=read(_tun_fd,buf,VPN_MAX_MTU);行之后,数据包被解析,没有读取recvlen的值。稍后某处有 if (recvlen > 0) { 行,实际上应该在 read 行之后。

此外,在将 buf 转换为 iphdr 之前,需要检查 buf 是否至少为 20bypes(或 sizeof(struct iphdr) 长。在将 next header 转换为 TCP 之前需要检查协议确实是 TCP(根据 iphdr->protocol 的值)并且 buf 足够长以包含 TCP header

(2): ip->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);除非tcp中没有内容header(从代码看不出来),长度应该是总长度数据包的长度,而不是两个 headers

的长度

(3):writelen = write(_tun_fd, buf_1, sizeof(*ip));行最后一个参数应该是数据包的长度,也就是[=37=中应该写入的值]

(4):一些值可能需要从主机字节顺序转换为网络字节顺序。