解析TCP数据包

5
我正在尝试解析一个TCP数据包,然后将其分配给指向有效负载开始的指针。
我正在使用C语言编写代码,以下是我的代码:
void dump(const unsigned char *data, int length) { //*data contains the raw packet data
    unsigned int i;
    static unsigned long pcount = 0;

    // Decode Packet Header
    struct ether_header *eth_header = (struct ether_header *) data;

    printf("\n\n === PACKET %ld HEADER ===\n", pcount);

    printf("\nSource MAC: ");
    for (i = 0; i < 6; ++i) {
        printf("%02x", eth_header->ether_shost[i]); //? Why don't i use nthos here?
        if (i < 5) printf(":");
    }

    unsigned short ethernet_type = ntohs(eth_header->ether_type);
    printf("\nType: %hu\n", ethernet_type);

    if (ethernet_type == ETHERTYPE_IP) { //IP Header
        printf("\n  == IP HEADER ==\n");
        struct ip *ip_hdr = (struct ip*) data + sizeof(struct ether_header);
        unsigned int size_ip = ip_hdr->ip_hl * 4;
        printf("\nIP Version: %u", ip_hdr->ip_v); //? Nthos or no nthos
        printf("\nHeader Length: %u", ip_hdr->ip_hl); //? Nthos or no nthos
        printf("\nTotal Length: %hu", ntohs(ip_hdr->ip_len)); //? Nthos or no nthos

        // TCP Header
        printf("\n== TCP HEADER ==\n");
        struct tcphdr *tcp_hdr = (struct tcphdr*) data + sizeof(struct ether_header) + size_ip;
        printf("\n Source Port: %" PRIu16, nthos(tcp_hdr->th_sport));
        printf("\n Destination Port: %" PRIu16, nthos(tcp_hdr->th_dport));
        printf("\n fin: %" PRIu16, tcp_hdr->fin);
        printf("\n urg: %" PRIu16, tcp_hdr->urg);
        printf("\n ack_seq: %" PRIu32, ntohl(tcp_hdr->ack_seq));

        //Transport payload! i.e. rest of the data
        const unsigned char *payload = data + ETH_HLEN + size_ip + sizeof(struct tcphdr) + tcp_hdr->doff;

    }

我确定这段代码有错误,因为端口号都很奇怪。没有一个分配到80。输出的IP版本也可能非常奇怪(比如版本11)。我做错了什么?谢谢!
另外,我不确定何时使用nthos以及何时不使用。我知道nthos用于16位无符号整数,而nthol用于32位无符号整数,但我知道你不能在那些数据包中的所有内容中都使用它们(例如:tcp_hdr->fin)。为什么某些事情可以使用,而其他事情则不行?
非常感谢!
编辑:
感谢Art修复了大部分问题。我编辑了我的tcp_hdr和ip_hdr,所以现在括号是正确的!
我还有两个问题:
  • 负载的前10个字节有奇怪的符号(所以我认为我没有正确分配负载)。
  • 我仍然不确定何时使用nthos / nthol。 我知道u_int16_t是ntohs,u_int32_t是ntohl。 但是对于带符号int或无符号short int的内容呢?例如,我没有为ip_v使用ntohs或nthol才能使其正常工作。 为什么不呢? “ip_hdr-> ip_hl”是nthol吗?等等...

编辑2

我已经修复了我的负载未正确输出的原因(这是因为我计算TCP_header大小错误)。

尽管我仍然困惑于何时使用nthos,但我会将其作为单独的问题放在这里,因为我认为我在这篇文章中提出了太多问题!

何时在C中使用ntohs和ntohl?


你知道像是端口号这样的东西都是以 网络字节序 存储的吗?使用 ntohs 将一个短整型(16位)从网络字节序转换为主机字节序。 - Some programmer dude
我尝试了两种方法,但是都得到了奇怪的数字。无论如何,现在正在更新代码! - Yahya Uddin
是否有其他字段看起来正确?例如源/目的地址?以太网头部?与在Wireshark中捕获的相同数据包相比如何?而且您确实在原始套接字上接收到了它吗?并且接收到了整个数据包吗? - Some programmer dude
1个回答

5

你的问题很可能出在这里:

struct ip *ip_hdr = (struct ip*) data + sizeof(struct ether_header);
struct tcphdr *tcp_hdr = (struct tcphdr*) data + sizeof(struct ether_header) + size_ip;

首先,(struct ip*) data + sizeof(struct ether_header) 将data转换为 struct ip *, 然后再将 sizeof(struct ether_header) 加到它上面。由于指针算术的原因,这并不会像你期望的那样工作。

如果问题还不清楚,这里有一个简单的程序可以指引你正确地解决问题:

#include <stdio.h>

struct foo {
    int a, b;
};

int
main(int argc, char **argv)
{
    char *x = NULL;

    printf("%p\n", x);
    printf("%p\n", (struct foo *)x + 4);
    printf("%p\n", (struct foo *)(x + 4));

    return 0;
}

这个问题已经解决了,就像我遇到的几乎所有问题一样。你介意回答一下我的第二个问题吗?什么时候使用ntohs?我目前制定的规则是对于任何类型为u_int16_t的内容都使用ntohs,而对于u_int32_t则使用ntohl。但是对于signed int或unsigned short int这样的内容呢?例如,我没有使用ntohs来处理ip_v,它为什么能正常工作?谢谢。 - Yahya Uddin
查看IP头和定义。基本规则是:不要转换长度为1B或更短的值(ip_v,ip_hl)。对于2B值(ip_len,ip_p),使用ntohs。对于4B值,请使用ntohl。 “但是有符号int或无符号short int怎么办”:它是否带符号并不重要(我认为ip hdr中没有任何带符号的值),因此有符号int只是4B值,而无符号short是2B值。您可以在此处查看IP头:http://en.wikipedia.org/wiki/IPv4#Header - davak
所以,“ip_hdr->ip_hl”使用htol吗?因为它的类型是“unsigned int ip_hl:4;” 当我添加htol时,程序甚至在读取TCP头之前就终止了(可能意味着它正在读取不应该读取的内存部分)。然而,当我没有使用ntohl或ntohs时,它却正常工作! - Yahya Uddin
1
不,ip_hl只有4位。仅在值长度为2字节或更长时才谈论字节顺序才有意义。如果您有一个变量是1字节,则在网络字节顺序和主机字节顺序中是相同的(如果您更改1字节的顺序,则不会改变)。 - davak
1
澄清一下你的例子:我不知道你有什么ip.h,但我的说法是u_char ip_hl:4(即1个字节)。如果你的说unsigned int,那只能意味着unsigned int ip_tos:8,unsigned int ip_len:16跟随。如果你在ip_hl上调用ntohl,它会将你的4位值视为32位,因此它会处理我之前提到的所有值,因此它完全改变了头文件的含义。 - davak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接