Hardware Checksum support on NetBSD-current(2)
ちょっと真面目に調べる事にした。
前回書いたgem_attach()のコードで
ifp->if_capabilities |= IFCAP_CSUM_TCPv4_Tx;
というのがあったが、どんなフラグがあるのか、net/if.hを見てみよう:
/* Capabilities that interfaces can advertise. */ #define IFCAP_TSOv4 0x00080 /* can do TCPv4 segmentation offload */ #define IFCAP_CSUM_IPv4_Rx 0x00100 /* can do IPv4 header checksums (Rx) */ #define IFCAP_CSUM_IPv4_Tx 0x00200 /* can do IPv4 header checksums (Tx) */ #define IFCAP_CSUM_TCPv4_Rx 0x00400 /* can do IPv4/TCP checksums (Rx) */ #define IFCAP_CSUM_TCPv4_Tx 0x00800 /* can do IPv4/TCP checksums (Tx) */ #define IFCAP_CSUM_UDPv4_Rx 0x01000 /* can do IPv4/UDP checksums (Rx) */ #define IFCAP_CSUM_UDPv4_Tx 0x02000 /* can do IPv4/UDP checksums (Tx) */ #define IFCAP_CSUM_TCPv6_Rx 0x04000 /* can do IPv6/TCP checksums (Rx) */ #define IFCAP_CSUM_TCPv6_Tx 0x08000 /* can do IPv6/TCP checksums (Tx) */ #define IFCAP_CSUM_UDPv6_Rx 0x10000 /* can do IPv6/UDP checksums (Rx) */ #define IFCAP_CSUM_UDPv6_Tx 0x20000 /* can do IPv6/UDP checksums (Tx) */ #define IFCAP_TSOv6 0x40000 /* can do TCPv6 segmentation offload */
IPv4 header/TCPv4/UDPv4/TCPv6/UDPv6のchecksumとTCPv4/TCPv6のsegmentation offloadに関するフラグが存在する。
このフラグは
ifioctl_common()でチェックされ、ifp->csum_flags_tx・ifp->csum_flags_rxにフラグをセットしなおしている。
ifp->if_csum_flags_tx = 0; ifp->if_csum_flags_rx = 0; if (ifp->if_capenable & IFCAP_CSUM_IPv4_Tx) { ifp->if_csum_flags_tx |= M_CSUM_IPv4; } if (ifp->if_capenable & IFCAP_CSUM_IPv4_Rx) { ifp->if_csum_flags_rx |= M_CSUM_IPv4; } if (ifp->if_capenable & IFCAP_CSUM_TCPv4_Tx) { ifp->if_csum_flags_tx |= M_CSUM_TCPv4; } if (ifp->if_capenable & IFCAP_CSUM_TCPv4_Rx) { ifp->if_csum_flags_rx |= M_CSUM_TCPv4; } if (ifp->if_capenable & IFCAP_CSUM_UDPv4_Tx) { ifp->if_csum_flags_tx |= M_CSUM_UDPv4; } if (ifp->if_capenable & IFCAP_CSUM_UDPv4_Rx) { ifp->if_csum_flags_rx |= M_CSUM_UDPv4; } if (ifp->if_capenable & IFCAP_CSUM_TCPv6_Tx) { ifp->if_csum_flags_tx |= M_CSUM_TCPv6; } if (ifp->if_capenable & IFCAP_CSUM_TCPv6_Rx) { ifp->if_csum_flags_rx |= M_CSUM_TCPv6; } if (ifp->if_capenable & IFCAP_CSUM_UDPv6_Tx) { ifp->if_csum_flags_tx |= M_CSUM_UDPv6; } if (ifp->if_capenable & IFCAP_CSUM_UDPv6_Rx) { ifp->if_csum_flags_rx |= M_CSUM_UDPv6; }
でもなんでこんなに回りくどい事するんだろう・・・。
ip_input()ではIP Headerのchecksumを調べている:
switch (m->m_pkthdr.csum_flags & ((m->m_pkthdr.rcvif->if_csum_flags_rx & M_CSUM_IPv4) | M_CSUM_IPv4_BAD)) { case M_CSUM_IPv4|M_CSUM_IPv4_BAD: INET_CSUM_COUNTER_INCR(&ip_hwcsum_bad); goto badcsum; case M_CSUM_IPv4: /* Checksum was okay. */ INET_CSUM_COUNTER_INCR(&ip_hwcsum_ok); break; default: /* * Must compute it ourselves. Maybe skip checksum on * loopback interfaces. */ if (__predict_true(!(m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) || ip_do_loopback_cksum)) { INET_CSUM_COUNTER_INCR(&ip_swcsum); if (in_cksum(m, hlen) != 0) goto badcsum; } break; }
M_CSUM_IPv4フラグが立ってればM_CSUM_IPv4BADでチェックサムが正しいか調べ、立ってなければin_cksum()でチェックしている。
tcp_input()は無条件にtcp_input_checksum()を読んでいて、ここでチェックしている:
case AF_INET: switch (m->m_pkthdr.csum_flags & ((m->m_pkthdr.rcvif->if_csum_flags_rx & M_CSUM_TCPv4) | M_CSUM_TCP_UDP_BAD | M_CSUM_DATA)) { case M_CSUM_TCPv4|M_CSUM_TCP_UDP_BAD: TCP_CSUM_COUNTER_INCR(&tcp_hwcsum_bad); goto badcsum; case M_CSUM_TCPv4|M_CSUM_DATA: { u_int32_t hw_csum = m->m_pkthdr.csum_data; TCP_CSUM_COUNTER_INCR(&tcp_hwcsum_data); if (m->m_pkthdr.csum_flags & M_CSUM_NO_PSEUDOHDR) { const struct ip *ip = mtod(m, const struct ip *); hw_csum = in_cksum_phdr(ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(hw_csum + tlen + off + IPPROTO_TCP)); } if ((hw_csum ^ 0xffff) != 0) goto badcsum; break; } case M_CSUM_TCPv4: /* Checksum was okay. */ TCP_CSUM_COUNTER_INCR(&tcp_hwcsum_ok); break; default: /* * Must compute it ourselves. Maybe skip checksum * on loopback interfaces. */ if (__predict_true(!(m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) || tcp_do_loopback_cksum)) { TCP_CSUM_COUNTER_INCR(&tcp_swcsum); if (in4_cksum(m, IPPROTO_TCP, toff, tlen + off) != 0) goto badcsum; } break; } break;
使い方はIP Header checksumと変わらないが、M_CSUM_DATAというのが有って、何か面倒くさい事をしている。
ip_output()では、TCP/UDP checksumのdelayed checkをやろうとしているらしい:
/* * We can't defer the checksum of payload data if * we're about to encrypt/authenticate it. * * XXX When we support crypto offloading functions of * XXX network interfaces, we need to reconsider this, * XXX since it's likely that they'll support checksumming, * XXX as well. */ if (m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) { in_delayed_cksum(m); m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4); }
/* * We can't use HW checksumming if we're about to * to fragment the packet. * * XXX Some hardware can do this. */ if (m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) { if (IN_NEED_CHECKSUM(ifp, m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4))) { in_delayed_cksum(m); } m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4); }
追記:ここは勘違い。
コメントにあるように、ここはIPSEC周りのコードで、暗号化してしまうとchecksumをハードウェアで付ける事が出来ないからここで付けてしまっている。