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.1 2026/04/08 00:33:07 joe 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 } npf_ext_route_t;
     83 
     84 static int
     85 npf_route_ctor(npf_rproc_t *rp, const nvlist_t* params)
     86 {
     87 	npf_ext_route_t *meta;
     88 	const char *ifname;
     89 
     90 	meta = kmem_zalloc(sizeof(*meta), KM_SLEEP);
     91 	ifname = nvlist_get_string(params, "route-interface");
     92 
     93 	if (!ifname)
     94 		return EINVAL;
     95 
     96 	/* XXX use something like npf_ifmap */
     97 	strlcpy(meta->ifname, ifname, IFNAMSIZ);
     98 	npf_rproc_assign(rp, meta);
     99 	return 0;
    100 }
    101 
    102 static void
    103 npf_route_dtor(npf_rproc_t *rp, void *meta)
    104 {
    105 	kmem_free(meta, sizeof(npf_ext_route_t));
    106 }
    107 
    108 static void
    109 npf_chcksum(struct ifnet *ifp1, struct mbuf *m0, struct ip *ip1, int *sw_csum)
    110 {
    111 	int hlen;
    112 	struct mbuf *m = m0;
    113 	struct ip *ip = ip1;
    114 	struct ifnet *ifp = ifp1;
    115 	int csum;
    116 
    117 	/* NB: This code is copied from ip_output */
    118 	hlen = ip->ip_hl << 2;
    119 	ip->ip_sum = 0;
    120 	m->m_pkthdr.csum_data |= hlen << 16;
    121 
    122 	/* Maybe skip checksums on loopback interfaces. */
    123 	if (IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4)) {
    124 		m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
    125 	}
    126 
    127 	*sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
    128 	csum = *sw_csum;
    129 
    130 	if ((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) == 0) {
    131 		/*
    132 		 * Perform any checksums that the hardware can't do
    133 		 * for us.
    134 		 *
    135 		 * XXX Does any hardware require the {th,uh}_sum
    136 		 * XXX fields to be 0?
    137 		 */
    138 		if (csum & M_CSUM_IPv4) {
    139 			KASSERT(IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4));
    140 			ip->ip_sum = in_cksum(m, hlen);
    141 			m->m_pkthdr.csum_flags &= ~M_CSUM_IPv4;
    142 		}
    143 		if (csum & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
    144 
    145 			if (IN_NEED_CHECKSUM(ifp,
    146 				csum & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
    147 				in_undefer_cksum_tcpudp(m);
    148 			}
    149 			m->m_pkthdr.csum_flags &=
    150 			~(M_CSUM_TCPv4|M_CSUM_UDPv4);
    151 
    152 		}
    153 	}
    154 }
    155 
    156 static int
    157 npf_fragment(npf_t *npf, struct ifnet *ifp, struct ip *ip1,
    158     struct mbuf **m0, struct sockaddr_in *dst)
    159 {
    160 	int error;
    161 	struct ip *ip = ip1;
    162 	struct mbuf *m = *m0;
    163 
    164 	/*
    165 	 * Too large for interface; fragment if possible.
    166 	 * Must be able to put at least 8 bytes per fragment.
    167 	 */
    168 	if (ntohs(ip->ip_off) & IP_DF) {
    169 		npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
    170 		icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0,
    171 			ifp->if_mtu);
    172 		return EINVAL;
    173 	}
    174 	/*
    175 		* We can't use HW checksumming if we're about to fragment the packet.
    176 		*
    177 		* XXX Some hardware can do this.
    178 		*/
    179 	if (m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
    180 		if (IN_NEED_CHECKSUM(ifp,
    181 			m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
    182 			in_undefer_cksum_tcpudp(m);
    183 		}
    184 		m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4);
    185 	}
    186 
    187 	error = ip_fragment(m, ifp, ifp->if_mtu);
    188 	if (error) {
    189 		m = NULL;
    190 		return error;
    191 	}
    192 
    193 	for (; m; m = *m0) {
    194 		*m0 = m->m_nextpkt;
    195 		m->m_nextpkt = NULL;
    196 		if (error) {
    197 			m_freem(m);
    198 			continue;
    199 		}
    200 
    201 		KASSERT((m->m_pkthdr.csum_flags &
    202 			(M_CSUM_UDPv4 | M_CSUM_TCPv4)) == 0);
    203 		error = ip_if_output(ifp, m, sintocsa(dst), NULL);
    204 	}
    205 
    206 	if (error == 0) {
    207 		npf_stats_inc(npf, NPF_STAT_FRAGMENTS);
    208 	}
    209 
    210 	return error;
    211 }
    212 
    213 /*
    214  * Ensure sending address is valid.
    215  * if the packet could be dropped without error (protocol dependent).
    216  */
    217 static int
    218 ip_ifaddrvalid(const struct in_ifaddr *ia)
    219 {
    220 
    221 	if (ia->ia_addr.sin_addr.s_addr == INADDR_ANY)
    222 		return 0;
    223 
    224 	if (ia->ia4_flags & IN_IFF_DUPLICATED)
    225 		return EADDRINUSE;
    226 	else if (ia->ia4_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED))
    227 		return EADDRNOTAVAIL;
    228 
    229 	return 0;
    230 }
    231 
    232 /*
    233  * search for the source address structure to
    234  * maintain output statistics, and verify address
    235  * validity
    236  */
    237 static int
    238 npf_validate_saddr(npf_t * npf, struct ip *ip, struct ifnet* ifp)
    239 {
    240 	int error = 0;
    241 	struct in_ifaddr *ia = NULL;
    242 	union {
    243 		struct sockaddr		sa;
    244 		struct sockaddr_in	sin;
    245 	} usrc;
    246 
    247 	struct psref psref_ia;
    248 
    249 	KASSERT(ia == NULL);
    250 	sockaddr_in_init(&usrc.sin, &ip->ip_src, 0);
    251 	ia = ifatoia(ifaof_ifpforaddr_psref(&usrc.sa, ifp, &psref_ia));
    252 
    253 	/*
    254 	 * Ensure we only send from a valid address.
    255 	 * A NULL address is valid because the packet could be
    256 	 * generated from a packet filter.
    257 	 */
    258 	if (ia != NULL &&
    259 	    (error = ip_ifaddrvalid(ia)) != 0)
    260 	{
    261 		char buf[32];
    262 		NPF_LOGADDR(buf, &ip->ip_src);
    263 		log(LOG_ERR,
    264 		    "refusing to send from invalid address %s (pid %d)\n",
    265 		    buf, curproc->p_pid);
    266 
    267 		npf_stats_inc(npf, NPF_STAT_NOREROUTE);
    268 	}
    269 	ia4_release(ia, &psref_ia);
    270 	return error;
    271 }
    272 
    273 #if defined(INET6)
    274 /* this code is copied from ip6_output*/
    275 static void
    276 npf_validate_s6addr(struct mbuf *m0, struct ifnet *ifp1, int *sw_csum)
    277 {
    278 	struct in6_ifaddr *ia6;
    279 	struct mbuf *m = m0;
    280 	struct ifnet *ifp = ifp1;
    281 	struct ip6_hdr *ip6;
    282 	int csum;
    283 	int s;
    284 
    285 	ip6 = mtod(m, struct ip6_hdr *);
    286 	s = pserialize_read_enter();
    287 	ia6 = in6_ifawithifp(ifp, &ip6->ip6_src);
    288 	if (ia6) {
    289 		/* Record statistics for this interface address. */
    290 		ia6->ia_ifa.ifa_data.ifad_outbytes += m->m_pkthdr.len;
    291 	}
    292 	pserialize_read_exit(s);
    293 
    294 	/* check sum */
    295 	*sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
    296 	csum = *sw_csum;
    297 
    298 	if ((csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) != 0) {
    299 		if (IN6_NEED_CHECKSUM(ifp,
    300 			csum & (M_CSUM_UDPv6|M_CSUM_TCPv6))) {
    301 			in6_undefer_cksum_tcpudp(m);
    302 		}
    303 		m->m_pkthdr.csum_flags &= ~(M_CSUM_UDPv6|M_CSUM_TCPv6);
    304 	}
    305 }
    306 #endif
    307 
    308 /* main routing function for kernel module */
    309 static bool
    310 npf_route(npf_cache_t *npc, void *meta, const npf_match_info_t __unused *mi, int *decision)
    311 {
    312 	struct mbuf *m0 = nbuf_head_mbuf(npc->npc_nbuf);
    313 	const npf_ext_route_t *route = meta;
    314 	npf_t *npf = npf_getkernctx();
    315 	struct ifnet *ifp;
    316 	int error;
    317 	int sw_csum;
    318 
    319 	union {
    320 		struct sockaddr_in v4;
    321 		struct sockaddr_in6 v6;
    322 	} dst;
    323 
    324 	/* Skip, if already blocking.
    325 	 * also when routing is applied to a stateful rule, incoming packets
    326 	 * are routed since the procedure becomes attached to the connection
    327 	 * and hence will not be desirable
    328 	 */
    329 	if (*decision == NPF_DECISION_BLOCK ||
    330     (mi->mi_di == PFIL_IN)) {
    331 			return true;
    332 	}
    333 
    334 	/* global lock for interface lookup */
    335 	KERNEL_LOCK(1, NULL);
    336 	ifp = ifunit(route->ifname);
    337 	if (ifp == NULL) {
    338 			/* XXX: oops */
    339 			goto bad;
    340 	}
    341 
    342 	if (npf_iscached(npc, NPC_IP6)) {
    343 #if defined(INET6)
    344 		struct ip6_hdr *ip6 = npc->npc_ip.v6;
    345 		sockaddr_in6_init(&dst.v6, &ip6->ip6_dst, 0, 0, 0);
    346 
    347 		if (IN6_IS_SCOPE_EMBEDDABLE(&dst.v6.sin6_addr)) {
    348 			error = in6_setscope(&dst.v6.sin6_addr, ifp, NULL);
    349 			if (error) {
    350 				goto bad;
    351 			}
    352 		}
    353 
    354 		npf_validate_s6addr(m0, ifp, &sw_csum);
    355 
    356 		if (m0->m_pkthdr.len <= ifp->if_mtu) {
    357 			if (__predict_false(sw_csum & M_CSUM_TSOv6)) {
    358 				/*
    359 				 * TSO6 is required by a packet, but disabled for
    360 				 * the interface.
    361 				 */
    362 				error = ip6_tso_output(ifp, ifp, m0, &dst.v6, NULL);
    363 			} else
    364 				error = ip6_if_output(ifp, ifp, m0, &dst.v6, NULL);
    365 
    366 			if (error) {
    367 				goto bad;
    368 			}
    369 
    370 		} else {
    371 			/* router not allowed to fragmenrt */
    372 			npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
    373 			icmp6_error(m0, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu);
    374 		}
    375 #endif
    376 	} else if (npf_iscached(npc, NPC_IP4)) {
    377 		struct ip *ip = npc->npc_ip.v4;
    378 		struct mbuf *m = m0;
    379 
    380 		KASSERT(ip != NULL);
    381 		KASSERT(m != NULL);
    382 
    383 		/*
    384 		 * NB: This code is copied from ip_output and re-arranged
    385 		 * checks fragmentation, checksum and source address validity
    386 		 */
    387 		sockaddr_in_init(&dst.v4, &ip->ip_dst, 0);
    388 
    389 		error = npf_validate_saddr(npf, ip, ifp);
    390 		if (error)
    391 			goto bad;
    392 
    393 		if (ntohs(ip->ip_len) > ifp->if_mtu)
    394 			goto fragment;
    395 
    396 		npf_chcksum(ifp, m, ip, &sw_csum);
    397 
    398 		/* Send it */
    399 		if (__predict_false(sw_csum & M_CSUM_TSOv4)) {
    400 			/*
    401 			 * TSO4 is required by a packet, but disabled for
    402 			 * the interface.
    403 			 */
    404 			error = ip_tso_output(ifp, m, sintocsa(&dst.v4), NULL);
    405 		} else
    406 			error = ip_if_output(ifp, m, sintocsa(&dst.v4), NULL);
    407 
    408 		if (error) {
    409 			goto bad;
    410 		}
    411 		goto done;
    412 
    413 fragment:
    414 		error = npf_fragment(npf, ifp, ip, &m0, &dst.v4);
    415 		if (error) {
    416 			goto bad;
    417 		}
    418 	}
    419 
    420 /*
    421  * for routing procedures, we reverse the returns
    422  * because we need the kernel to stop processing the mbuf
    423  * after we leave the filtering context
    424  */
    425 done:
    426 	npf_stats_inc(npf, NPF_STAT_REROUTE);
    427 	m0 = NULL;
    428 	KERNEL_UNLOCK_ONE(NULL);
    429 	return false;
    430 
    431 bad:
    432 	npf_stats_inc(npf, NPF_STAT_NOREROUTE);
    433 	m_freem(m0);
    434 	m0 = NULL;
    435 	KERNEL_UNLOCK_ONE(NULL);
    436 	return true;
    437 }
    438 
    439 __dso_public int
    440 npf_ext_route_init(npf_t *npf)
    441 {
    442 	static const npf_ext_ops_t npf_route_ops = {
    443 		.version        = NPFEXT_ROUTE_VER,
    444 		.ctx            = NULL,
    445 		.ctor           = npf_route_ctor,
    446 		.dtor           = npf_route_dtor,
    447 		.proc           = npf_route
    448 	};
    449 	npf_ext_route_id = npf_ext_register(npf, "route", &npf_route_ops);
    450 	return npf_ext_route_id ? 0 : EEXIST;
    451 }
    452 
    453 __dso_public int
    454 npf_ext_route_fini(npf_t *npf)
    455 {
    456 	return npf_ext_unregister(npf, npf_ext_route_id);
    457 }
    458 
    459 #ifdef _KERNEL
    460 static int
    461 npf_ext_route_modcmd(modcmd_t cmd, void *arg)
    462 {
    463 	npf_t *npf = npf_getkernctx();
    464 
    465 	switch (cmd) {
    466 	case MODULE_CMD_INIT:
    467 		return npf_ext_route_init(npf);
    468 	case MODULE_CMD_FINI:
    469 		return npf_ext_route_fini(npf);
    470 	case MODULE_CMD_AUTOUNLOAD:
    471 		/* Allow auto-unload only if NPF permits it. */
    472 		return npf_autounload_p() ? 0 : EBUSY;
    473 	default:
    474 		return ENOTTY;
    475 	}
    476 	return 0;
    477 }
    478 #endif
    479