xref: /netbsd-src/sys/net/npf/npf_alg_icmp.c (revision b899bfd96fd2cbaf2befc9ce4aaed9b9c230837a)
1 /*-
2  * Copyright (c) 2010 The NetBSD Foundation, Inc.
3  * All rights reserved.
4  *
5  * This material is based upon work partially supported by The
6  * NetBSD Foundation under a contract with Mindaugas Rasiukevicius.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
21  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /*
31  * NPF ALG for ICMP and traceroute translations.
32  */
33 
34 #ifdef _KERNEL
35 #include <sys/cdefs.h>
36 __KERNEL_RCSID(0, "$NetBSD: npf_alg_icmp.c,v 1.33 2020/05/30 14:16:56 rmind Exp $");
37 
38 #include <sys/param.h>
39 #include <sys/module.h>
40 
41 #include <netinet/in_systm.h>
42 #include <netinet/in.h>
43 #include <netinet/ip.h>
44 #include <netinet/tcp.h>
45 #include <netinet/udp.h>
46 #include <netinet/ip_icmp.h>
47 #include <netinet/icmp6.h>
48 #include <net/pfil.h>
49 #endif
50 
51 #include "npf_impl.h"
52 #include "npf_conn.h"
53 
54 MODULE(MODULE_CLASS_MISC, npf_alg_icmp, "npf");
55 
56 /*
57  * Traceroute criteria.
58  *
59  * IANA assigned base port: 33434.  However, common practice is to increase
60  * the port, thus monitor [33434-33484] range.  Additional filter is low TTL.
61  */
62 
63 #define	TR_BASE_PORT	33434
64 #define	TR_PORT_RANGE	33484
65 #define	TR_MAX_TTL	48
66 
67 static npf_alg_t *	alg_icmp	__read_mostly;
68 
69 /*
70  * npfa_icmp_match: matching inspector determines ALG case and associates
71  * our ALG with the NAT entry.
72  */
73 static bool
npfa_icmp_match(npf_cache_t * npc,npf_nat_t * nt,int di)74 npfa_icmp_match(npf_cache_t *npc, npf_nat_t *nt, int di)
75 {
76 	const int proto = npc->npc_proto;
77 	const struct ip *ip = npc->npc_ip.v4;
78 	in_port_t dport;
79 
80 	KASSERT(npf_iscached(npc, NPC_IP46));
81 	KASSERT(npf_iscached(npc, NPC_LAYER4));
82 
83 	/* Check for low TTL.  Also, we support outbound NAT only. */
84 	if (ip->ip_ttl > TR_MAX_TTL || di != PFIL_OUT) {
85 		return false;
86 	}
87 
88 	switch (proto) {
89 	case IPPROTO_TCP: {
90 		const struct tcphdr *th = npc->npc_l4.tcp;
91 		dport = ntohs(th->th_dport);
92 		break;
93 	}
94 	case IPPROTO_UDP: {
95 		const struct udphdr *uh = npc->npc_l4.udp;
96 		dport = ntohs(uh->uh_dport);
97 		break;
98 	}
99 	case IPPROTO_ICMP:
100 	case IPPROTO_ICMPV6:
101 		/* Just to pass the test below. */
102 		dport = TR_BASE_PORT;
103 		break;
104 	default:
105 		return false;
106 	}
107 
108 	/* Handle TCP/UDP traceroute - check for port range. */
109 	if (dport < TR_BASE_PORT || dport > TR_PORT_RANGE) {
110 		return false;
111 	}
112 
113 	/* Associate ALG with translation entry. */
114 	npf_nat_setalg(nt, alg_icmp, 0);
115 	return true;
116 }
117 
118 /*
119  * npfa_icmp{4,6}_inspect: retrieve unique identifiers - either ICMP query
120  * ID or TCP/UDP ports of the original packet, which is embedded.
121  *
122  * => Sets hasqid=true if the packet has a Query Id. In this case neither
123  *    the nbuf nor npc is touched.
124  */
125 
126 static bool
npfa_icmp4_inspect(const int type,npf_cache_t * npc,bool * hasqid)127 npfa_icmp4_inspect(const int type, npf_cache_t *npc, bool *hasqid)
128 {
129 	nbuf_t *nbuf = npc->npc_nbuf;
130 
131 	/* Per RFC 792. */
132 	switch (type) {
133 	case ICMP_UNREACH:
134 	case ICMP_SOURCEQUENCH:
135 	case ICMP_REDIRECT:
136 	case ICMP_TIMXCEED:
137 	case ICMP_PARAMPROB:
138 		/* Should contain original IP header. */
139 		if (!nbuf_advance(nbuf, offsetof(struct icmp, icmp_ip), 0)) {
140 			return false;
141 		}
142 		return (npf_cache_all(npc) & NPC_LAYER4) != 0;
143 
144 	case ICMP_ECHOREPLY:
145 	case ICMP_ECHO:
146 	case ICMP_TSTAMP:
147 	case ICMP_TSTAMPREPLY:
148 	case ICMP_IREQ:
149 	case ICMP_IREQREPLY:
150 		/* Contains ICMP query ID. */
151 		*hasqid = true;
152 		return true;
153 	default:
154 		break;
155 	}
156 	return false;
157 }
158 
159 static bool
npfa_icmp6_inspect(const int type,npf_cache_t * npc,bool * hasqid)160 npfa_icmp6_inspect(const int type, npf_cache_t *npc, bool *hasqid)
161 {
162 	nbuf_t *nbuf = npc->npc_nbuf;
163 
164 	/* Per RFC 4443. */
165 	switch (type) {
166 	case ICMP6_DST_UNREACH:
167 	case ICMP6_PACKET_TOO_BIG:
168 	case ICMP6_TIME_EXCEEDED:
169 	case ICMP6_PARAM_PROB:
170 		/* Should contain original IP header. */
171 		if (!nbuf_advance(nbuf, sizeof(struct icmp6_hdr), 0)) {
172 			return false;
173 		}
174 		return (npf_cache_all(npc) & NPC_LAYER4) != 0;
175 
176 	case ICMP6_ECHO_REQUEST:
177 	case ICMP6_ECHO_REPLY:
178 		/* Contains ICMP query ID. */
179 		*hasqid = true;
180 		return true;
181 	default:
182 		break;
183 	}
184 	return false;
185 }
186 
187 /*
188  * npfa_icmp_inspect: ALG ICMP inspector.
189  *
190  * => Returns false if there is a problem with the format.
191  */
192 static bool
npfa_icmp_inspect(npf_cache_t * npc,npf_cache_t * enpc)193 npfa_icmp_inspect(npf_cache_t *npc, npf_cache_t *enpc)
194 {
195 	nbuf_t *nbuf = npc->npc_nbuf;
196 	bool ret, hasqid = false;
197 
198 	KASSERT(npf_iscached(npc, NPC_IP46));
199 	KASSERT(npf_iscached(npc, NPC_ICMP));
200 
201 	/* Advance to ICMP header. */
202 	nbuf_reset(nbuf);
203 	if (!nbuf_advance(nbuf, npc->npc_hlen, 0)) {
204 		return false;
205 	}
206 	memset(enpc, 0, sizeof(npf_cache_t));
207 	enpc->npc_ctx = npc->npc_ctx;
208 	enpc->npc_nbuf = nbuf;
209 
210 	/*
211 	 * Inspect the ICMP packet.  The relevant data might be in the
212 	 * embedded packet.  Fill the "enpc" cache, if so.
213 	 */
214 	if (npf_iscached(npc, NPC_IP4) &&
215 	    npc->npc_proto == IPPROTO_ICMP) {
216 		const struct icmp *ic = npc->npc_l4.icmp;
217 		ret = npfa_icmp4_inspect(ic->icmp_type, enpc, &hasqid);
218 	} else if (npf_iscached(npc, NPC_IP6) &&
219 	    npc->npc_proto == IPPROTO_ICMPV6) {
220 		const struct icmp6_hdr *ic6 = npc->npc_l4.icmp6;
221 		ret = npfa_icmp6_inspect(ic6->icmp6_type, enpc, &hasqid);
222 	} else {
223 		ret = false;
224 	}
225 	if (!ret) {
226 		return false;
227 	}
228 
229 	/* ICMP ID is the original packet, just indicate it. */
230 	if (hasqid) {
231 		npc->npc_info |= NPC_ICMP_ID;
232 	}
233 
234 	return true;
235 }
236 
237 static npf_conn_t *
npfa_icmp_conn(npf_cache_t * npc,int di)238 npfa_icmp_conn(npf_cache_t *npc, int di)
239 {
240 	npf_conn_t *conn = NULL;
241 	npf_cache_t enpc;
242 	bool hasqid = false;
243 
244 	/* Inspect ICMP packet for an embedded packet. */
245 	if (!npf_iscached(npc, NPC_ICMP))
246 		return NULL;
247 	if (!npfa_icmp_inspect(npc, &enpc))
248 		goto out;
249 
250 	/*
251 	 * If the ICMP packet had a Query Id, leave now. The packet didn't get
252 	 * modified, so no need to recache npc.
253 	 */
254 	if (npf_iscached(npc, NPC_ICMP_ID)) {
255 		KASSERT(!nbuf_flag_p(npc->npc_nbuf, NBUF_DATAREF_RESET));
256 		return NULL;
257 	}
258 
259 	/*
260 	 * Invert the identifiers of the embedded packet.
261 	 * If it is ICMP, then ensure ICMP ID.
262 	 */
263 	union l4 {
264 		struct tcphdr th;
265 		struct udphdr uh;
266 	} l4;
267 	npf_flow_t flow;
268 	bool ret;
269 
270 	#define	SWAP(type, x, y) { type tmp = x; x = y; y = tmp; }
271 	SWAP(npf_addr_t *, enpc.npc_ips[NPF_SRC], enpc.npc_ips[NPF_DST]);
272 
273 	switch (enpc.npc_proto) {
274 	case IPPROTO_TCP:
275 		l4.th.th_sport = enpc.npc_l4.tcp->th_dport;
276 		l4.th.th_dport = enpc.npc_l4.tcp->th_sport;
277 		enpc.npc_l4.tcp = &l4.th;
278 		break;
279 	case IPPROTO_UDP:
280 		l4.uh.uh_sport = enpc.npc_l4.udp->uh_dport;
281 		l4.uh.uh_dport = enpc.npc_l4.udp->uh_sport;
282 		enpc.npc_l4.udp = &l4.uh;
283 		break;
284 	case IPPROTO_ICMP: {
285 		const struct icmp *ic = enpc.npc_l4.icmp;
286 		ret = npfa_icmp4_inspect(ic->icmp_type, &enpc, &hasqid);
287 		if (!ret || !hasqid)
288 			goto out;
289 		enpc.npc_info |= NPC_ICMP_ID;
290 		break;
291 	}
292 	case IPPROTO_ICMPV6: {
293 		const struct icmp6_hdr *ic6 = enpc.npc_l4.icmp6;
294 		ret = npfa_icmp6_inspect(ic6->icmp6_type, &enpc, &hasqid);
295 		if (!ret || !hasqid)
296 			goto out;
297 		enpc.npc_info |= NPC_ICMP_ID;
298 		break;
299 	}
300 	default:
301 		goto out;
302 	}
303 
304 	/* Lookup a connection using the embedded packet. */
305 	conn = npf_conn_lookup(&enpc, di, &flow);
306 out:
307 	/*
308 	 * Recache npc. The nbuf may have been updated as a result of
309 	 * caching enpc.
310 	 */
311 	npf_recache(npc);
312 	return conn;
313 }
314 
315 /*
316  * npfa_icmp_nat: ALG translator - rewrites IP address in the IP header
317  * which is embedded in ICMP packet.  Note: backwards stream only.
318  */
319 static bool
npfa_icmp_nat(npf_cache_t * npc,npf_nat_t * nt,npf_flow_t flow)320 npfa_icmp_nat(npf_cache_t *npc, npf_nat_t *nt, npf_flow_t flow)
321 {
322 	const unsigned which = NPF_SRC;
323 	npf_cache_t enpc;
324 	struct icmp *ic;
325 	uint16_t cksum;
326 
327 	if (flow == NPF_FLOW_FORW || !npf_iscached(npc, NPC_ICMP))
328 		return false;
329 
330 	/*
331 	 * ICMP: fetch the current checksum we are going to fixup.
332 	 */
333 	ic = npc->npc_l4.icmp;
334 	cksum = ic->icmp_cksum;
335 
336 	if (!npfa_icmp_inspect(npc, &enpc))
337 		goto err;
338 
339 	/*
340 	 * If the ICMP packet had a Query Id, leave now. The packet didn't get
341 	 * modified, so no need to recache npc.
342 	 */
343 	if (npf_iscached(npc, NPC_ICMP_ID)) {
344 		KASSERT(!nbuf_flag_p(npc->npc_nbuf, NBUF_DATAREF_RESET));
345 		return false;
346 	}
347 
348 	KASSERT(npf_iscached(&enpc, NPC_IP46));
349 	KASSERT(npf_iscached(&enpc, NPC_LAYER4));
350 
351 	CTASSERT(offsetof(struct icmp, icmp_cksum) ==
352 	    offsetof(struct icmp6_hdr, icmp6_cksum));
353 
354 	/*
355 	 * Fetch the IP and port in the _embedded_ packet.  Also, fetch
356 	 * the IPv4 and TCP/UDP checksums before they are rewritten.
357 	 */
358 	const int proto = enpc.npc_proto;
359 	uint16_t ipcksum = 0, l4cksum = 0;
360 	in_port_t old_port = 0;
361 
362 	if (npf_iscached(&enpc, NPC_IP4)) {
363 		const struct ip *eip = enpc.npc_ip.v4;
364 		ipcksum = eip->ip_sum;
365 	}
366 	switch (proto) {
367 	case IPPROTO_TCP: {
368 		const struct tcphdr *th = enpc.npc_l4.tcp;
369 		old_port = th->th_sport;
370 		l4cksum = th->th_sum;
371 		break;
372 	}
373 	case IPPROTO_UDP: {
374 		const struct udphdr *uh = enpc.npc_l4.udp;
375 		old_port = uh->uh_sport;
376 		l4cksum = uh->uh_sum;
377 		break;
378 	}
379 	case IPPROTO_ICMP:
380 	case IPPROTO_ICMPV6:
381 		break;
382 	default:
383 		goto err;
384 	}
385 
386 	/*
387 	 * Get the original IP address and port.
388 	 * Calculate the part of the ICMP checksum fixup.
389 	 */
390 	npf_addr_t *addr;
391 	in_port_t port;
392 
393 	npf_nat_getorig(nt, &addr, &port);
394 
395 	cksum = npf_addr_cksum(cksum, enpc.npc_alen, enpc.npc_ips[which], addr);
396 	if (port) {
397 		cksum = npf_fixup16_cksum(cksum, old_port, port);
398 	}
399 
400 	/*
401 	 * Translate the embedded packet.  The following changes will
402 	 * be performed by npf_napt_rwr():
403 	 *
404 	 *	1) Rewrite the IP address and, if not ICMP, port.
405 	 *	2) Rewrite the TCP/UDP checksum (if not ICMP).
406 	 *	3) Rewrite the IPv4 checksum for (1) and (2).
407 	 *
408 	 * XXX: Assumes NPF_NATOUT (source address/port).  Currently,
409 	 * npfa_icmp_match() matches only for the PFIL_OUT traffic.
410 	 */
411 	if (npf_napt_rwr(&enpc, which, addr, port)) {
412 		goto err;
413 	}
414 
415 	/*
416 	 * Finally, finish the ICMP checksum fixup: include the checksum
417 	 * changes in the embedded packet.
418 	 */
419 	if (npf_iscached(&enpc, NPC_IP4)) {
420 		const struct ip *eip = enpc.npc_ip.v4;
421 		cksum = npf_fixup16_cksum(cksum, ipcksum, eip->ip_sum);
422 	}
423 	switch (proto) {
424 	case IPPROTO_TCP: {
425 		const struct tcphdr *th = enpc.npc_l4.tcp;
426 		cksum = npf_fixup16_cksum(cksum, l4cksum, th->th_sum);
427 		break;
428 	}
429 	case IPPROTO_UDP:
430 		if (l4cksum) {
431 			const struct udphdr *uh = enpc.npc_l4.udp;
432 			cksum = npf_fixup16_cksum(cksum, l4cksum, uh->uh_sum);
433 		}
434 		break;
435 	}
436 	npf_recache(npc);
437 	KASSERT(npf_iscached(npc, NPC_ICMP));
438 	ic = npc->npc_l4.icmp;
439 	ic->icmp_cksum = cksum;
440 	return true;
441 
442 err:
443 	/*
444 	 * Recache npc. The nbuf may have been updated as a result of
445 	 * caching enpc.
446 	 */
447 	npf_recache(npc);
448 	return false;
449 }
450 
451 /*
452  * npf_alg_icmp_{init,fini,modcmd}: ICMP ALG initialization, destruction
453  * and module interface.
454  */
455 
456 __dso_public int
npf_alg_icmp_init(npf_t * npf)457 npf_alg_icmp_init(npf_t *npf)
458 {
459 	static const npfa_funcs_t icmp = {
460 		.match		= npfa_icmp_match,
461 		.translate	= npfa_icmp_nat,
462 		.inspect	= npfa_icmp_conn,
463 		.destroy	= NULL,
464 	};
465 	alg_icmp = npf_alg_register(npf, "icmp", &icmp);
466 	return alg_icmp ? 0 : ENOMEM;
467 }
468 
469 __dso_public int
npf_alg_icmp_fini(npf_t * npf)470 npf_alg_icmp_fini(npf_t *npf)
471 {
472 	KASSERT(alg_icmp != NULL);
473 	return npf_alg_unregister(npf, alg_icmp);
474 }
475 
476 #ifdef _KERNEL
477 static int
npf_alg_icmp_modcmd(modcmd_t cmd,void * arg)478 npf_alg_icmp_modcmd(modcmd_t cmd, void *arg)
479 {
480 	npf_t *npf = npf_getkernctx();
481 
482 	switch (cmd) {
483 	case MODULE_CMD_INIT:
484 		return npf_alg_icmp_init(npf);
485 	case MODULE_CMD_FINI:
486 		return npf_alg_icmp_fini(npf);
487 	case MODULE_CMD_AUTOUNLOAD:
488 		return EBUSY;
489 	default:
490 		return ENOTTY;
491 	}
492 	return 0;
493 }
494 #endif
495