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が既にフィールド名として使用されているのがややこしい。



コメント