【问题标题】:Why does not the server respond with syn-ack packets when I send syn-packets with raw sockets?当我发送带有原始套接字的同步数据包时,为什么服务器不响应同步确认数据包?
【发布时间】:2014-01-20 14:27:57
【问题描述】:

我正在试验原始套接字,我刚刚编写了一个小程序,它发送带有syn 标志集的 TCP 数据包。我可以在服务器端看到 Wireshark 附带的数据包,它们看起来不错,但服务器从不响应任何 syn-ack 数据包。

我已经比较了我的程序构造的syn 数据包(参见下面的代码)和hping3 发送的数据包(因为hping3 的数据包总是得到syn-ack)。我的 syn 数据包和 hping3 的 syn 数据包之间唯一不同的是 ip identification 数字、tcp source port(在 hping3 中随机)、tcp sequence number(在 hping3 中也是随机的)和 ip checksum 字段。所有这四个字段都基于一些随机数,这就是它们不同的原因。 所有其他字段都相同!但是我的程序没有得到任何同步确认,但 hping3 有!

我使用 Kali Linux 发送数据包(当然是 root)和 CentOS 发送服务器。

我是否错过了一些重要的东西,或者只是误解了什么?

删除的代码

编辑
这是客户端Wireshark捕获的整个数据包(下面分为4张图片)。注意hping3发送的数据包除了ip标识、源端口、序列号和校验和的值是完全相等的:

图片已移除


这是数据包的十六进制转储。

Hexdump 已移除

EDIT 2

好的,现在我已经根据RFC793 创建了伪标头。伪标头仅用于 tcp 校验和计算。现在 IP 标头似乎是正确的,但 Wireshark 抱怨该数据包不包含完整的 TCP 标头,而且它看起来确实已损坏,因为某些字段包含我没有设置的奇怪值。

首先,我为 tcp 标头和伪标头分配了一个缓冲区(称为tcp_header)。其次,我为 ip 标头创建一个缓冲区,其中包含 ip、tcp 和伪标头的空间。
首先我用它的数据填充tcp_header,然后我将它复制到ip_header,然后用sendto函数发送它。

当我将tcp_packet 的内容复制到ip_packet 时出现问题还是我做错了什么?

#include <cstdlib>
#include <stdio.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#define __FAVOR_BSD 1
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <sys/ioctl.h>
#include <string.h>
#include <iostream>
#include <net/ethernet.h>
#include <time.h>

#define PCKT_LEN 1024

struct pseudohdr
{
    __u32 saddr;
    __u32 daddr;
    __u8  zero;
    __u8  protocol;
    __u16 lenght;
};

#define PSEUDOHDR_SIZE sizeof(struct pseudohdr)

unsigned short csum(unsigned short *buf, int len) {
    unsigned long sum;
    for(sum=0; len>0; len-=2)
        sum += *buf++;
    sum = (sum >> 16) + (sum &0xffff);
    sum += (sum >> 16);
    return (unsigned short)(~sum);
}


