1 /* $NetBSD: xennet_checksum.c,v 1.14 2020/05/04 08:22:45 jdolecek Exp $ */
2
3 /*-
4 * Copyright (c)2006 YAMAMOTO Takashi,
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: xennet_checksum.c,v 1.14 2020/05/04 08:22:45 jdolecek Exp $");
31
32 #ifdef _KERNEL_OPT
33 #include "opt_inet.h"
34 #endif
35
36 #include <sys/types.h>
37 #include <sys/param.h>
38
39 #include <net/if.h>
40 #include <net/if_dl.h>
41 #include <net/if_ether.h>
42 #include <net/if_vlanvar.h>
43
44 #include <netinet/in.h>
45 #include <netinet/in_systm.h>
46 #include <netinet/in_offload.h>
47 #include <netinet/ip.h>
48 #include <netinet/tcp.h>
49 #include <netinet/udp.h>
50 #include <netinet/ip6.h>
51 #include <netinet6/in6_offload.h>
52
53 #include <xen/xennet_checksum.h>
54
55 #ifdef XENNET_DEBUG
56 /* ratecheck(9) for checksum validation failures */
57 static const struct timeval xn_cksum_errintvl = { 600, 0 }; /* 10 min, each */
58 #endif
59
60 static void *
m_extract(struct mbuf * m,int off,int len)61 m_extract(struct mbuf *m, int off, int len)
62 {
63 if (m->m_len >= off + len)
64 return mtod(m, char *) + off;
65 else
66 return NULL;
67 }
68
69 /*
70 * xennet_checksum_fill: fill TCP/UDP checksum, or arrange
71 * for hw offload to do it
72 */
73 int
xennet_checksum_fill(struct ifnet * ifp,struct mbuf * m,struct evcnt * cksum_blank,struct evcnt * cksum_undefer)74 xennet_checksum_fill(struct ifnet *ifp, struct mbuf *m,
75 struct evcnt *cksum_blank, struct evcnt *cksum_undefer)
76 {
77 const struct ether_header *eh;
78 struct ip *iph = NULL;
79 #ifdef INET6
80 struct ip6_hdr *ip6h = NULL;
81 #endif
82 int ehlen;
83 int iphlen;
84 int iplen;
85 uint16_t etype;
86 uint8_t nxt;
87 int error = 0;
88 int sw_csum;
89
90 KASSERT(!M_READONLY(m));
91 KASSERT((m->m_flags & M_PKTHDR) != 0);
92
93 eh = m_extract(m, 0, sizeof(*eh));
94 if (eh == NULL) {
95 /* Too short, packet will be dropped by upper layer */
96 return EINVAL;
97 }
98 etype = eh->ether_type;
99 ehlen = ETHER_HDR_LEN;
100 if (__predict_false(etype == htons(ETHERTYPE_VLAN))) {
101 struct ether_vlan_header *evl = m_extract(m, 0, sizeof(*evl));
102 if (evl == NULL) {
103 /* Too short, packet will be dropped by upper layer */
104 return EINVAL;
105 }
106 ehlen += ETHER_VLAN_ENCAP_LEN;
107 etype = ntohs(evl->evl_proto);
108 }
109
110 switch (etype) {
111 case htons(ETHERTYPE_IP):
112 iph = m_extract(m, ehlen, sizeof(*iph));
113 if (iph == NULL) {
114 /* Too short, packet will be dropped by upper layer */
115 return EINVAL;
116 }
117 nxt = iph->ip_p;
118 iphlen = iph->ip_hl << 2;
119 iplen = ntohs(iph->ip_len);
120 break;
121 #ifdef INET6
122 case htons(ETHERTYPE_IPV6):
123 ip6h = m_extract(m, ehlen, sizeof(*ip6h));
124 if (ip6h == NULL) {
125 /* Too short, packet will be dropped by upper layer */
126 return EINVAL;
127 }
128 if ((ip6h->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) {
129 /* Bad version */
130 return EINVAL;
131 }
132 nxt = ip6h->ip6_nxt;
133 iphlen = sizeof(*ip6h);
134 iplen = ntohs(ip6h->ip6_plen);
135 break;
136 #endif
137 default:
138 /* Not supported ethernet type */
139 return EOPNOTSUPP;
140 }
141
142 if (ehlen + iplen > m->m_pkthdr.len) {
143 /* Too short, packet will be dropped by upper layer */
144 return EINVAL;
145 }
146
147 switch (nxt) {
148 case IPPROTO_UDP:
149 if (iph)
150 m->m_pkthdr.csum_flags = M_CSUM_UDPv4;
151 #ifdef INET6
152 else
153 m->m_pkthdr.csum_flags = M_CSUM_UDPv6;
154 #endif
155 m->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum);
156 m->m_pkthdr.csum_data |= iphlen << 16;
157 break;
158 case IPPROTO_TCP:
159 if (iph)
160 m->m_pkthdr.csum_flags = M_CSUM_TCPv4;
161 #ifdef INET6
162 else
163 m->m_pkthdr.csum_flags = M_CSUM_TCPv6;
164 #endif
165 m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
166 m->m_pkthdr.csum_data |= iphlen << 16;
167 break;
168 case IPPROTO_ICMP:
169 case IPPROTO_IGMP:
170 case IPPROTO_HOPOPTS:
171 case IPPROTO_ICMPV6:
172 case IPPROTO_FRAGMENT:
173 /* nothing to do */
174 error = 0;
175 goto out;
176 /* NOTREACHED */
177 default:
178 {
179 #ifdef XENNET_DEBUG
180 static struct timeval lasttime;
181 if (ratecheck(&lasttime, &xn_cksum_errintvl))
182 printf("%s: unknown proto %d passed no checksum\n",
183 ifp->if_xname, nxt);
184 #endif /* XENNET_DEBUG */
185 error = EOPNOTSUPP;
186 goto out;
187 }
188 }
189
190 /*
191 * Only compute the checksum if impossible to defer.
192 */
193 sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_rx;
194
195 if (sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4)) {
196 in_undefer_cksum(m, ehlen,
197 sw_csum & (M_CSUM_UDPv4|M_CSUM_TCPv4));
198 }
199
200 #ifdef INET6
201 if (sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) {
202 in6_undefer_cksum(m, ehlen,
203 sw_csum & (M_CSUM_UDPv6|M_CSUM_TCPv6));
204 }
205 #endif
206
207 if (m->m_pkthdr.csum_flags != 0) {
208 if (sw_csum)
209 cksum_undefer->ev_count++;
210 cksum_blank->ev_count++;
211 #ifdef M_CSUM_BLANK
212 m->m_pkthdr.csum_flags |= M_CSUM_BLANK;
213 #endif
214 } else {
215 cksum_undefer->ev_count++;
216 }
217
218 out:
219 return error;
220 }
221