Home | History | Annotate | Line # | Download | only in npf
      1 /*-
      2  * Copyright (c) 2025 The NetBSD Foundation, Inc.
      3  * All rights reserved.
      4  *
      5  * This code is derived from software contributed to The NetBSD Foundation
      6  * by Emmanuel Nyarko.
      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 route extension.
     32  */
     33 
     34 #ifdef _KERNEL
     35 #include <sys/cdefs.h>
     36 __KERNEL_RCSID(0, "$NetBSD: npf_ext_route.c,v 1.18 2026/10/23 04:19:13 andvar Exp $");
     37 
     38 #include <sys/param.h>
     39 #include <sys/types.h>
     40 #include <sys/module.h>
     41 
     42 #include <sys/conf.h>
     43 #include <sys/kmem.h>
     44 #include <sys/mbuf.h>
     45 #include <sys/mutex.h>
     46 #include <sys/queue.h>
     47 #include <sys/syslog.h>
     48 #include <sys/proc.h>
     49 
     50 #include <net/if.h>
     51 #include <net/if_types.h>
     52 #include <net/bpf.h>
     53 #include <net/route.h>
     54 #include <netinet/in.h>
     55 #include <netinet/in_var.h>
     56 #include <netinet/ip.h>
     57 #include <netinet/ip_var.h>
     58 #include <netinet/in_offload.h>
     59 #include <netinet/ip6.h>
     60 #include <netinet6/ip6_var.h>
     61 #include <netinet6/scope6_var.h>
     62 #include <netinet6/in6_offload.h>
     63 #endif
     64 
     65 #include "npf_impl.h"
     66 
     67 NPF_EXT_MODULE(npf_ext_route, "");
     68 
     69 #define        NPFEXT_ROUTE_VER                1
     70 
     71 #define NPF_LOGADDR(buf, a) \
     72     snprintf(buf, sizeof(buf), "%u.%u.%u.%u", \
     73         ((a)->s_addr >> 24) & 0xFF, \
     74         ((a)->s_addr >> 16) & 0xFF, \
     75         ((a)->s_addr >> 8) & 0xFF, \
     76         (a)->s_addr & 0xFF)
     77 
     78 static void *          npf_ext_route_id;
     79 
     80 typedef struct {
     81     char ifname[IFNAMSIZ];
     82 	struct sockaddr_in v4_gateway;
     83 	struct sockaddr_in6 v6_gateway;
     84 	bool dup_to;
     85 } npf_ext_route_t;
     86 
     87 static int
     88 npf_route_ctor(npf_rproc_t *rp, const nvlist_t* params)
     89 {
     90 	npf_ext_route_t *meta;
     91 	const char *ifname;
     92 	const void *v4_gw, *v6_gw;
     93 	size_t alen;
     94 
     95 	ifname = dnvlist_get_string(params, "route-interface", NULL);
     96 	if (!ifname)
     97 		return EINVAL;
     98 
     99 	v4_gw = dnvlist_get_binary(params, "via", &alen, NULL, 0);
    100 	v6_gw = dnvlist_get_binary(params, "via6", &alen, NULL, 0);
    101 
    102 	meta = kmem_zalloc(sizeof(*meta), KM_SLEEP);
    103 	if (v4_gw) {
    104 		sockaddr_in_init(&meta->v4_gateway, v4_gw, 0);
    105 	}
    106 	if (v6_gw) {
    107 		sockaddr_in6_init(&meta->v6_gateway, v6_gw, 0, 0, 0);
    108 	}
    109 
    110 	strlcpy(meta->ifname, ifname, IFNAMSIZ);
    111 	meta->dup_to = dnvlist_get_bool(params, "dup-to", false);
    112 
    113 	npf_rproc_assign(rp, meta);
    114 	return 0;
    115 }
    116 
    117 static void
    118 npf_route_dtor(npf_rproc_t *rp, void *meta)
    119 {
    120 	kmem_free(meta, sizeof(npf_ext_route_t));
    121 }
    122 
    123 static void
    124 npf_chcksum(struct ifnet *ifp1, struct mbuf *m, struct ip *ip1, int *sw_csum)
    125 {
    126 	int hlen;
    127 	struct ip *ip = ip1;
    128 	struct ifnet *ifp = ifp1;
    129 	int csum;
    130 
    131 	/* NB: This code is copied from ip_output */
    132 	hlen = ip->ip_hl << 2;
    133 	ip->ip_sum = 0;
    134 	m->m_pkthdr.csum_data |= hlen << 16;
    135 
    136 	/* Maybe skip checksums on loopback interfaces. */
    137 	if (IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4)) {
    138 		m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
    139 	}
    140 
    141 	*sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
    142 	csum = *sw_csum;
    143 
    144 	if ((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) == 0) {
    145 		/*
    146 		 * Perform any checksums that the hardware can't do
    147 		 * for us.
    148 		 *
    149 		 * XXX Does any hardware require the {th,uh}_sum
    150 		 * XXX fields to be 0?
    151 		 */
    152 		if (csum & M_CSUM_IPv4) {
    153 			KASSERT(IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4));
    154 			ip->ip_sum = in_cksum(m, hlen);
    155 			m->m_pkthdr.csum_flags &= ~M_CSUM_IPv4;
    156 		}
    157 		if (csum & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
    158 
    159 			if (IN_NEED_CHECKSUM(ifp,
    160 				csum & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
    161 				in_undefer_cksum_tcpudp(m);
    162 			}
    163 			m->m_pkthdr.csum_flags &=
    164 			~(M_CSUM_TCPv4|M_CSUM_UDPv4);
    165 
    166 		}
    167 	}
    168 }
    169 
    170 static int
    171 npf_fragment(npf_t *npf, struct ifnet *ifp, struct ip *ip1,
    172     struct mbuf **m0, const struct sockaddr *edst, struct rtentry *rt)
    173 {
    174 	int error;
    175 	struct ip *ip = ip1;
    176 	struct mbuf *m = *m0;
    177 
    178 	/*
    179 	 * Too large for interface; fragment if possible.
    180 	 * Must be able to put at least 8 bytes per fragment.
    181 	 */
    182 	if (ntohs(ip->ip_off) & IP_DF) {
    183 		npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
    184 		icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0,
    185 			ifp->if_mtu);
    186 		return EINVAL;
    187 	}
    188 	/*
    189 	 * We can't use HW checksumming if we're about to fragment the packet.
    190 	 *
    191 	 * XXX Some hardware can do this.
    192 	 */
    193 	if (m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
    194 		if (IN_NEED_CHECKSUM(ifp,
    195 			m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
    196 			in_undefer_cksum_tcpudp(m);
    197 		}
    198 		m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4);
    199 	}
    200 
    201 	error = ip_fragment(m, ifp, ifp->if_mtu);
    202 	if (error) {
    203 		m = NULL;
    204 		return error;
    205 	}
    206 
    207 	for (; m; m = *m0) {
    208 		*m0 = m->m_nextpkt;
    209 		m->m_nextpkt = NULL;
    210 		if (error) {
    211 			m_freem(m);
    212 			continue;
    213 		}
    214 
    215 		KASSERT((m->m_pkthdr.csum_flags &
    216 			(M_CSUM_UDPv4 | M_CSUM_TCPv4)) == 0);
    217 		error = ip_if_output(ifp, m, edst, rt);
    218 	}
    219 
    220 	if (error == 0) {
    221 		npf_stats_inc(npf, NPF_STAT_FRAGMENTS);
    222 	}
    223 
    224 	return error;
    225 }
    226 
    227 /*
    228  * Ensure sending address is valid.
    229  * if the packet could be dropped without error (protocol dependent).
    230  */
    231 static int
    232 ip_ifaddrvalid(const struct in_ifaddr *ia)
    233 {
    234 
    235 	if (ia->ia_addr.sin_addr.s_addr == INADDR_ANY)
    236 		return 0;
    237 
    238 	if (ia->ia4_flags & IN_IFF_DUPLICATED)
    239 		return EADDRINUSE;
    240 	else if (ia->ia4_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED))
    241 		return EADDRNOTAVAIL;
    242 
    243 	return 0;
    244 }
    245 
    246 /*
    247  * search for the source address structure to
    248  * maintain output statistics, and verify address
    249  * validity
    250  */
    251 static int
    252 npf_validate_saddr(npf_t * npf, struct ip *ip, struct ifnet* ifp)
    253 {
    254 	int error = 0;
    255 	struct in_ifaddr *ia = NULL;
    256 	union {
    257 		struct sockaddr		sa;
    258 		struct sockaddr_in	sin;
    259 	} usrc;
    260 
    261 	struct psref psref_ia;
    262 
    263 	KASSERT(ia == NULL);
    264 	sockaddr_in_init(&usrc.sin, &ip->ip_src, 0);
    265 	ia = ifatoia(ifaof_ifpforaddr_psref(&usrc.sa, ifp, &psref_ia));
    266 
    267 	/*
    268 	 * Ensure we only send from a valid address.
    269 	 * A NULL address is valid because the packet could be
    270 	 * generated from a packet filter.
    271 	 */
    272 	if (ia != NULL &&
    273 	    (error = ip_ifaddrvalid(ia)) != 0)
    274 	{
    275 		char buf[32];
    276 		NPF_LOGADDR(buf, &ip->ip_src);
    277 		log(LOG_ERR,
    278 		    "refusing to send from invalid address %s (pid %d)\n",
    279 		    buf, curproc->p_pid);
    280 
    281 		npf_stats_inc(npf, NPF_STAT_NOREROUTE);
    282 	}
    283 	ia4_release(ia, &psref_ia);
    284 	return error;
    285 }
    286 
    287 #if defined(INET6)
    288 /* this code is copied from ip6_output*/
    289 static void
    290 npf_validate_s6addr(struct mbuf *m, struct ifnet *ifp1, int *sw_csum)
    291 {
    292 	struct in6_ifaddr *ia6;
    293 	struct ifnet *ifp = ifp1;
    294 	struct ip6_hdr *ip6;
    295 	int csum;
    296 	int s;
    297 
    298 	ip6 = mtod(m, struct ip6_hdr *);
    299 	s = pserialize_read_enter();
    300 	ia6 = in6_ifawithifp(ifp, &ip6->ip6_src);
    301 	if (ia6) {
    302 		/* Record statistics for this interface address. */
    303 		ia6->ia_ifa.ifa_data.ifad_outbytes += m->m_pkthdr.len;
    304 	}
    305 	pserialize_read_exit(s);
    306 
    307 	/* check sum */
    308 	*sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
    309 	csum = *sw_csum;
    310 
    311 	if ((csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) != 0) {
    312 		if (IN6_NEED_CHECKSUM(ifp,
    313 			csum & (M_CSUM_UDPv6|M_CSUM_TCPv6))) {
    314 			in6_undefer_cksum_tcpudp(m);
    315 		}
    316 		m->m_pkthdr.csum_flags &= ~(M_CSUM_UDPv6|M_CSUM_TCPv6);
    317 	}
    318 }
    319 #endif
    320 
    321 /* main routing function for kernel module */
    322 static bool
    323 npf_route(npf_cache_t *npc, void *meta, const npf_match_info_t __unused *mi,
    324     int *decision)
    325 {
    326 	struct mbuf *m = nbuf_head_mbuf(npc->npc_nbuf);
    327 	struct mbuf *m0 = NULL;
    328 	const npf_ext_route_t *route = meta;
    329 	npf_t *npf = npf_getkernctx();
    330 	struct ifnet *ifp;
    331 	int error, sw_csum;
    332 	bool consumed = false, dup_to = route->dup_to;
    333 	bool rv;
    334 	int stats;
    335 	const struct sockaddr *edst;
    336 	union {
    337 		struct sockaddr_in v4;
    338 		struct sockaddr_in6 v6;
    339 	} dst;
    340 
    341 	stats = NPF_STAT_REROUTE;
    342 
    343 	/* Skip, if already blocking.
    344 	 * also when routing is applied to a stateful rule, incoming packets
    345 	 * are routed since the procedure becomes attached to the connection
    346 	 * and hence will not be desirable
    347 	 */
    348 	if (*decision == NPF_DECISION_BLOCK || mi->mi_di == PFIL_IN) {
    349 		return true;
    350 	}
    351 
    352 	/* global lock for interface lookup */
    353 	KERNEL_LOCK(1, NULL);
    354 	ifp = ifunit(route->ifname);
    355 	if (ifp == NULL) {
    356 		/* XXX: oops */
    357 		goto bad;
    358 	}
    359 
    360 	/* duplicate(if using dup-to) packet and send our version */
    361 	if (dup_to) {
    362 		if ((m0 = m_dup(m, 0, M_COPYALL, M_NOWAIT)) == NULL) {
    363 			goto bad;
    364 		}
    365 	} else {
    366 		m0 = m;
    367 	}
    368 
    369 	if (npf_iscached(npc, NPC_IP6)) {
    370 #if defined(INET6)
    371 		struct ip6_hdr *ip6 = npc->npc_ip.v6;
    372 		dst.v6 = route->v6_gateway;
    373 
    374 		/* if no gateway is specified or this is mutlicast, use dst addr */
    375 		if (IN6_IS_ADDR_UNSPECIFIED(&dst.v6.sin6_addr) || IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst))
    376 			sockaddr_in6_init(&dst.v6, &ip6->ip6_dst, 0, 0, 0);
    377 
    378 		if (IN6_IS_SCOPE_EMBEDDABLE(&dst.v6.sin6_addr)) {
    379 			error = in6_setscope(&dst.v6.sin6_addr, ifp, NULL);
    380 			if (error) {
    381 				goto bad;
    382 			}
    383 		}
    384 
    385 		npf_validate_s6addr(m0, ifp, &sw_csum);
    386 
    387 		if (m->m_pkthdr.len > ifp->if_mtu) {
    388 			/* router not allowed to fragment */
    389 			npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
    390 			goto bad;
    391 		}
    392 
    393 		if (__predict_false(sw_csum & M_CSUM_TSOv6)) {
    394 			/*
    395 			 * TSO6 is required by a packet, but disabled for
    396 			 * the interface.
    397 			*/
    398 			error = ip6_tso_output(ifp, ifp, m0, &dst.v6, NULL);
    399 		} else
    400 			error = ip6_if_output(ifp, ifp, m0, &dst.v6, NULL);
    401 		consumed = true;
    402 		if (!error) {
    403 			goto done;
    404 		}
    405 #endif
    406 	} else if (npf_iscached(npc, NPC_IP4)) {
    407 		struct ip *ip = npc->npc_ip.v4;
    408 		sockaddr_in_init(&dst.v4, &ip->ip_dst, 0);
    409 
    410 		edst = sintocsa(&route->v4_gateway);
    411 
    412 		/* if no gateway is specified or if this is multicast, use dst address */
    413 		if (edst == NULL || m0->m_flags & M_MCAST)
    414 			edst = sintocsa(&dst.v4);
    415 
    416 		/*
    417 		 * NB: This code is copied from ip_output and re-arranged
    418 		 * checks fragmentation, checksum and source address validity
    419 		 */
    420 
    421 		error = npf_validate_saddr(npf, ip, ifp);
    422 		if (error)
    423 			goto bad;
    424 
    425 		if (ntohs(ip->ip_len) > ifp->if_mtu)
    426 			goto fragment;
    427 
    428 		npf_chcksum(ifp, m0, ip, &sw_csum);
    429 
    430 		/* Send it */
    431 		if (__predict_false(sw_csum & M_CSUM_TSOv4)) {
    432 			/*
    433 			 * TSO4 is required by a packet, but disabled for
    434 			 * the interface.
    435 			 */
    436 			error = ip_tso_output(ifp, m0, edst, NULL);
    437 		} else
    438 			error = ip_if_output(ifp, m0, edst, NULL);
    439 		consumed = true;
    440 		if (error) {
    441 			goto bad;
    442 		}
    443 		goto done;
    444 
    445 fragment:
    446 		error = npf_fragment(npf, ifp, ip, &m0, edst, NULL);
    447 		consumed = true;
    448 		if (!error) {
    449 			goto done;
    450 		}
    451 	}
    452 
    453 /*
    454  * for routing procedures, when we dup-to, we need original packet to
    455  * continue its normal routing and copied packet go through our route.
    456  * if we don't dup-to, we need processing of the moving mbuf stopped either in a failure or success path
    457  * since we now care about the copied which is to be sent.
    458  * just return false and handle the freeing of the moving mbuf,
    459  * in a dup-to, where both packets leave successfully, we don't free any of them.
    460  */
    461 bad:
    462 	if (!consumed)
    463 		m_freem(m0);
    464 	stats = NPF_STAT_NOREROUTE;
    465 done:
    466 	rv = true;
    467 	npf_stats_inc(npf, stats);
    468 
    469 	if (!dup_to) {
    470 		rv = false;
    471 		m = NULL;
    472 	}
    473 
    474 	KERNEL_UNLOCK_ONE(NULL);
    475 	return rv;
    476 }
    477 
    478 __dso_public int
    479 npf_ext_route_init(npf_t *npf)
    480 {
    481 	static const npf_ext_ops_t npf_route_ops = {
    482 		.version        = NPFEXT_ROUTE_VER,
    483 		.ctx            = NULL,
    484 		.ctor           = npf_route_ctor,
    485 		.dtor           = npf_route_dtor,
    486 		.proc           = npf_route
    487 	};
    488 	npf_ext_route_id = npf_ext_register(npf, "route", &npf_route_ops);
    489 	return npf_ext_route_id ? 0 : EEXIST;
    490 }
    491 
    492 __dso_public int
    493 npf_ext_route_fini(npf_t *npf)
    494 {
    495 	return npf_ext_unregister(npf, npf_ext_route_id);
    496 }
    497 
    498 #ifdef _KERNEL
    499 static int
    500 npf_ext_route_modcmd(modcmd_t cmd, void *arg)
    501 {
    502 	npf_t *npf = npf_getkernctx();
    503 
    504 	switch (cmd) {
    505 	case MODULE_CMD_INIT:
    506 		return npf_ext_route_init(npf);
    507 	case MODULE_CMD_FINI:
    508 		return npf_ext_route_fini(npf);
    509 	case MODULE_CMD_AUTOUNLOAD:
    510 		/* Allow auto-unload only if NPF permits it. */
    511 		return npf_autounload_p() ? 0 : EBUSY;
    512 	default:
    513 		return ENOTTY;
    514 	}
    515 	return 0;
    516 }
    517 #endif
    518