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