IPv4ヘッダとTCPヘッダの構造は以下のようになっている。
Linuxのヘッダ構造体では、ビット単位でメンバの領域を指定するビットフィールドを用い、ヘッダ長やフラグといった1B未満のメンバを定義している。
struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix <asm/byteorder.h>" #endif __u8 tos; __be16 tot_len; __be16 id; __be16 frag_off; __u8 ttl; __u8 protocol; __sum16 check; __struct_group(/* no tag */, addrs, /* no attrs */, __be32 saddr; __be32 daddr; ); /*The options start here. */ };
struct tcphdr { __be16 source; __be16 dest; __be32 seq; __be32 ack_seq; #if defined(__LITTLE_ENDIAN_BITFIELD) __u16 res1:4, doff:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1; #elif defined(__BIG_ENDIAN_BITFIELD) __u16 doff:4, res1:4, cwr:1, ece:1, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1; #else #error "Adjust your <asm/byteorder.h> defines" #endif __be16 window; __sum16 check; __be16 urg_ptr; };
同様にDPDKのIPv4ヘッダもビットフィールドを用いて定義しているが、DPDKのTCPヘッダはビットフィールドを使用していない。
struct rte_tcp_hdr { rte_be16_t src_port; /**< TCP source port. */ rte_be16_t dst_port; /**< TCP destination port. */ rte_be32_t sent_seq; /**< TX data sequence number. */ rte_be32_t recv_ack; /**< RX data acknowledgment sequence number. */ uint8_t data_off; /**< Data offset. */ uint8_t tcp_flags; /**< TCP flags */ rte_be16_t rx_win; /**< RX flow control window. */ rte_be16_t cksum; /**< TCP checksum. */ rte_be16_t tcp_urp; /**< TCP urgent pointer, if any. */ } __rte_packed;
フラグを確認する際はビットマスクで論理積を取る。
struct rte_tcp_hdr *tcph; if (tcph->tcp_flags & RTE_TCP_SYN_FLAG) { printf("syn packet\n"); }
また、TCPヘッダ長を示すデータオフセットは4bitのフィールドだが、data_off
は予約領域を含めた8bitで定義されているためリトルエンディアン環境では24=16倍の値となる。IPv4ヘッダ長はipv4h->ihl << 2
と2bit左シフトで計算し、TCPヘッダ長はtcph->data_off >> 2
と2bit右シフトで計算する必要がある。
ビットフィールドは互換性を維持して導入できるものの、データオフセットを表さないdata_off
が既にフィールド名として使用されているのがややこしい。
コメント