何时在C中使用ntohs和ntohl?

时间:2014-12-04 16:57:07

标签: c packet packet-capture packet-sniffers

我在何时使用ntohs和ntohl时非常困惑。我知道你在uint16_t和ntohl uint32_t使用ntohs的时候。但是那些使用unsigned int或者指定了特定位数的那些(例如u_int16_t doff:4;)。

这是我的工作代码,以说明问题:

// Utility/Debugging method for dumping raw packet data
void dump(const unsigned char *data, int length) {
    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]);
        if (i < 5) {
            printf(":");
        }
    }

    printf("\nDestination MAC: ");
    unsigned short ethernet_type = ntohs(eth_header->ether_type);
    printf("\nType: %hu\n", ethernet_type); //Why not nthos?

    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; //why no nthos or nthol
        printf("\nip_hdr->ip_hl: %u", ip_hdr->ip_hl); //why no nthos or nthol
        printf("\nIP Version: %u", ip_hdr->ip_v); //why no nthos or nthol
        printf("\nHeader Length: %u", ip_hdr->ip_hl); //why no nthos or nthol
        printf("\nTotal Length: %hu", ntohs(ip_hdr->ip_len)); //?is this right?

        // TCP Header
        printf("\n== TCP HEADER ==\n");
        struct tcphdr *tcp_hdr = (struct tcphdr*) (data + sizeof(struct ether_header) + size_ip);
        unsigned int size_tcp = tcp_hdr->doff * 4; //why no nthos or nthol
        printf("\n Source Port: %" PRIu16, ntohs(tcp_hdr->th_sport)); 
        printf("\n Destination Port: %" PRIu16, ntohs(tcp_hdr->th_dport));
        printf("\n fin: %" PRIu16, tcp_hdr->fin ); //As this is 1 bit, both nthos or nthol will work
        printf("\n urg: %" PRIu16, tcp_hdr->urg ); //As this is 1 bit, both nthos or nthol will work
        printf("\n ack_seq: %" PRIu32, ntohl(tcp_hdr->ack_seq));

        u_int16_t sourcePort = ntohs(tcp_hdr->th_sport);
        u_int16_t destinationPort = ntohs(tcp_hdr->th_sport);

        if (sourcePort == 80 || destinationPort == 80){
            printf("\n\nPORT 80!!!\n");

            //Transport payload!
            printf("\n\  === TCP PAYLOAD DATA == \n");

            // Decode Packet Data (Skipping over the header)
            unsigned int headers_size = ETH_HLEN + size_ip + size_tcp;
            unsigned int data_bytes = length - headers_size;
            const unsigned char *payload = data + headers_size;

            const static int output_sz = 500; // Output this many bytes at a time
            while (data_bytes > 0) {
                int output_bytes = data_bytes < output_sz ? data_bytes : output_sz;
                // Print data in raw hexadecimal form
                printf("| ");
                // Print data in ascii form
                for (i = 0; i < output_bytes; ++i) {
                    char byte = payload[i];
                    if ( (byte > 31 && byte < 127) || byte == '\n') {
                        // Byte is in printable ascii range
                        printf("%c", byte);  //why no nthos or nthol
                    } else {
                        printf(".");
                    }
                }
                payload += output_bytes;
                data_bytes -= output_bytes;
            }
        }

    }

    pcount++;
}

正如你所看到的,我有时会使用ntohs / ntohl,有时我也不会使用ntohs / ntohl。我不明白何时使用它。

2 个答案:

答案 0 :(得分:5)

  

但是那些带有unsigned int的那些

原则上,如上所述,C不保证unsigned int的大小;有intunsigned int为16位的平台,如PDP-11,以及带有一些编译器的摩托罗拉68k处理器(其他编译器使它们成为32位),这可能仍然存在适用于某些16位微处理器。

因此,如果您通过网络发送数据,最好使用<stdint.h>中定义的类型(如果可用)。

实际上,您使用的计算机几乎肯定会有32位unsigned intalthough some Cray machines have 64-bit int and even short!但最好还是使用<stdint.h>中定义的类型。

  

或指定特定位数的那些(例如u_int16_t doff:4;)。

如果一个值短于一个字节,就像4位字段的情况一样,字节顺序是无关紧要的。

但是,请注意,在1,2或4个字节序列中的位字段的顺序未由C指定,所以你不应该在通过网络发送的数据中使用位字段。 (是的,一些UN * Xes碰巧在IPv4和TCP头的结构中使用它们,但这只有在供应商用于支持所有以相同顺序放置位域的架构的编译器时才有效,如果是第三方像GCC这样的编译器做同样的事情。)

因此,处理IPv4标头的正确方法是执行诸如

之类的操作
struct ip {
        uint8_t         ip_vhl;         /* header length, version */
#define IP_V(ip)        (((ip)->ip_vhl & 0xf0) >> 4)
#define IP_HL(ip)       ((ip)->ip_vhl & 0x0f)
        uint8_t         ip_tos;         /* type of service */
        uint16_t        ip_len;         /* total length */
        uint16_t        ip_id;          /* identification */
        uint16_t        ip_off;         /* fragment offset field */
#define IP_DF 0x4000                    /* dont fragment flag */
#define IP_MF 0x2000                    /* more fragments flag */
#define IP_OFFMASK 0x1fff               /* mask for fragmenting bits */
        uint8_t         ip_ttl;         /* time to live */
        uint8_t         ip_p;           /* protocol */
        uint16_t        ip_sum;         /* checksum */
        struct  in_addr ip_src,ip_dst;  /* source and dest address */
};

使用 结构来声明指向IP标头的ip_hdr指针,并且:

  • 要提取版本,请使用IP_V(ip_hdr);
  • 要提取标题长度,请使用IP_HL(ip_hdr)

如果供应商的ip.h标头使用位域,请不要使用供应商的ip.h标头;使用自己的标题。实际上,即使供应商的ip.h标题使用位域,也不要使用供应商的ip.h标题;使用自己的标题。毕竟,并不是因为IP头的定义依赖于操作系统......

(这就是tcpdump现在为几个版本所做的事情;上面的内容取自ip.h。)

答案 1 :(得分:2)

ntohXhtonX函数旨在帮助您构建与硬件无关的协议,可能是为了通过网络进行通信,但其他目的也是可能的。这些协议应该精确地关于数据包的布局,包括以与硬件无关的方式发送或接收的每个元素的大小。

由于C标准未指定unsigned int的大小,因此该类型不能用于与硬件无关的协议中的数据交换。您交换的所有元素都需要具有特定的大小。请改用<stdint.h>标题中定义的类型。

另一方面,比特字段应该以不同的方式处理。您的代码需要将它们转换为特定标准大小之一,然后以与硬件无关的方式(即使用htonX函数)将该类型放在线路上。当位字段的大小小于8时,将其强制转换为uint8_t,并将其置于电线上而不进行转换。