npf_inet.c revision 1.2 1 /* $NetBSD: npf_inet.c,v 1.2 2010/09/16 04:53:27 rmind Exp $ */
2
3 /*-
4 * Copyright (c) 2009-2010 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This material is based upon work partially supported by The
8 * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * Various procotol related helper routines.
34 */
35
36 #ifdef _KERNEL
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: npf_inet.c,v 1.2 2010/09/16 04:53:27 rmind Exp $");
39
40 #include <sys/param.h>
41 #include <sys/kernel.h>
42
43 #include <netinet/in_systm.h>
44 #include <netinet/in.h>
45 #include <netinet/ip.h>
46 #include <netinet/tcp.h>
47 #include <netinet/udp.h>
48 #include <netinet/ip_icmp.h>
49
50 #include <net/if.h>
51 #include <net/ethertypes.h>
52 #include <net/if_ether.h>
53 #endif
54 #include <net/pfil.h>
55
56 #include "npf_impl.h"
57
58 /*
59 * npf_fixup{16,32}_cksum: update IPv4 checksum.
60 */
61
62 uint16_t
63 npf_fixup16_cksum(uint16_t cksum, uint16_t odatum, uint16_t ndatum)
64 {
65 uint32_t sum;
66
67 /*
68 * RFC 1624:
69 * HC' = ~(~HC + ~m + m')
70 */
71 sum = ~ntohs(cksum) & 0xffff;
72 sum += (~ntohs(odatum) & 0xffff) + ntohs(ndatum);
73 sum = (sum >> 16) + (sum & 0xffff);
74 sum += (sum >> 16);
75
76 return htons(~sum & 0xffff);
77 }
78
79 uint16_t
80 npf_fixup32_cksum(uint16_t cksum, uint32_t odatum, uint32_t ndatum)
81 {
82
83 cksum = npf_fixup16_cksum(cksum, odatum & 0xffff, ndatum & 0xffff);
84 cksum = npf_fixup16_cksum(cksum, odatum >> 16, ndatum >> 16);
85 return cksum;
86 }
87
88 /*
89 * npf_ip4_proto: check IPv4 header length and match protocol number.
90 *
91 * => Returns pointer to protocol header or NULL on failure.
92 * => Stores protocol number in the cache.
93 * => Updates nbuf pointer to header's nbuf.
94 */
95 bool
96 npf_ip4_proto(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
97 {
98 u_int hlen, offby;
99 uint8_t val8;
100 int error;
101
102 /* IPv4 header: check IP version and header length. */
103 error = nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &val8);
104 if (error || (val8 >> 4) != IPVERSION)
105 return false;
106 hlen = (val8 & 0xf) << 2;
107 if (hlen < sizeof(struct ip))
108 return false;
109 offby = offsetof(struct ip, ip_off);
110 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
111 return false;
112
113 /* IPv4 header: check fragment offset. */
114 error = nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &val8);
115 if (error || (val8 & ~htons(IP_DF | IP_RF)))
116 return false;
117
118 /* Get and match protocol. */
119 KASSERT(offsetof(struct ip, ip_p) > offby);
120 offby = offsetof(struct ip, ip_p) - offby;
121 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
122 return false;
123 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &val8))
124 return false;
125
126 /* IP checksum. */
127 offby = offsetof(struct ip, ip_sum) - offsetof(struct ip, ip_p);
128 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
129 return false;
130 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint16_t), &npc->npc_ipsum))
131 return false;
132
133 /* Cache: IPv4, protocol, header length. */
134 npc->npc_info |= NPC_IP46;
135 npc->npc_proto = val8;
136 npc->npc_hlen = hlen;
137 return true;
138 }
139
140 /*
141 * npf_fetch_ip4addrs: fetch source and destination address from IPv4 header.
142 *
143 * => Stores both source and destination addresses into the cache.
144 */
145 bool
146 npf_fetch_ip4addrs(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
147 {
148 u_int offby;
149
150 /* Source address. */
151 offby = offsetof(struct ip, ip_src);
152 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
153 return false;
154 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_addr_t), &npc->npc_srcip))
155 return false;
156
157 /* Destination address. */
158 offby = offsetof(struct ip, ip_dst) - offby;
159 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
160 return false;
161 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_addr_t), &npc->npc_dstip))
162 return false;
163
164 /* Both addresses are cached. */
165 npc->npc_info |= NPC_ADDRS;
166 return true;
167 }
168
169 /*
170 * npf_fetch_ports: fetch ports from either TCP or UDP header.
171 *
172 * => Stores both source and destination ports into the cache.
173 */
174 bool
175 npf_fetch_ports(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int proto)
176 {
177 u_int dst_off;
178
179 /* Perform checks, advance to TCP/UDP header. */
180 if (!npf_iscached(npc, NPC_IP46) && !npf_ip4_proto(npc, nbuf, n_ptr))
181 return false;
182 n_ptr = nbuf_advance(&nbuf, n_ptr, npc->npc_hlen);
183 if (n_ptr == NULL || npc->npc_proto != proto)
184 return false;
185
186 /*
187 * TCP/UDP header: fetch source and destination ports. For both
188 * protocols offset of the source port offset is 0.
189 */
190 CTASSERT(offsetof(struct tcphdr, th_sport) == 0);
191 CTASSERT(offsetof(struct udphdr, uh_sport) == 0);
192 if (proto == IPPROTO_TCP) {
193 dst_off = offsetof(struct tcphdr, th_dport);
194 } else {
195 KASSERT(proto == IPPROTO_UDP);
196 dst_off = offsetof(struct udphdr, uh_dport);
197 }
198
199 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_port_t), &npc->npc_sport))
200 return false;
201 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, dst_off)) == NULL)
202 return false;
203 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(in_port_t), &npc->npc_dport))
204 return false;
205
206 /* Both ports are cached. */
207 npc->npc_info |= NPC_PORTS;
208 return true;
209 }
210
211 /*
212 * npf_fetch_icmp: fetch ICMP code, type and possible query ID.
213 *
214 * => Stores both all fetched items into the cache.
215 */
216 bool
217 npf_fetch_icmp(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
218 {
219 u_int offby;
220 uint8_t type;
221
222 KASSERT(npf_iscached(npc, NPC_IP46));
223
224 /* ICMP type. */
225 offby = npc->npc_hlen;
226 CTASSERT(offsetof(struct icmp, icmp_type) == 0);
227 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
228 return false;
229 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &type))
230 return false;
231
232 /* ICMP code. */
233 offby = offsetof(struct icmp, icmp_code);
234 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
235 return false;
236 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &npc->npc_icmp_code))
237 return false;
238
239 /* Mark as cached. */
240 npc->npc_icmp_type = type;
241 npc->npc_info |= NPC_ICMP;
242 return true;
243 }
244
245 /*
246 * npf_fetch_tcpfl: fetch TCP flags and store into the cache.
247 */
248 bool
249 npf_fetch_tcpfl(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr)
250 {
251 u_int offby;
252
253 /* Get TCP flags. */
254 offby = npc->npc_hlen + offsetof(struct tcphdr, th_flags);
255 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
256 return false;
257 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint8_t), &npc->npc_tcp_flags))
258 return false;
259 return true;
260 }
261
262 /*
263 * npf_cache_all: general routine to cache all relevant IPv4 and
264 * TCP, UDP or ICMP data.
265 */
266 bool
267 npf_cache_all(npf_cache_t *npc, nbuf_t *nbuf)
268 {
269 void *n_ptr = nbuf_dataptr(nbuf);
270
271 /* IPv4: get protocol, source and destination addresses. */
272 if (!npf_iscached(npc, NPC_IP46) && !npf_ip4_proto(npc, nbuf, n_ptr)) {
273 return false;
274 }
275 if (!npf_iscached(npc, NPC_ADDRS) &&
276 !npf_fetch_ip4addrs(npc, nbuf, n_ptr)) {
277 return false;
278 }
279 switch (npc->npc_proto) {
280 case IPPROTO_TCP:
281 /* TCP flags. */
282 if (!npf_fetch_tcpfl(npc, nbuf, n_ptr)) {
283 return false;
284 }
285 /* FALLTHROUGH */
286
287 case IPPROTO_UDP:
288 /* Fetch TCP/UDP ports. */
289 return npf_fetch_ports(npc, nbuf, n_ptr, npc->npc_proto);
290
291 case IPPROTO_ICMP:
292 /* Fetch ICMP data. */
293 return npf_fetch_icmp(npc, nbuf, n_ptr);
294 }
295 return false;
296 }
297
298 /*
299 * npf_rwrport: rewrite required TCP/UDP port and update checksum.
300 */
301 bool
302 npf_rwrport(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int di,
303 in_port_t port, in_addr_t naddr)
304 {
305 const int proto = npc->npc_proto;
306 u_int offby, toff;
307 in_addr_t oaddr;
308 in_port_t oport;
309 uint16_t cksum;
310
311 KASSERT(npf_iscached(npc, NPC_PORTS));
312 KASSERT(proto == IPPROTO_TCP || proto == IPPROTO_UDP);
313
314 offby = npc->npc_hlen;
315
316 if (di == PFIL_OUT) {
317 /* Offset to the source port is zero. */
318 CTASSERT(offsetof(struct tcphdr, th_sport) == 0);
319 CTASSERT(offsetof(struct udphdr, uh_sport) == 0);
320 if (proto == IPPROTO_TCP) {
321 toff = offsetof(struct tcphdr, th_sum);
322 } else {
323 toff = offsetof(struct udphdr, uh_sum);
324 }
325 oaddr = npc->npc_srcip;
326 oport = npc->npc_sport;
327 } else {
328 /* Calculate offset to destination port and checksum. */
329 u_int poff;
330 if (proto == IPPROTO_TCP) {
331 poff = offsetof(struct tcphdr, th_dport);
332 toff = offsetof(struct tcphdr, th_sum) - poff;
333 } else {
334 poff = offsetof(struct udphdr, uh_dport);
335 toff = offsetof(struct udphdr, uh_sum) - poff;
336 }
337 oaddr = npc->npc_dstip;
338 oport = npc->npc_dport;
339 offby += poff;
340 }
341
342 /* Advance and rewrite port. */
343 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
344 return false;
345 if (nbuf_store_datum(nbuf, n_ptr, sizeof(in_port_t), &port))
346 return false;
347
348 /* Advance and update TCP/UDP checksum. */
349 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, toff)) == NULL)
350 return false;
351 if (nbuf_fetch_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum))
352 return false;
353 if (__predict_true(cksum || proto == IPPROTO_TCP)) {
354 cksum = npf_fixup32_cksum(cksum, oaddr, naddr);
355 cksum = npf_fixup16_cksum(cksum, oport, port);
356 if (nbuf_store_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum))
357 return false;
358 }
359 return true;
360 }
361
362 /*
363 * npf_rwrip: rewrite required IP address and update checksum.
364 */
365 bool
366 npf_rwrip(npf_cache_t *npc, nbuf_t *nbuf, void *n_ptr, const int di,
367 in_addr_t addr)
368 {
369 u_int offby;
370 in_addr_t oaddr;
371
372 KASSERT(npf_iscached(npc, NPC_IP46 | NPC_ADDRS));
373
374 /* Advance to the checksum in IP header and fetch it. */
375 offby = offsetof(struct ip, ip_sum);
376 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
377 return false;
378
379 if (di == PFIL_OUT) {
380 /* Rewrite source address, if outgoing. */
381 offby = offsetof(struct ip, ip_src) - offby;
382 oaddr = npc->npc_srcip;
383 } else {
384 /* Rewrite destination, if incoming. */
385 offby = offsetof(struct ip, ip_dst) - offby;
386 oaddr = npc->npc_dstip;
387 }
388
389 /* Write new IP checksum (it is acceptable to do this earlier). */
390 uint16_t cksum = npf_fixup32_cksum(npc->npc_ipsum, oaddr, addr);
391 if (nbuf_store_datum(nbuf, n_ptr, sizeof(uint16_t), &cksum))
392 return false;
393
394 /* Advance to address and rewrite it. */
395 if ((n_ptr = nbuf_advance(&nbuf, n_ptr, offby)) == NULL)
396 return false;
397 if (nbuf_store_datum(nbuf, n_ptr, sizeof(in_addr_t), &addr))
398 return false;
399
400 npc->npc_ipsum = cksum;
401 return true;
402 }
403