我如何发送带有 Ip 标志和 Tcp 控制标志的 TCP 原始套接字

How i can Send TCP Raw Socket with Ip Flags and Tcp Control Flags

我尝试编写一个代码,它能够构建和发送原始套接字,能够填充 IP Header 和 TCP Header 中的所有字段。

我实际上可以为 tcp header 设置所有字段。 如果我设置 IP 标志

short int ip_moreFrag :1, ip_doNotFrag:1, ip_reserved : 1;

0并在Wireshark中跟踪数据包,我看到我设置的所有字段也已设置。 enter image description here

但是如果我将 Ip Flags 设置为 1 并在 Wireshark 上跟踪数据包,我可以看到我的数据包的 Protokol 更改为 Ipv4 并且没有在 Wireshark 中设置标志。 Wireshark IPv4 Protokol 为什么?

如果我将其中一个 ip 标志设置为 1,我找不到程序不发送 tcp 数据包的问题。 你能帮帮我吗?


这是我的代码:

// Run as root or SUID 0,

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

// Packet length
#define PCKT_LEN 8192

struct ipheader {
    unsigned char ip_hl :4, ip_v :4;
    unsigned char ip_tos :8;
    unsigned short int ip_len :16;
    unsigned short int ip_id :16;
    unsigned short int ip_off :13;

    unsigned short int ip_moreFrag :1, ip_doNotFrag:1, ip_reserved : 1;

    unsigned char ip_ttl :8;
    unsigned char ip_p :8;
    unsigned short int ip_sum :16;
    unsigned int iph_sourceip :32;
    unsigned int iph_destip :32;
};


/* Structure of a TCP header */
struct tcpheader {
    unsigned short int th_sport :16;
    unsigned short int th_dport :16;
    unsigned int th_seq :32;
    unsigned int th_acknum :32;

    unsigned char th_reserved :4, th_off :4;


    unsigned short int th_fin :1, th_syn :1, th_rst :1, th_psh :1, th_ack :1,
            th_urg :1, th_cwr :1, th_ece :1;


    unsigned short int th_win :16;
    unsigned short int th_sum :16;
    unsigned short int th_urp :16;
};


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

int main(int argc, char *argv[]) {
    int sd;

    // No data, just datagram
    char buffer[PCKT_LEN];

    // The size of the headers
    struct ipheader *ip = (struct ipheader *) buffer;
    struct tcpheader *tcp = (struct tcpheader *) (buffer
            + sizeof(struct ipheader));
    struct sockaddr_in sin, din;

    int one = 1;
    const int *val = &one;
    memset(buffer, 0, PCKT_LEN);

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

    // The source is redundant, may be used later if needed
    // Address family
    sin.sin_family = AF_INET;
    din.sin_family = AF_INET;

    // Source port, can be any, modify as needed
    sin.sin_port = htons(atoi("1000"));
    din.sin_port = htons(atoi("1000"));

    // Source IP, can be any, modify as needed
    sin.sin_addr.s_addr = inet_addr("1.2.3.4");
    din.sin_addr.s_addr = inet_addr("127.0.0.1");

    // IP structure
    ip->ip_v = 4;
    ip->ip_hl = 5;

    ip->ip_tos = 16;
    ip->ip_off = 0;

    ip->ip_reserved = 0;
    ip->ip_doNotFrag = 0;
    ip->ip_moreFrag = 0;

    ip->ip_len = htons(sizeof(struct ipheader) + sizeof(struct tcpheader));
    ip->ip_id = htons(54321);
    ip->ip_ttl = 255;
    ip->ip_p = 6; // TCP
    ip->ip_sum = 0; // Done by kernel

    ip->iph_sourceip = inet_addr("1.2.3.4");

    ip->iph_destip = inet_addr("127.0.0.1");

    tcp->th_sport = htons(atoi("1000"));

    tcp->th_dport = htons(atoi("1000"));
    tcp->th_seq = htonl(0);
    tcp->th_acknum = 0;

//  tcp->th_hlen = 4;


    tcp->th_reserved = 0;
    tcp->th_off = 5;


    tcp->th_fin = 1;

    tcp->th_syn = 1;
    tcp->th_rst = 1;
    tcp->th_psh = 1;
    tcp->th_ack = 1;
    tcp->th_urg = 1;
    tcp->th_cwr = 1;
    tcp->th_ece = 1;


    tcp->th_win = htons(32767);
    tcp->th_sum = 0; // Done by kernel
    tcp->th_urp = 0;

    // IP checksum calculation
    ip->ip_sum = htons(
            csum((unsigned short *) buffer,
                    (sizeof(struct ipheader) + sizeof(struct tcpheader))));

    // Inform the kernel do not fill up the headers' structure, we fabricated our own
    if (setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {
        perror("setsockopt() error");
        exit(-1);
    } else
        printf("setsockopt() is OK\n");

    unsigned int count;

    if (sendto(sd, buffer, ip->ip_len, 0, (struct sockaddr *) &din, sizeof(din))
            < 0)
            // Verify
            {
        perror("sendto() error");
        exit(-1);
    } else
        printf("Count #%u - sendto() is OK\n", count);

    close(sd);
    return 0;
}

您的程序假定您的编译器在这些结构成员的 2 字节单元内安排从 least-significant 到 most-significant 的位域:

    unsigned short int ip_off :13;
    unsigned short int ip_moreFrag :1, ip_doNotFrag:1, ip_reserved : 1;

但 Wireshark 表明该假设不正确。如果您查看第二次数据包捕获,显然,ip_moreFragip_doNotFragip_reserved 成员中的两个设置为 1,您会看到 Wireshark 不显示IP header 中设置的任何相应 RDFMF 标志位.那些标志位在数据包中都是0

相反,设置这些位域会导致 片段偏移量 字段中的两个位被设置为 1。这导致 Wireshark 认为此数据包仅包含较大数据报的一个片段,并且此数据包中跟随其 IP header 的数据属于较大数据报中的位置 768。位置 768 的数据不能是 TCP header(TCP header 会在位置 0 处找到)因此 Wireshark 不会将此数据包中的任何数据解释为 TCP header .

要修复,您可以重新排序 struct ipheader 中的位域,以便它们映射到 on-the-wire header 中的正确位置,或者您可以切换到不同的技术这不依赖于构建 header 的位域。使用位域是脆弱的,因为编译器在如何在内存中排列位域方面有很大的自由度。即使您设法使位域机制与您现在使用的编译器一起工作,也不能保证它会以相同的方式与不同的编译器或当前编译器的不同版本或调用您的编译器一起工作具有不同的编译选项。

如果您决定继续使用位域,那么您应该检查 struct tcpheader 中的 TCP 标志位是否符合您的要求。它们可能与 on-the-wire 格式的正确位匹配,也可能不匹配。从第一个数据包捕获中你所知道的是,如果你将它们全部设置为 1 那么你会在标志字段中得到 0xff,但你不确定各种结构成员是否控制了正确的位。例如,尝试将 th_syn 设置为 0 并查看 Wireshark 是否同意 SYN 标志已被关闭。