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