int main(int argc, char** argv) {

    srand(time(NULL));

    char *ip_packet = new char[sizeof(struct iphdr) + 
                               sizeof(struct tcphdr)]();

    char *tcp_packet = new char[sizeof(struct pseudohdr) + 
                                sizeof(struct tcphdr)]();

    struct pseudohdr *pseudoheader = (struct pseudohdr*) tcp_packet;
    class tcphdr *tcp = (struct tcphdr *) (tcp_packet + sizeof(struct pseudohdr));
    class iphdr *ip = (struct iphdr *) ip_packet;


    class sockaddr_in sin, din;

    int sd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if(sd < 0) {
       perror("socket() error");
       exit(-1);
    } else {
        printf("socket()-SOCK_RAW and tcp protocol is OK.\n");
    }

    // Randomize src port
    int srcport = rand()%100+25000;

    sin.sin_family = AF_INET;           // Address family
    sin.sin_addr.s_addr = inet_addr("192.168.2.80");
    sin.sin_port = htons(srcport); // Source port

    din.sin_family = AF_INET;
    din.sin_addr.s_addr = inet_addr("192.168.2.6");
    din.sin_port = htons(80); // Destination port

    /* tcp pseudo header */
    memcpy(&pseudoheader->saddr, &sin.sin_addr.s_addr, 4);
    memcpy(&pseudoheader->daddr, &din.sin_addr.s_addr, 4);
    pseudoheader->protocol      = 6; /* tcp */
    pseudoheader->lenght        = htons(sizeof(struct pseudohdr) + sizeof(struct tcphdr));


    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 0;
    ip->tot_len = sizeof(class iphdr) + sizeof(class tcphdr);
    ip->id = htons((getpid() & 255) + rand()%100+30000);
    ip->frag_off = 0;
    ip->ttl = 32;
    ip->protocol = 6; // TCP
    ip->check = 0; // Done by kernel
    memcpy(&ip->saddr, (char*)&sin.sin_addr, sizeof(ip->saddr));
    memcpy(&ip->daddr, (char*)&din.sin_addr, sizeof(ip->daddr));


    // The TCP structure
    tcp->th_sport = htons(srcport);
    tcp->th_dport = htons(80);      // Destination port
    tcp->th_seq = htonl(rand()%100+1000);
    tcp->th_ack = htonl(rand()%30);
    tcp->th_off = 5;
    tcp->th_flags = TH_SYN;
    tcp->th_win = htons(1024);
    tcp->th_urp = 0;

    // Now calculate tcp checksum
    tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr));

    // Copy tcp_packet to ip_packet
    memcpy(ip_packet + sizeof(struct iphdr), tcp_packet+sizeof(struct pseudohdr), sizeof(struct tcphdr));

    // Bind socket to interface
    int one = 1;
    const int *val = &one;
    const char opt[] = "eth0";

    if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, (char *)&one, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(-1);
    }
    else
        printf("setsockopt() is OK\n");

    if(sendto(sd, ip_packet, ip->tot_len, 0, (sockaddr*)&din, sizeof(din)) < 0) {
       perror("sendto() error");
       exit(-1);
    }
    else
        printf("Send OK!");

    close(sd);
    return 0;
}

数据包的tcp内容:

图片已移除

编辑 3

现在我发现了一些有趣的东西。研究这张图片上的校验和:

校验和是网络顺序的,因此应该以相反的顺序读取,如0x06c0(而不是上面所说的0xc006)。这等于1728 的十进制值。 Wireshark 说正确的校验和应该是0x12c0,它给出的十进制值是4800
4800-1728=3072.这就是我的程序发送的所有数据包中 Wireshark 计算的 实际 校验和和 正确 校验和之间的差异。

所以,如果我只是将该值添加到校验和结果中:

tcp->th_sum = csum((unsigned short *) tcp_packet, sizeof(struct pseudohdr) + sizeof(struct tcphdr)) + 3072;

...然后所有数据包都得到正确的校验和并收到相应的SYN-ACK

为什么是幻数 3072???

【问题讨论】:

  • 也许校验和是错误的? (我不是说它,但如果这是我的调试问题,那将是我要检查的第一件事......)
  • 嗯,我实现校验和计算的方法和我在网上找到的所有关于原始套接字的教程一样,即:csum((unsigned short *) buffer, (sizeof(class iphdr) + sizeof(class tcphdr)))csum 函数在上面代码的顶部。
  • 它说,就在那里。它甚至以黄色突出显示。当您不确认任何内容时,您正在设置一个非零的已确认序列号。服务器正在丢弃您的无效数据包。
  • 长度正确,但是 TCP 应该从哪里开始(偏移 22h)源和目标 IP 地址重复。将 tcp 数据包复制到 ip 数据包的 memcpy 将是第一个查看的位置。
  • 3072 = 2048 + 1024。这对你有什么建议吗?

标签: c++ c tcp raw-sockets


【解决方案1】:

我对您使用的校验和算法不满意。史蒂文斯建议的那个:

uint16_t
in_cksum(uint16_t *addr, int len)
{
        int                     nleft = len;
        uint32_t                sum = 0;
        uint16_t                *w = addr;
        uint16_t                answer = 0;

        /*
         * 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;
        }

                /* 4mop up an odd byte, if necessary */
        if (nleft == 1) {
                *(unsigned char *)(&answer) = *(unsigned char *)w ;
                sum += answer;
        }

                /* 4add 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);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-26
    • 2019-12-27
    • 1970-01-01
    • 2012-03-06
    • 2012-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多