NPF Policy based routing based on interface
Emmanuel Nyarko <[email protected]> Fri, 27 Mar 2026 12:31:04 +0000
| Newsgroups | gmane.os.netbsd.devel.network |
|---|---|
| Message-ID | <[email protected]> |
--Apple-Mail=_4A5285C0-2A5D-4D6A-91E7-D771CC69371B
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=utf-8
Hi tech-net,
i got access to code rmind@ started on NPF routing on interface and i =
have completed it and I want to commit it on Monday.
attached is a diff of the work for review.
simple use case:
say you want to route google traffic through bge0 and the rest on bge1.
in /etc/npf.conf,
map $external_interface static $local_subnet -> bge0_IP \ // =
selectively NATing packets going to google from your internal network
pass to $google_IP
map $external_interface static from $local_subnet -> bge1_IP // rest =
of packets
procedure : =E2=80=9Cgoogle-route=E2=80=9D {
route : bge0
}
group =E2=80=9Cinternal=E2=80=9D on $internal_interface {
pass in final from $local_subnet to any // inbound =
packets from your home network you want to route
}=20
group =E2=80=9Cexternal=E2=80=9D on $external_interface {
pass out final from $local_subnet to $google_IP apply =
=E2=80=9Cgoogle-route=E2=80=9D // you want google traffic to go through =
bge0 after NAT
pass out final from $local_subnet to any // follows =
normal routing=20
}
we are very fine to have a ruleset like this because an inbound packet =
which is not for us will
go through through ip_forward and ip_output being filtered again. =
applying=20
a route procedure on an outbound rule for the packet being forwarded and =
it works.
Checks done which mostly copied from ip_output after filtering:
1. source address validity,
2. checksum
3. Fragmentation.
--Apple-Mail=_4A5285C0-2A5D-4D6A-91E7-D771CC69371B
Content-Disposition: attachment;
filename=npf_route
Content-Type: application/octet-stream;
x-unix-mode=0644;
name="npf_route"
Content-Transfer-Encoding: 7bit
? diffs
Index: distrib/sets/lists/base/shl.mi
===================================================================
RCS file: /cvsroot/src/distrib/sets/lists/base/shl.mi,v
retrieving revision 1.1032
diff -u -r1.1032 shl.mi
--- distrib/sets/lists/base/shl.mi 18 Mar 2026 23:53:57 -0000 1.1032
+++ distrib/sets/lists/base/shl.mi 27 Mar 2026 10:11:44 -0000
@@ -134,6 +134,9 @@
./lib/npf/ext_rndblock.so base-npf-shlib compatfile,npf
./lib/npf/ext_rndblock.so.0 base-npf-shlib compatfile,npf
./lib/npf/ext_rndblock.so.0.0 base-npf-shlib compatfile,npf
+./lib/npf/ext_route.so base-npf-shlib compatfile,npf
+./lib/npf/ext_route.so.0 base-npf-shlib compatfile,npf
+./lib/npf/ext_route.so.0.0 base-npf-shlib compatfile,npf
./libexec/ld.elf_so base-sys-shlib dynamicroot
./usr/lib/i18n/libBIG5.so base-i18n-shlib compatfile
./usr/lib/i18n/libBIG5.so.5 base-i18n-shlib compatfile
Index: distrib/sets/lists/modules/mi
===================================================================
RCS file: /cvsroot/src/distrib/sets/lists/modules/mi,v
retrieving revision 1.165
diff -u -r1.165 mi
--- distrib/sets/lists/modules/mi 12 Feb 2026 04:22:59 -0000 1.165
+++ distrib/sets/lists/modules/mi 27 Mar 2026 10:11:44 -0000
@@ -363,6 +363,8 @@
./@MODULEDIR@/npf_ext_normalize/npf_ext_normalize.kmod modules-base-kernel kmod
./@MODULEDIR@/npf_ext_rndblock modules-base-kernel kmod
./@MODULEDIR@/npf_ext_rndblock/npf_ext_rndblock.kmod modules-base-kernel kmod
+./@MODULEDIR@/npf_ext_route modules-base-kernel kmod
+./@MODULEDIR@/npf_ext_route/npf_ext_route.kmod modules-base-kernel kmod
./@MODULEDIR@/ntfs modules-base-kernel kmod
./@MODULEDIR@/ntfs/ntfs.kmod modules-base-kernel kmod
./@MODULEDIR@/null modules-base-kernel kmod
Index: lib/npf/Makefile
===================================================================
RCS file: /cvsroot/src/lib/npf/Makefile,v
retrieving revision 1.3
diff -u -r1.3 Makefile
--- lib/npf/Makefile 10 Mar 2013 21:41:05 -0000 1.3
+++ lib/npf/Makefile 27 Mar 2026 10:12:00 -0000
@@ -4,7 +4,7 @@
.if ${MKPIC} != "no"
-SUBDIR= ext_log ext_normalize ext_rndblock
+SUBDIR= ext_log ext_normalize ext_rndblock ext_route
.endif
Index: lib/npf/ext_route/Makefile
===================================================================
RCS file: lib/npf/ext_route/Makefile
diff -N lib/npf/ext_route/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/npf/ext_route/Makefile 27 Mar 2026 10:12:00 -0000
@@ -0,0 +1,3 @@
+
+MOD= ext_route
+.include "${.CURDIR}/../mod.mk"
\ No newline at end of file
Index: lib/npf/ext_route/npfext_route.c
===================================================================
RCS file: lib/npf/ext_route/npfext_route.c
diff -N lib/npf/ext_route/npfext_route.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/npf/ext_route/npfext_route.c 27 Mar 2026 10:12:00 -0000
@@ -0,0 +1,71 @@
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emmanuel Nyarko.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD$");
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <err.h>
+#include <unistd.h>
+
+#include <npf.h>
+
+int npfext_route_init(void);
+nl_ext_t * npfext_route_construct(const char *);
+int npfext_route_param(nl_ext_t *, const char *, const char *);
+
+int
+npfext_route_init(void)
+{
+ /* Nothing to initialise. */
+ return 0;
+}
+
+nl_ext_t *
+npfext_route_construct(const char *name)
+{
+ assert(strcmp(name, "route") == 0);
+ return npf_ext_construct(name);
+}
+
+int
+npfext_route_param(nl_ext_t *ext, const char *param, const char *val __unused)
+{
+ assert(param != NULL);
+ npf_ext_param_string(ext, "route-interface", param);
+ return 0;
+}
Index: lib/npf/ext_route/shlib_version
===================================================================
RCS file: lib/npf/ext_route/shlib_version
diff -N lib/npf/ext_route/shlib_version
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ lib/npf/ext_route/shlib_version 27 Mar 2026 10:12:00 -0000
@@ -0,0 +1,3 @@
+
+major=0
+minor=0
Index: sys/modules/Makefile
===================================================================
RCS file: /cvsroot/src/sys/modules/Makefile,v
retrieving revision 1.300
diff -u -r1.300 Makefile
--- sys/modules/Makefile 20 Feb 2026 07:54:26 -0000 1.300
+++ sys/modules/Makefile 27 Mar 2026 10:12:03 -0000
@@ -144,6 +144,7 @@
SUBDIR+= npf_ext_log
SUBDIR+= npf_ext_normalize
SUBDIR+= npf_ext_rndblock
+SUBDIR+= npf_ext_route
SUBDIR+= ntfs
SUBDIR+= null
SUBDIR+= onewire
Index: sys/modules/npf_ext_route/Makefile
===================================================================
RCS file: sys/modules/npf_ext_route/Makefile
diff -N sys/modules/npf_ext_route/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sys/modules/npf_ext_route/Makefile 27 Mar 2026 10:12:03 -0000
@@ -0,0 +1,11 @@
+.include "../Makefile.inc"
+
+.PATH: ${S}/net/npf
+
+KMOD= npf_ext_route
+
+SRCS= npf_ext_route.c
+
+CPPFLAGS+= -I${NETBSDSRCDIR}/sys/external/bsd/libnv/dist
+
+.include <bsd.kmodule.mk>
Index: sys/net/npf/files.npf
===================================================================
RCS file: /cvsroot/src/sys/net/npf/files.npf,v
retrieving revision 1.24
diff -u -r1.24 files.npf
--- sys/net/npf/files.npf 1 Jun 2025 00:24:19 -0000 1.24
+++ sys/net/npf/files.npf 27 Mar 2026 10:12:03 -0000
@@ -44,6 +44,7 @@
file net/npf/npf_ext_log.c npf
file net/npf/npf_ext_normalize.c npf
file net/npf/npf_ext_rndblock.c npf
+file net/npf/npf_ext_route.c npf
# ALGs
file net/npf/npf_alg_icmp.c npf
Index: sys/net/npf/npf.h
===================================================================
RCS file: /cvsroot/src/sys/net/npf/npf.h,v
retrieving revision 1.68
diff -u -r1.68 npf.h
--- sys/net/npf/npf.h 9 Oct 2025 15:30:18 -0000 1.68
+++ sys/net/npf/npf.h 27 Mar 2026 10:12:03 -0000
@@ -373,8 +373,12 @@
NPF_STAT_RACE_NAT,
/* Fragments. */
NPF_STAT_FRAGMENTS,
+ NPF_STAT_NOFRAGMENT,
NPF_STAT_REASSEMBLY,
NPF_STAT_REASSFAIL,
+ /* routing */
+ NPF_STAT_REROUTE,
+ NPF_STAT_NOREROUTE,
/* Other errors. */
NPF_STAT_ERROR,
/* nbuf non-contiguous cases. */
Index: sys/net/npf/npf_ext_route.c
===================================================================
RCS file: sys/net/npf/npf_ext_route.c
diff -N sys/net/npf/npf_ext_route.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ sys/net/npf/npf_ext_route.c 27 Mar 2026 10:12:03 -0000
@@ -0,0 +1,478 @@
+/*-
+ * Copyright (c) 2025 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Emmanuel Nyarko.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * NPF route extension.
+ */
+
+ #ifdef _KERNEL
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/module.h>
+
+#include <sys/conf.h>
+#include <sys/kmem.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/queue.h>
+#include <sys/syslog.h>
+#include <sys/proc.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/bpf.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/in_offload.h>
+#include <netinet/ip6.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/scope6_var.h>
+#include <netinet6/in6_offload.h>
+#endif
+
+#include "npf_impl.h"
+
+NPF_EXT_MODULE(npf_ext_route, "");
+
+#define NPFEXT_ROUTE_VER 1
+
+#define NPF_LOGADDR(buf, a) \
+ snprintf(buf, sizeof(buf), "%u.%u.%u.%u", \
+ ((a)->s_addr >> 24) & 0xFF, \
+ ((a)->s_addr >> 16) & 0xFF, \
+ ((a)->s_addr >> 8) & 0xFF, \
+ (a)->s_addr & 0xFF)
+
+static void * npf_ext_route_id;
+
+typedef struct {
+ char ifname[IFNAMSIZ];
+} npf_ext_route_t;
+
+static int
+npf_route_ctor(npf_rproc_t *rp, const nvlist_t* params)
+{
+ npf_ext_route_t *meta;
+ const char *ifname;
+
+ meta = kmem_zalloc(sizeof(*meta), KM_SLEEP);
+ ifname = nvlist_get_string(params, "route-interface");
+
+ if (!ifname)
+ return EINVAL;
+
+ /* XXX use something like npf_ifmap */
+ strlcpy(meta->ifname, ifname, IFNAMSIZ);
+ npf_rproc_assign(rp, meta);
+ return 0;
+}
+
+static void
+npf_route_dtor(npf_rproc_t *rp, void *meta)
+{
+ kmem_free(meta, sizeof(npf_ext_route_t));
+}
+
+static void
+npf_chcksum(struct ifnet *ifp1, struct mbuf *m0, struct ip *ip1, int *sw_csum)
+{
+ int hlen;
+ struct mbuf *m = m0;
+ struct ip *ip = ip1;
+ struct ifnet *ifp = ifp1;
+ int csum;
+
+ /* NB: This code is copied from ip_output */
+ hlen = ip->ip_hl << 2;
+ ip->ip_sum = 0;
+ m->m_pkthdr.csum_data |= hlen << 16;
+
+ /* Maybe skip checksums on loopback interfaces. */
+ if (IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4)) {
+ m->m_pkthdr.csum_flags |= M_CSUM_IPv4;
+ }
+
+ *sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
+ csum = *sw_csum;
+
+ if ((m->m_pkthdr.csum_flags & M_CSUM_TSOv4) == 0) {
+ /*
+ * Perform any checksums that the hardware can't do
+ * for us.
+ *
+ * XXX Does any hardware require the {th,uh}_sum
+ * XXX fields to be 0?
+ */
+ if (csum & M_CSUM_IPv4) {
+ KASSERT(IN_NEED_CHECKSUM(ifp, M_CSUM_IPv4));
+ ip->ip_sum = in_cksum(m, hlen);
+ m->m_pkthdr.csum_flags &= ~M_CSUM_IPv4;
+ }
+ if (csum & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
+
+ if (IN_NEED_CHECKSUM(ifp,
+ csum & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
+ in_undefer_cksum_tcpudp(m);
+ }
+ m->m_pkthdr.csum_flags &=
+ ~(M_CSUM_TCPv4|M_CSUM_UDPv4);
+
+ }
+ }
+}
+
+static int
+npf_fragment(npf_t *npf, struct ifnet *ifp, struct ip *ip1,
+ struct mbuf **m0, struct sockaddr_in *dst)
+{
+ int error;
+ struct ip *ip = ip1;
+ struct mbuf *m = *m0;
+
+ /*
+ * Too large for interface; fragment if possible.
+ * Must be able to put at least 8 bytes per fragment.
+ */
+ if (ntohs(ip->ip_off) & IP_DF) {
+ npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
+ icmp_error(m, ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG, 0,
+ ifp->if_mtu);
+ return EINVAL;
+ }
+ /*
+ * We can't use HW checksumming if we're about to fragment the packet.
+ *
+ * XXX Some hardware can do this.
+ */
+ if (m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4)) {
+ if (IN_NEED_CHECKSUM(ifp,
+ m->m_pkthdr.csum_flags & (M_CSUM_TCPv4|M_CSUM_UDPv4))) {
+ in_undefer_cksum_tcpudp(m);
+ }
+ m->m_pkthdr.csum_flags &= ~(M_CSUM_TCPv4|M_CSUM_UDPv4);
+ }
+
+ error = ip_fragment(m, ifp, ifp->if_mtu);
+ if (error) {
+ m = NULL;
+ return error;
+ }
+
+ for (; m; m = *m0) {
+ *m0 = m->m_nextpkt;
+ m->m_nextpkt = NULL;
+ if (error) {
+ m_freem(m);
+ continue;
+ }
+
+ KASSERT((m->m_pkthdr.csum_flags &
+ (M_CSUM_UDPv4 | M_CSUM_TCPv4)) == 0);
+ error = ip_if_output(ifp, m, sintocsa(dst), NULL);
+ }
+
+ if (error == 0) {
+ npf_stats_inc(npf, NPF_STAT_FRAGMENTS);
+ }
+
+ return error;
+}
+
+/*
+ * Ensure sending address is valid.
+ * if the packet could be dropped without error (protocol dependent).
+ */
+static int
+ip_ifaddrvalid(const struct in_ifaddr *ia)
+{
+
+ if (ia->ia_addr.sin_addr.s_addr == INADDR_ANY)
+ return 0;
+
+ if (ia->ia4_flags & IN_IFF_DUPLICATED)
+ return EADDRINUSE;
+ else if (ia->ia4_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED))
+ return EADDRNOTAVAIL;
+
+ return 0;
+}
+
+/*
+ * search for the source address structure to
+ * maintain output statistics, and verify address
+ * validity
+ */
+static int
+npf_validate_saddr(npf_t * npf, struct ip *ip, struct ifnet* ifp)
+{
+ int error = 0;
+ struct in_ifaddr *ia = NULL;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ } usrc;
+
+ struct psref psref_ia;
+
+ KASSERT(ia == NULL);
+ sockaddr_in_init(&usrc.sin, &ip->ip_src, 0);
+ ia = ifatoia(ifaof_ifpforaddr_psref(&usrc.sa, ifp, &psref_ia));
+
+ /*
+ * Ensure we only send from a valid address.
+ * A NULL address is valid because the packet could be
+ * generated from a packet filter.
+ */
+ if (ia != NULL &&
+ (error = ip_ifaddrvalid(ia)) != 0)
+ {
+ char buf[32];
+ NPF_LOGADDR(buf, &ip->ip_src);
+ log(LOG_ERR,
+ "refusing to send from invalid address %s (pid %d)\n",
+ buf, curproc->p_pid);
+
+ npf_stats_inc(npf, NPF_STAT_NOREROUTE);
+ }
+ ia4_release(ia, &psref_ia);
+ return error;
+}
+
+#if defined(INET6)
+/* this code is copied from ip6_output*/
+static void
+npf_validate_s6addr(struct mbuf *m0, struct ifnet *ifp1, int *sw_csum)
+{
+ struct in6_ifaddr *ia6;
+ struct mbuf *m = m0;
+ struct ifnet *ifp = ifp1;
+ struct ip6_hdr *ip6;
+ int csum;
+ int s;
+
+ ip6 = mtod(m, struct ip6_hdr *);
+ s = pserialize_read_enter();
+ ia6 = in6_ifawithifp(ifp, &ip6->ip6_src);
+ if (ia6) {
+ /* Record statistics for this interface address. */
+ ia6->ia_ifa.ifa_data.ifad_outbytes += m->m_pkthdr.len;
+ }
+ pserialize_read_exit(s);
+
+ /* check sum */
+ *sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_csum_flags_tx;
+ csum = *sw_csum;
+
+ if ((csum & (M_CSUM_UDPv6|M_CSUM_TCPv6)) != 0) {
+ if (IN6_NEED_CHECKSUM(ifp,
+ csum & (M_CSUM_UDPv6|M_CSUM_TCPv6))) {
+ in6_undefer_cksum_tcpudp(m);
+ }
+ m->m_pkthdr.csum_flags &= ~(M_CSUM_UDPv6|M_CSUM_TCPv6);
+ }
+}
+#endif
+
+/* main routing function for kernel module */
+static bool
+npf_route(npf_cache_t *npc, void *meta, const npf_match_info_t __unused *mi, int *decision)
+{
+ struct mbuf *m0 = nbuf_head_mbuf(npc->npc_nbuf);
+ const npf_ext_route_t *route = meta;
+ npf_t *npf = npf_getkernctx();
+ struct ifnet *ifp;
+ int error;
+ int sw_csum;
+
+ union {
+ struct sockaddr_in v4;
+ struct sockaddr_in6 v6;
+ } dst;
+
+ /* Skip, if already blocking.
+ * also when routing is applied to a stateful rule, incoming packets
+ * are routed since the procedure becomes attached to the connection
+ * and hence will not be desirable
+ */
+ if (*decision == NPF_DECISION_BLOCK ||
+ (mi->mi_di == PFIL_IN)) {
+ return true;
+ }
+
+ /* global lock for interface lookup */
+ KERNEL_LOCK(1, NULL);
+ ifp = ifunit(route->ifname);
+ if (ifp == NULL) {
+ /* XXX: oops */
+ goto bad;
+ }
+
+ if (npf_iscached(npc, NPC_IP6)) {
+#if defined(INET6)
+ struct ip6_hdr *ip6 = npc->npc_ip.v6;
+ sockaddr_in6_init(&dst.v6, &ip6->ip6_dst, 0, 0, 0);
+
+ if (IN6_IS_SCOPE_EMBEDDABLE(&dst.v6.sin6_addr)) {
+ error = in6_setscope(&dst.v6.sin6_addr, ifp, NULL);
+ if (error) {
+ goto bad;
+ }
+ }
+
+ npf_validate_s6addr(m0, ifp, &sw_csum);
+
+ if (m0->m_pkthdr.len <= ifp->if_mtu) {
+ if (__predict_false(sw_csum & M_CSUM_TSOv6)) {
+ /*
+ * TSO6 is required by a packet, but disabled for
+ * the interface.
+ */
+ error = ip6_tso_output(ifp, ifp, m0, &dst.v6, NULL);
+ } else
+ error = ip6_if_output(ifp, ifp, m0, &dst.v6, NULL);
+
+ if (error) {
+ goto bad;
+ }
+
+ } else {
+ /* router not allowed to fragmenrt */
+ npf_stats_inc(npf, NPF_STAT_NOFRAGMENT);
+ icmp6_error(m0, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu);
+ }
+#endif
+ } else if (npf_iscached(npc, NPC_IP4)) {
+ struct ip *ip = npc->npc_ip.v4;
+ struct mbuf *m = m0;
+
+ KASSERT(ip != NULL);
+ KASSERT(m != NULL);
+
+ /*
+ * NB: This code is copied from ip_output and re-arranged
+ * checks fragmentation, checksum and source address validity
+ */
+ sockaddr_in_init(&dst.v4, &ip->ip_dst, 0);
+
+ error = npf_validate_saddr(npf, ip, ifp);
+ if (error)
+ goto bad;
+
+ if (ntohs(ip->ip_len) > ifp->if_mtu)
+ goto fragment;
+
+ npf_chcksum(ifp, m, ip, &sw_csum);
+
+ /* Send it */
+ if (__predict_false(sw_csum & M_CSUM_TSOv4)) {
+ /*
+ * TSO4 is required by a packet, but disabled for
+ * the interface.
+ */
+ error = ip_tso_output(ifp, m, sintocsa(&dst.v4), NULL);
+ } else
+ error = ip_if_output(ifp, m, sintocsa(&dst.v4), NULL);
+
+ if (error) {
+ goto bad;
+ }
+ goto done;
+
+fragment:
+ error = npf_fragment(npf, ifp, ip, &m0, &dst.v4);
+ if (error) {
+ goto bad;
+ }
+ }
+
+/*
+ * for routing procedures, we reverse the returns
+ * because we need the kernel to stop processing the mbuf
+ * after we leave the filtering context
+ */
+done:
+ npf_stats_inc(npf, NPF_STAT_REROUTE);
+ m0 = NULL;
+ KERNEL_UNLOCK_ONE(NULL);
+ return false;
+
+bad:
+ npf_stats_inc(npf, NPF_STAT_NOREROUTE);
+ m_freem(m0);
+ m0 = NULL;
+ KERNEL_UNLOCK_ONE(NULL);
+ return true;
+}
+
+__dso_public int
+npf_ext_route_init(npf_t *npf)
+{
+ static const npf_ext_ops_t npf_route_ops = {
+ .version = NPFEXT_ROUTE_VER,
+ .ctx = NULL,
+ .ctor = npf_route_ctor,
+ .dtor = npf_route_dtor,
+ .proc = npf_route
+ };
+ npf_ext_route_id = npf_ext_register(npf, "route", &npf_route_ops);
+ return npf_ext_route_id ? 0 : EEXIST;
+}
+
+__dso_public int
+npf_ext_route_fini(npf_t *npf)
+{
+ return npf_ext_unregister(npf, npf_ext_route_id);
+}
+
+#ifdef _KERNEL
+static int
+npf_ext_route_modcmd(modcmd_t cmd, void *arg)
+{
+ npf_t *npf = npf_getkernctx();
+
+ switch (cmd) {
+ case MODULE_CMD_INIT:
+ return npf_ext_route_init(npf);
+ case MODULE_CMD_FINI:
+ return npf_ext_route_fini(npf);
+ case MODULE_CMD_AUTOUNLOAD:
+ /* Allow auto-unload only if NPF permits it. */
+ return npf_autounload_p() ? 0 : EBUSY;
+ default:
+ return ENOTTY;
+ }
+ return 0;
+}
+#endif
Index: sys/net/npf/npf_handler.c
===================================================================
RCS file: /cvsroot/src/sys/net/npf/npf_handler.c,v
retrieving revision 1.54
diff -u -r1.54 npf_handler.c
--- sys/net/npf/npf_handler.c 8 Jul 2025 15:56:23 -0000 1.54
+++ sys/net/npf/npf_handler.c 27 Mar 2026 10:12:03 -0000
@@ -292,6 +292,10 @@
if (rp && !npf_rproc_run(&npc, rp, &mi, &decision)) {
if (con) {
npf_conn_release(con);
+
+ /* ensure that reference is released only on connection in stateful rules */
+ if (is_rproc_route(rp))
+ return 0;
}
npf_rproc_release(rp);
/* mbuf already freed */
Index: sys/net/npf/npf_impl.h
===================================================================
RCS file: /cvsroot/src/sys/net/npf/npf_impl.h,v
retrieving revision 1.85
diff -u -r1.85 npf_impl.h
--- sys/net/npf/npf_impl.h 1 Jul 2025 18:42:37 -0000 1.85
+++ sys/net/npf/npf_impl.h 27 Mar 2026 10:12:03 -0000
@@ -457,6 +457,7 @@
npf_rproc_t * npf_rprocset_lookup(npf_rprocset_t *, const char *);
void npf_rprocset_insert(npf_rprocset_t *, npf_rproc_t *);
int npf_rprocset_export(const npf_rprocset_t *, nvlist_t *);
+int is_rproc_route(npf_rproc_t *);
npf_rproc_t * npf_rproc_create(const nvlist_t *);
void npf_rproc_acquire(npf_rproc_t *);
Index: sys/net/npf/npf_rproc.c
===================================================================
RCS file: /cvsroot/src/sys/net/npf/npf_rproc.c,v
retrieving revision 1.23
diff -u -r1.23 npf_rproc.c
--- sys/net/npf/npf_rproc.c 24 Feb 2023 11:03:01 -0000 1.23
+++ sys/net/npf/npf_rproc.c 27 Mar 2026 10:12:03 -0000
@@ -388,3 +388,9 @@
return true;
}
+
+int
+is_rproc_route(npf_rproc_t *rp)
+{
+ return !strcmp(rp->rp_name, "route");
+}
Index: sys/net/npf/npfkern.h
===================================================================
RCS file: /cvsroot/src/sys/net/npf/npfkern.h,v
retrieving revision 1.6
diff -u -r1.6 npfkern.h
--- sys/net/npf/npfkern.h 1 Jul 2025 18:42:37 -0000 1.6
+++ sys/net/npf/npfkern.h 27 Mar 2026 10:12:03 -0000
@@ -101,6 +101,10 @@
int npf_ext_rndblock_init(npf_t *);
int npf_ext_rndblock_fini(npf_t *);
+/* Routing */
+int npf_ext_route_init(npf_t *);
+int npf_ext_route_fini(npf_t *);
+
/*
* ALGs.
*/
Index: usr.sbin/npf/npfctl/npfctl.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/npf/npfctl/npfctl.c,v
retrieving revision 1.67
diff -u -r1.67 npfctl.c
--- usr.sbin/npf/npfctl/npfctl.c 1 Jul 2025 19:55:16 -0000 1.67
+++ usr.sbin/npf/npfctl/npfctl.c 27 Mar 2026 10:12:05 -0000
@@ -171,9 +171,14 @@
{ -1, "Fragmentation" },
{ NPF_STAT_FRAGMENTS, "fragments" },
+ { NPF_STAT_NOFRAGMENT, "failed fragmentation" },
{ NPF_STAT_REASSEMBLY, "reassembled" },
{ NPF_STAT_REASSFAIL, "failed reassembly" },
+ { -1, "Routing" },
+ { NPF_STAT_REROUTE, "re-routing" },
+ { NPF_STAT_NOREROUTE, "failed re-routing" },
+
{ -1, "Other" },
{ NPF_STAT_ERROR, "unexpected errors" },
};
--Apple-Mail=_4A5285C0-2A5D-4D6A-91E7-D771CC69371B
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=us-ascii
Emmanuel
--Apple-Mail=_4A5285C0-2A5D-4D6A-91E7-D771CC69371B--