#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xennet_checksum.c,v 1.14 2020/05/04 08:22:45 jdolecek Exp $");
#ifdef _KERNEL_OPT
#include "opt_inet.h"
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_vlanvar.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_offload.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip6.h>
#include <netinet6/in6_offload.h>
#include <xen/xennet_checksum.h>
#ifdef XENNET_DEBUG
static const struct timeval xn_cksum_errintvl = { 600, 0 };
#endif
static void *
m_extract(struct mbuf *m, int off, int len)
{
if (m->m_len >= off + len)
return mtod(m, char *) + off;
else
return NULL;
}
int
xennet_checksum_fill(struct ifnet *ifp, struct mbuf *m,
struct evcnt *cksum_blank, struct evcnt *cksum_undefer)
{
const struct ether_header *eh;
struct ip *iph = NULL;
#ifdef INET6
struct ip6_hdr *ip6h = NULL;
#endif
int ehlen;
int iphlen;
int iplen;
uint16_t etype;
uint8_t nxt;
int error = 0;
int sw_csum;
KASSERT(!M_READONLY(m));
KASSERT((m->m_flags & M_PKTHDR) != 0);
eh = m_extract(m, 0, sizeof(*eh));
if (eh == NULL) {
return EINVAL;
}
etype = eh->ether_type;
ehlen = ETHER_HDR_LEN;
if (__predict_false(etype == htons(ETHERTYPE_VLAN))) {
struct ether_vlan_header *evl = m_extract(m, 0, sizeof(*evl));
if (evl == NULL) {
return EINVAL;
}
ehlen += ETHER_VLAN_ENCAP_LEN;
etype = ntohs(evl->evl_proto);
}
switch (etype) {
case htons(ETHERTYPE_IP):
iph = m_extract(m, ehlen, sizeof(*iph));
if (iph == NULL) {
return EINVAL;
}
nxt = iph->ip_p;
iphlen = iph->ip_hl << 2;
iplen = ntohs(iph->ip_len);
break;
#ifdef INET6
case htons(ETHERTYPE_IPV6):
ip6h = m_extract(m, ehlen, sizeof(*ip6h));
if (ip6h == NULL) {
return EINVAL;
}
if ((ip6h->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) {
return EINVAL;
}
nxt = ip6h->ip6_nxt;
iphlen = sizeof(*ip6h);
iplen = ntohs(ip6h->ip6_plen);
break;
#endif
default:
return EOPNOTSUPP;
}
if (ehlen + iplen > m->m_pkthdr.len) {
return EINVAL;
}
switch (nxt) {
case IPPROTO_UDP:
if (iph)
m->m_pkthdr.csum_flags = M_CSUM_UDPv4;
#ifdef INET6
else
m->m_pkthdr.csum_flags = M_CSUM_UDPv6;
#endif
m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum);
m->m_pkthdr.csum_data |= iphlen << 16;
break;
case IPPROTO_TCP:
if (iph)
m->m_pkthdr.csum_flags = M_CSUM_TCPv4;
#ifdef INET6
else
m->m_pkthdr.csum_flags = M_CSUM_TCPv6;
#endif
m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
m->m_pkthdr.csum_data |= iphlen << 16;
break;
case IPPROTO_ICMP:
case IPPROTO_IGMP:
case IPPROTO_HOPOPTS:
case IPPROTO_ICMPV6:
case IPPROTO_FRAGMENT:
error = 0;
goto out;
default:
{
#ifdef XENNET_DEBUG
static struct timeval lasttime;
if (ratecheck(&lasttime, &xn_cksum_errintvl))
printf("%s: unknown proto %d passed no checksum\n",
ifp->if_xname, nxt);
#endif
error = EOPNOTSUPP;
goto out;
}
}
sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_rx;
if (sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4)) {
in_undefer_cksum(m, ehlen,
sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4));
}
#ifdef INET6
if (sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) {
in6_undefer_cksum(m, ehlen,
sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6));
}
#endif
if (m->m_pkthdr.csum_flags != 0) {
if (sw_csum)
cksum_undefer->ev_count++;
cksum_blank->ev_count++;
#ifdef M_CSUM_BLANK
m->m_pkthdr.csum_flags |= M_CSUM_BLANK;
#endif
} else {
cksum_undefer->ev_count++;
}
out:
return error;
}