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をハードウェアで付ける事が出来ないからここで付けてしまっている。