diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -238,6 +238,7 @@ #define POM_TYPE 0x01 #define POM_STICKYADDRESS 0x02 #define POM_ENDPI 0x04 +#define POM_IPV6NH 0x08 u_int8_t opts; int type; int staticport; @@ -543,7 +544,7 @@ %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS %token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO -%token BINATTO MAXPKTRATE MAXPKTSIZE +%token BINATTO MAXPKTRATE MAXPKTSIZE IPV6NH %token STRING %token NUMBER %token PORTBINARY @@ -2648,13 +2649,16 @@ YYERROR; } r.rt = $5.rt; - decide_address_family($5.redirspec->host, &r.af); - if (!(r.rule_flag & PFRULE_AFTO)) - remove_invalid_hosts(&($5.redirspec->host), &r.af); - if ($5.redirspec->host == NULL) { - yyerror("no routing address with " - "matching address family found."); - YYERROR; + + if (!($5.redirspec->pool_opts.opts & PF_POOL_IPV6NH)) { + decide_address_family($5.redirspec->host, &r.af); + if (!(r.rule_flag & PFRULE_AFTO)) + remove_invalid_hosts(&($5.redirspec->host), &r.af); + if ($5.redirspec->host == NULL) { + yyerror("no routing address with " + "matching address family found."); + YYERROR; + } } } #ifdef __FreeBSD__ @@ -2978,7 +2982,8 @@ filter_opts.nat = $4; filter_opts.nat->af = $2; - if ($4->af && $4->af != $2) { + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + if ($4->host == NULL) { yyerror("af-to addresses must be in the " "target address family"); YYERROR; @@ -2998,8 +3003,9 @@ filter_opts.nat->af = $2; filter_opts.rdr = $6; filter_opts.rdr->af = $2; - if (($4->af && $4->host->af != $2) || - ($6->af && $6->host->af != $2)) { + remove_invalid_hosts(&($4->host), &(filter_opts.nat->af)); + remove_invalid_hosts(&($6->host), &(filter_opts.rdr->af)); + if ($4->host == NULL || $6->host == NULL) { yyerror("af-to addresses must be in the " "target address family"); YYERROR; @@ -4674,6 +4680,14 @@ pool_opts.marker |= POM_ENDPI; pool_opts.opts |= PF_POOL_ENDPI; } + | IPV6NH { + if (pool_opts.marker & POM_IPV6NH) { + yyerror("prefer-ipv6-nexthop cannot be redefined"); + YYERROR; + } + pool_opts.marker |= POM_IPV6NH; + pool_opts.opts |= PF_POOL_IPV6NH; + } | MAPEPORTSET number '/' number '/' number { if (pool_opts.mape.offset) { yyerror("map-e-portset cannot be redefined"); @@ -4813,6 +4827,12 @@ "address'"); YYERROR; } + if ($9->pool_opts.opts & PF_POOL_IPV6NH) { + yyerror("The prefer-ipv6-nexthop option " + "can't be used for nat/rdr/binat pools" + ); + YYERROR; + } if (!r.af && ! $9->host->ifindex) r.af = $9->host->af; @@ -5074,13 +5094,6 @@ route_host_list : route_host optnl { $$ = $1; } | route_host_list comma route_host optnl { - if ($1->af == 0) - $1->af = $3->af; - if ($1->af != $3->af) { - yyerror("all pool addresses must be in the " - "same address family"); - YYERROR; - } $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; @@ -6678,6 +6691,7 @@ { "pass", PASS}, { "pflow", PFLOW}, { "port", PORT}, + { "prefer-ipv6-nexthop", IPV6NH}, { "prio", PRIO}, { "priority", PRIORITY}, { "priq", PRIQ}, diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -508,6 +508,8 @@ if (pool->mape.offset > 0) printf(" map-e-portset %u/%u/%u", pool->mape.offset, pool->mape.psidlen, pool->mape.psid); + if (pool->opts & PF_POOL_IPV6NH) + printf(" prefer-ipv6-nexthop"); } void @@ -1438,7 +1440,7 @@ ENTRY item; ENTRY *ret_item; int *answer; - + item.key = ifg->ifgrq_group; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) { struct ifgroupreq ifgr2; @@ -1580,7 +1582,7 @@ { ENTRY item; ENTRY *ret_item; - + item.key = name; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) return (0); diff --git a/sbin/pfctl/tests/files/pf1073.in b/sbin/pfctl/tests/files/pf1073.in new file mode 100644 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1073.in @@ -0,0 +1 @@ +pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) prefer-ipv6-nexthop inet diff --git a/sbin/pfctl/tests/files/pf1073.ok b/sbin/pfctl/tests/files/pf1073.ok new file mode 100644 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1073.ok @@ -0,0 +1 @@ +pass in on vtnet0 route-to (vtnet1 2001:db8::1) prefer-ipv6-nexthop inet all flags S/SA keep state diff --git a/sbin/pfctl/tests/files/pf1074.fail b/sbin/pfctl/tests/files/pf1074.fail new file mode 100644 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1074.fail @@ -0,0 +1 @@ +no routing address with matching address family found. diff --git a/sbin/pfctl/tests/files/pf1074.in b/sbin/pfctl/tests/files/pf1074.in new file mode 100644 --- /dev/null +++ b/sbin/pfctl/tests/files/pf1074.in @@ -0,0 +1 @@ +pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) inet diff --git a/sbin/pfctl/tests/pfctl_test_list.inc b/sbin/pfctl/tests/pfctl_test_list.inc --- a/sbin/pfctl/tests/pfctl_test_list.inc +++ b/sbin/pfctl/tests/pfctl_test_list.inc @@ -181,3 +181,5 @@ PFCTL_TEST_FAIL(1070, "include line number") PFCTL_TEST(1071, "mask length on (lo0)") PFCTL_TEST_FAIL(1072, "Invalid port range") +PFCTL_TEST(1073, "Filter AF different than route-to AF, with prefer-ipv6-nexthop") +PFCTL_TEST_FAIL(1074, "Filter AF different than route-to AF, without prefer-ipv6-nexthop") diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -2470,7 +2470,13 @@ This feature implements "full-cone" NAT behavior. .El .Pp -Additionally, the +Additionally, options +.Ar sticky-address +and +.Ar prefer-ipv6-nexthop +can be specified to influence how IP addresses selected from pools. +.Pp +The .Ar sticky-address option can be specified to help ensure that multiple connections from the same source are mapped to the same redirection address. @@ -2486,6 +2492,14 @@ See .Sx STATEFUL TRACKING OPTIONS for more ways to control the source tracking. +.Pp +The +.Ar prefer-ipv6-nexthop +option allows for IPv6 addresses to be used as the nexthop +for IPv4 packets routed with the +.Ar route-to +rule option. If a table is used with IPv4 and IPv6 addresses, first the IPv6 addresses +will be used in round-robin fashion, then IPv4 addresses. .Sh STATE MODULATION Much of the security derived from TCP is attributable to how well the initial sequence numbers (ISNs) are chosen. @@ -3580,7 +3594,7 @@ pooltype = ( "bitmask" | "random" | "source-hash" [ ( hex-key | string-key ) ] | - "round-robin" ) [ sticky-address ] + "round-robin" ) [ sticky-address | prefer-ipv6-nexthop ] subqueue = string | "{" queue-list "}" queue-list = string [ [ "," ] string ] diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -645,6 +645,7 @@ int tblidx; u_int16_t proxy_port[2]; u_int8_t opts; + sa_family_t ipv6_nexthop_af; }; struct pf_rule_actions { @@ -2680,7 +2681,7 @@ struct pf_addr *, struct pf_kpool *); u_short pf_map_addr_sn(u_int8_t, struct pf_krule *, struct pf_addr *, struct pf_addr *, - sa_family_t *, struct pfi_kkif **nkif, + sa_family_t *, struct pfi_kkif **, struct pf_addr *, struct pf_kpool *, pf_sn_types_t); int pf_get_transaddr_af(struct pf_krule *, diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c --- a/sys/netpfil/pf/if_pfsync.c +++ b/sys/netpfil/pf/if_pfsync.c @@ -605,7 +605,8 @@ rt_kif = rpool_first->kif; /* * Guess the AF of the route address, FreeBSD 13 does - * not support af-to so it should be safe. + * not support af-to nor prefer-ipv6-nexthop + * so it should be safe. */ rt_af = r->af; } else if (!PF_AZERO(&sp->pfs_1301.rt_addr, sp->pfs_1301.af)) { @@ -634,8 +635,9 @@ } rt = sp->pfs_1400.rt; /* - * Guess the AF of the route address, FreeBSD 13 does - * not support af-to so it should be safe. + * Guess the AF of the route address, FreeBSD 14 does + * not support af-to nor prefer-ipv6-nexthop + * so it should be safe. */ rt_af = sp->pfs_1400.af; } diff --git a/sys/netpfil/pf/pf.h b/sys/netpfil/pf/pf.h --- a/sys/netpfil/pf/pf.h +++ b/sys/netpfil/pf/pf.h @@ -131,6 +131,7 @@ #define PF_POOL_TYPEMASK 0x0f #define PF_POOL_STICKYADDR 0x20 #define PF_POOL_ENDPI 0x40 +#define PF_POOL_IPV6NH 0x80 #define PF_WSCALE_FLAG 0x80 #define PF_WSCALE_MASK 0x0f diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -5960,7 +5960,9 @@ if (r->rt) { /* * Set act.rt here instead of in pf_rule_to_actions() because - * it is applied only from the last pass rule. + * it is applied only from the last pass rule. For rules + * with the prefer-ipv6-nexthop option act.rt_af is a hint + * about AF of the forwarded packet and might be changed. */ pd->act.rt = r->rt; if (r->rt == PF_REPLYTO) @@ -8974,9 +8976,10 @@ struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp) { struct mbuf *m0, *m1, *md; - struct route ro; - const struct sockaddr *gw = &ro.ro_dst; - struct sockaddr_in *dst; + struct route_in6 ro; + union sockaddr_union rt_gw; + const union sockaddr_union *gw = (const union sockaddr_union *)&ro.ro_dst; + union sockaddr_union *dst; struct ip *ip; struct ifnet *ifp = NULL; int error = 0; @@ -9071,10 +9074,35 @@ ip = mtod(m0, struct ip *); bzero(&ro, sizeof(ro)); - dst = (struct sockaddr_in *)&ro.ro_dst; - dst->sin_family = AF_INET; - dst->sin_len = sizeof(struct sockaddr_in); - dst->sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; + dst = (union sockaddr_union *)&ro.ro_dst; + dst->sin.sin_family = AF_INET; + dst->sin.sin_len = sizeof(struct sockaddr_in); + dst->sin.sin_addr = ip->ip_dst; + if (ifp) { /* Only needed in forward direction and route-to */ + bzero(&rt_gw, sizeof(rt_gw)); + ro.ro_flags |= RT_HAS_GW; + gw = &rt_gw; + switch (pd->act.rt_af) { +#ifdef INET + case AF_INET: + rt_gw.sin.sin_family = AF_INET; + rt_gw.sin.sin_len = sizeof(struct sockaddr_in); + rt_gw.sin.sin_addr.s_addr = pd->act.rt_addr.v4.s_addr; + break; +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + rt_gw.sin6.sin6_family = AF_INET6; + rt_gw.sin6.sin6_len = sizeof(struct sockaddr_in6); + pf_addrcpy((struct pf_addr *)&rt_gw.sin6.sin6_addr, + &pd->act.rt_addr, AF_INET6); + break; +#endif /* INET6 */ + default: + /* Normal af-to without route-to */ + break; + } + } if (pd->dir == PF_IN) { if (ip->ip_ttl <= IPTTLDEC) { @@ -9098,10 +9126,10 @@ /* Use the gateway if needed. */ if (nh->nh_flags & NHF_GATEWAY) { - gw = &nh->gw_sa; + gw = (const union sockaddr_union *)&nh->gw_sa; ro.ro_flags |= RT_HAS_GW; } else { - dst->sin_addr = ip->ip_dst; + dst->sin.sin_addr = ip->ip_dst; } /* @@ -9126,6 +9154,9 @@ PF_STATE_UNLOCK(s); } + /* It must have been either set from rt_af or from fib4_lookup */ + KASSERT(gw->sin.sin_family != 0, ("%s: gw address family undetermined", __func__)); + if (ifp == NULL) { m0 = pd->m; pd->m = NULL; @@ -9210,9 +9241,11 @@ m_clrprotoflags(m0); /* Avoid confusing lower layers. */ md = m0; - error = pf_dummynet_route(pd, s, r, ifp, gw, &md); + error = pf_dummynet_route(pd, s, r, ifp, + (const struct sockaddr *)gw, &md); if (md != NULL) { - error = (*ifp->if_output)(ifp, md, gw, &ro); + error = (*ifp->if_output)(ifp, md, + (const struct sockaddr *)gw, (struct route *)&ro); SDT_PROBE2(pf, ip, route_to, output, ifp, error); } goto done; @@ -9253,9 +9286,11 @@ md = m0; pd->pf_mtag = pf_find_mtag(md); error = pf_dummynet_route(pd, s, r, ifp, - gw, &md); + (const struct sockaddr *)gw, &md); if (md != NULL) { - error = (*ifp->if_output)(ifp, md, gw, &ro); + error = (*ifp->if_output)(ifp, md, + (const struct sockaddr *)gw, + (struct route *)&ro); SDT_PROBE2(pf, ip, route_to, output, ifp, error); } } else diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -2276,6 +2276,7 @@ rule->nat.cur = TAILQ_FIRST(&rule->nat.list); rule->rdr.cur = TAILQ_FIRST(&rule->rdr.list); rule->route.cur = TAILQ_FIRST(&rule->route.list); + rule->route.ipv6_nexthop_af = AF_INET6; TAILQ_INSERT_TAIL(ruleset->rules[rs_num].inactive.ptr, rule, entries); ruleset->rules[rs_num].inactive.rcount++; diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c --- a/sys/netpfil/pf/pf_lb.c +++ b/sys/netpfil/pf/pf_lb.c @@ -545,11 +545,18 @@ uint64_t hashidx; int cnt; sa_family_t wanted_af; + u_int8_t pool_type; + bool prefer_ipv6_nexthop = rpool->opts & PF_POOL_IPV6NH; KASSERT(saf != 0, ("%s: saf == 0", __func__)); KASSERT(naf != NULL, ("%s: naf = NULL", __func__)); KASSERT((*naf) != 0, ("%s: *naf = 0", __func__)); + /* + * Given (*naf) is a hint about AF of the forwarded packet. + * It might be changed if prefer_ipv6_nexthop is enabled and + * the combination of nexthop AF and packet AF allows for it. + */ wanted_af = (*naf); mtx_lock(&rpool->mtx); @@ -594,19 +601,38 @@ } else { raddr = &rpool->cur->addr.v.a.addr; rmask = &rpool->cur->addr.v.a.mask; - /* - * For single addresses check their address family. Unless they - * have none, which happens when addresses are added with - * the old ioctl mechanism. In such case trust that the address - * has the proper AF. - */ - if (rpool->cur->af && rpool->cur->af != wanted_af) { - reason = PFRES_MAPFAILED; - goto done_pool_mtx; + } + + /* + * For pools with a single host with the prefer-ipv6-nexthop option + * we can return pool address of any AF, unless the forwarded packet + * is IPv6, then we can return only if pool address is IPv6. + * For non-prefer-ipv6-nexthop we can return pool address only + * of wanted AF, unless the pool address'es AF is unknown, which + * happens in case old ioctls have been used to set up the pool. + * + * Round-robin pools have their own logic for retrying next addresses. + */ + pool_type = rpool->opts & PF_POOL_TYPEMASK; + if (pool_type == PF_POOL_NONE || pool_type == PF_POOL_BITMASK || + ((pool_type == PF_POOL_RANDOM || pool_type == PF_POOL_SRCHASH) && + rpool->cur->addr.type != PF_ADDR_TABLE && + rpool->cur->addr.type != PF_ADDR_DYNIFTL)) { + if (prefer_ipv6_nexthop) { + if (rpool->cur->af == AF_INET && (*naf) == AF_INET6) { + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } + wanted_af = rpool->cur->af; + } else { + if (rpool->cur->af != 0 && rpool->cur->af != (*naf)) { + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } } } - switch (rpool->opts & PF_POOL_TYPEMASK) { + switch (pool_type) { case PF_POOL_NONE: pf_addrcpy(naddr, raddr, wanted_af); break; @@ -631,10 +657,22 @@ else rpool->tblidx = (int)arc4random_uniform(cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); + if (prefer_ipv6_nexthop) + wanted_af = AF_INET6; + retry_other_af_random: if (pfr_pool_get(kt, &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, false)) { - reason = PFRES_MAPFAILED; - goto done_pool_mtx; /* unsupported */ + /* Retry with IPv4 nexthop for IPv4 traffic */ + if (prefer_ipv6_nexthop && + wanted_af == AF_INET6 && + (*naf) == AF_INET) { + wanted_af = AF_INET; + goto retry_other_af_random; + } else { + /* no hosts in wanted AF */ + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } } pf_addrcpy(naddr, &rpool->counter, wanted_af); } else if (init_addr != NULL && PF_AZERO(init_addr, @@ -702,10 +740,22 @@ else rpool->tblidx = (int)(hashidx % cnt); memset(&rpool->counter, 0, sizeof(rpool->counter)); + if (prefer_ipv6_nexthop) + wanted_af = AF_INET6; + retry_other_af_srchash: if (pfr_pool_get(kt, &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, false)) { - reason = PFRES_MAPFAILED; - goto done_pool_mtx; /* unsupported */ + /* Retry with IPv4 nexthop for IPv4 traffic */ + if (prefer_ipv6_nexthop && + wanted_af == AF_INET6 && + (*naf) == AF_INET) { + wanted_af = AF_INET; + goto retry_other_af_srchash; + } else { + /* no hosts in wanted AF */ + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } } pf_addrcpy(naddr, &rpool->counter, wanted_af); } else { @@ -718,6 +768,9 @@ { struct pf_kpooladdr *acur = rpool->cur; + retry_other_af_rr: + if (prefer_ipv6_nexthop) + wanted_af = rpool->ipv6_nexthop_af; if (rpool->cur->addr.type == PF_ADDR_TABLE) { if (!pfr_pool_get(rpool->cur->addr.p.tbl, &rpool->tblidx, &rpool->counter, wanted_af, @@ -728,46 +781,55 @@ &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, true)) goto get_addr; - } else if (pf_match_addr(0, raddr, rmask, &rpool->counter, - wanted_af)) + } else if (rpool->cur->af == wanted_af && + pf_match_addr(0, raddr, rmask, &rpool->counter, wanted_af)) goto get_addr; - + if (prefer_ipv6_nexthop && + (*naf) == AF_INET && wanted_af == AF_INET6) { + /* Reset table index when changing wanted AF. */ + rpool->tblidx = -1; + rpool->ipv6_nexthop_af = AF_INET; + goto retry_other_af_rr; + } try_next: + /* Reset prefer-ipv6-nexthop search to IPv6 when iterating pools. */ + rpool->ipv6_nexthop_af = AF_INET6; if (TAILQ_NEXT(rpool->cur, entries) == NULL) rpool->cur = TAILQ_FIRST(&rpool->list); else rpool->cur = TAILQ_NEXT(rpool->cur, entries); + try_next_ipv6_nexthop_rr: + /* Reset table index when iterating pools or changing wanted AF. */ rpool->tblidx = -1; + if (prefer_ipv6_nexthop) + wanted_af = rpool->ipv6_nexthop_af; if (rpool->cur->addr.type == PF_ADDR_TABLE) { - if (pfr_pool_get(rpool->cur->addr.p.tbl, + if (!pfr_pool_get(rpool->cur->addr.p.tbl, &rpool->tblidx, &rpool->counter, wanted_af, NULL, - true)) { - /* table contains no address of type 'wanted_af' */ - if (rpool->cur != acur) - goto try_next; - reason = PFRES_MAPFAILED; - goto done_pool_mtx; - } + true)) + goto get_addr; } else if (rpool->cur->addr.type == PF_ADDR_DYNIFTL) { - if (pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, - &rpool->tblidx, &rpool->counter, wanted_af, - pf_islinklocal, true)) { - /* interface has no address of type 'wanted_af' */ - if (rpool->cur != acur) - goto try_next; - reason = PFRES_MAPFAILED; - goto done_pool_mtx; - } + if (!pfr_pool_get(rpool->cur->addr.p.dyn->pfid_kt, + &rpool->tblidx, &rpool->counter, wanted_af, pf_islinklocal, + true)) + goto get_addr; } else { - raddr = &rpool->cur->addr.v.a.addr; - rmask = &rpool->cur->addr.v.a.mask; - if (rpool->cur->af && rpool->cur->af != wanted_af) { - reason = PFRES_MAPFAILED; - goto done_pool_mtx; + if (rpool->cur->af == wanted_af) { + raddr = &rpool->cur->addr.v.a.addr; + rmask = &rpool->cur->addr.v.a.mask; + pf_addrcpy(&rpool->counter, raddr, wanted_af); + goto get_addr; } - pf_addrcpy(&rpool->counter, raddr, wanted_af); } - + if (prefer_ipv6_nexthop && + (*naf) == AF_INET && wanted_af == AF_INET6) { + rpool->ipv6_nexthop_af = AF_INET; + goto try_next_ipv6_nexthop_rr; + } + if (rpool->cur != acur) + goto try_next; + reason = PFRES_MAPFAILED; + goto done_pool_mtx; get_addr: pf_addrcpy(naddr, &rpool->counter, wanted_af); if (init_addr != NULL && PF_AZERO(init_addr, wanted_af)) @@ -777,9 +839,16 @@ } } + if (wanted_af == 0) { + reason = PFRES_MAPFAILED; + goto done_pool_mtx; + } + if (nkif) *nkif = rpool->cur->kif; + (*naf) = wanted_af; + done_pool_mtx: mtx_unlock(&rpool->mtx); diff --git a/tests/sys/netpfil/pf/route_to.sh b/tests/sys/netpfil/pf/route_to.sh --- a/tests/sys/netpfil/pf/route_to.sh +++ b/tests/sys/netpfil/pf/route_to.sh @@ -28,6 +28,75 @@ common_dir=$(atf_get_srcdir)/../common +# We need to somehow test if the random algorithm of pf_map_addr() is working. +# The table or prefix contains multiple IP next-hop addresses, for each one try +# to establish up to 10 connections. Fail the test if with this many attempts +# the "good" target has not been chosen. However this choice is random, +# the check might still ocasionally fail. +check_random() { + if [ "$1" = "IPv4" ]; then + ping_from="${net_clients_4}.1" + ping_to="${host_server_4}" + else + ping_from="${net_clients_6}::1" + ping_to="${host_server_6}" + fi + good_targets="$2" + bad_targets="$3" + + port=42000 + states=$(mktemp) || exit 1 + for good_target in $good_targets; do + found="no" + for attempt in $(seq 1 10); do + port=$(( port + 1 )) + jexec router pfctl -Fs + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${ping_from} --to ${ping_to} \ + --ping-type=tcp3way --send-sport=${port} + jexec router pfctl -qvvss | normalize_pfctl_s > $states + cat $states + if [ -n "${bad_targets}" ]; then + for bad_target in $bad_targets; do + if grep -qE "route-to: ${bad_target}@" $states; then + atf_fail "Bad target ${bad_target} selected!" + fi + done + fi; + if grep -qE "route-to: ${good_target}@" $states; then + found=yes + break + fi + done + if [ "${found}" = "no" ]; then + atf_fail "Target ${good_target} not selected after ${attempt} attempts!" + fi + done +} + +pf_map_addr_common() +{ + setup_router_server_nat64 + + # Clients will connect from another network behind the router. + # This allows for using multiple source addresses. + jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} + jexec router route add ${net_clients_4}.0/${net_clients_4_mask} ${net_tester_4_host_tester} + + # The servers are reachable over additional IP addresses for + # testing of tables and subnets. The addresses are noncontinougnus + # for pf_map_addr() counter tests. + for i in 0 1 4 5; do + a1=$((24 + i)) + jexec server1 ifconfig ${epair_server1}b inet ${net_server1_4}.${a1}/32 alias + jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:${i}/128 alias + a2=$((40 + i)) + jexec server2 ifconfig ${epair_server2}b inet ${net_server2_4}.${a2}/32 alias + jexec server2 ifconfig ${epair_server2}b inet6 ${net_server2_6}::42:${i}/128 alias + done +} + atf_test_case "v4" "cleanup" v4_head() { @@ -893,36 +962,17 @@ pft_cleanup } - atf_test_case "table_loop" "cleanup" table_loop_head() { atf_set descr 'Check that iterating over tables poperly loops' atf_set require.user root - atf_set require.progs python3 scapy } table_loop_body() { - setup_router_server_nat64 - - # Clients will connect from another network behind the router. - # This allows for using multiple source addresses. - jexec router route add -6 ${net_clients_6}::/${net_clients_6_mask} ${net_tester_6_host_tester} - jexec router route add ${net_clients_4}.0/${net_clients_4_mask} ${net_tester_4_host_tester} - - # The servers are reachable over additional IP addresses for - # testing of tables and subnets. The addresses are noncontinougnus - # for pf_map_addr() counter tests. - for i in 0 1 4 5; do - a1=$((24 + i)) - jexec server1 ifconfig ${epair_server1}b inet ${net_server1_4}.${a1}/32 alias - jexec server1 ifconfig ${epair_server1}b inet6 ${net_server1_6}::42:${i}/128 alias - a2=$((40 + i)) - jexec server2 ifconfig ${epair_server2}b inet ${net_server2_4}.${a2}/32 alias - jexec server2 ifconfig ${epair_server2}b inet6 ${net_server2_6}::42:${i}/128 alias - done + pf_map_addr_common jexec router pfctl -e pft_set_rules router \ @@ -976,6 +1026,612 @@ } +atf_test_case "roundrobin" "cleanup" + +roundrobin_head() +{ + atf_set descr 'multiple gateways of mixed AF, including prefixes and tables, for IPv6 packets' + atf_set require.user root +} + +roundrobin_body() +{ + pf_map_addr_common + + # The rule is defined as "inet6 proto tcp" so directly given IPv4 hosts + # will be removed from the pool by pfctl. Tables will still be loaded + # and pf_map_addr() will only use IPv6 addresses from them. It will + # iterate over members of the pool and inside of tables and prefixes. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_4_host_server}) \ + (${epair_server2}a ) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a ) \ + (${epair_server2}a ) \ + } \ + inet6 proto tcp \ + keep state" + + for port in $(seq 1 6); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4201\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4203\] .* route-to: ${net_server2_6}::42:0@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4204\] .* route-to: ${net_server2_6}::42:1@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4205\] .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4206\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +roundrobin_cleanup() +{ + pft_cleanup +} + +atf_test_case "random_table" "cleanup" + +random_table_head() +{ + atf_set descr 'Pool with random flag and a table for IPv6' + atf_set require.user root +} + +random_table_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from the table but will + # not dive into prefixes, always choosing the 0th address. + # Proper address family will be choosen. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ) } random \ + inet6 proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:4" + bad_targets="${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" "${bad_targets}" +} + +random_table_cleanup() +{ + pft_cleanup +} + +atf_test_case "random_prefix" "cleanup" + +random_prefix_head() +{ + atf_set descr 'Pool with random flag and a table for IPv4' + atf_set require.user root +} + +random_prefix_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from given prefix. + # The choice being random makes testing it non-trivial. We do 10 + # attempts to have each target chosen. Hopefully this is enough to have + # this test pass often enough. + + jexec router pfctl -e + pft_set_rules router \ + "set debug loud" \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random \ + inet6 proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" +} + +random_prefix_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_single_ipv4" "cleanup" + +prefer_ipv6_nexthop_single_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a single IPv4 gateway' + atf_set require.user root +} + +prefer_ipv6_nexthop_single_ipv4_body() +{ + pf_map_addr_common + + # Basic forwarding test for prefer-ipv6-nexthop pool option. + # A single IPv4 gateway will work only for IPv4 traffic. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_4_host_server}) prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_4_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done + + # The IPv6 packet could not have been routed over IPv4 gateway. + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_single_ipv4_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_single_ipv6" "cleanup" + +prefer_ipv6_nexthop_single_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a single IPv6 gateway' + atf_set require.user root +} + +prefer_ipv6_nexthop_single_ipv6_body() +{ + pf_map_addr_common + + # Basic forwarding test for prefer-ipv6-nexthop pool option. + # A single IPve gateway will work both for IPv4 and IPv6 traffic. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to (${epair_server1}a ${net_server1_6_host_server}) prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=4201 \ + + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4202 + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_single_ipv6_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4" "cleanup" + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for multiple gateways of mixed AF with prefixes and tables, round robin selection, for IPv4 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_body() +{ + pf_map_addr_common + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding prefer-ipv6-nexthop + # support, we test for it. Some targets are hosts and some are tables. + # Those are iterated too. Finally the iteration loops back + # to the beginning of the pool. Send out IPv4 probes. They will use all + # IPv4 and IPv6 addresses from the pool. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:4/127 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a ${net_server2_4_host_server}) \ + (${epair_server2}a ) \ + (${epair_server1}a ${net_server1_4}.24/31) \ + (${epair_server2}a ) \ + } prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + for port in $(seq 1 12); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_4}.1 --to ${host_server_4} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4201 .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4202 .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4203 .* route-to: ${net_server2_4_host_server}@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4204 .* route-to: ${net_server1_4}.24@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4205 .* route-to: ${net_server1_4}.25@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4206 .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4207 .* route-to: ${net_server2_6}::42:5@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4208 .* route-to: ${net_server2_4}.40@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4209 .* route-to: ${net_server2_4}.41@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4210 .* route-to: ${net_server2_4}.44@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4211 .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_4}:9 <- ${net_clients_4}.1:4212 .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for multiple gateways of mixed AF with prefixes and tables, round-robin selection, for IPv6 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_body() +{ + pf_map_addr_common + + # The "random" flag will pick random hosts from the table but will + # not dive into prefixes, always choosing the 0th address. + # Proper address family will be choosen. The choice being random makes + # testing it non-trivial. We do 10 attempts to have each target chosen. + # Hopefully this is enough to have this test pass often enough. + + # pf_map_addr() starts iterating over hosts of the pool from the 2nd + # host. This behaviour was here before adding prefer-ipv6-nexthop + # support, we test for it. Some targets are hosts and some are tables. + # Those are iterated too. Finally the iteration loops back + # to the beginning of the pool. Send out IPv6 probes. They will use only + # IPv6 addresses from the pool. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:4/127 }" \ + "pass in on ${epair_tester}b \ + route-to { \ + (${epair_server1}a ${net_server1_6_host_server}) \ + (${epair_server1}a ${net_server1_6}::42:0/127) \ + (${epair_server2}a ${net_server2_4_host_server}) \ + (${epair_server2}a ) \ + (${epair_server1}a ${net_server1_4}.24/31) \ + (${epair_server2}a ) \ + } prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + for port in $(seq 1 6); do + port=$((4200 + port)) + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=${port} + done + + states=$(mktemp) || exit 1 + jexec router pfctl -qvvss | normalize_pfctl_s > $states + + for state_regexp in \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4201\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4202\] .* route-to: ${net_server1_6}::42:1@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4203\] .* route-to: ${net_server2_6}::42:4@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4204\] .* route-to: ${net_server2_6}::42:5@${epair_server2}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4205\] .* route-to: ${net_server1_6_host_server}@${epair_server1}a" \ + "${epair_tester}b tcp ${host_server_6}\[9\] <- ${net_clients_6}::1\[4206\] .* route-to: ${net_server1_6}::42:0@${epair_server1}a" \ + ; do + grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" + done +} + +prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6_cleanup() +{ + pft_cleanup +} + +atf_test_case "prefer_ipv6_nexthop_mixed_af_random_ipv4" "cleanup" + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for a mixed-af table with random selection for IPv4 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_body() +{ + pf_map_addr_common + + # Similarly to the test "random_table" the algorithm will choose + # IP addresses from the table not diving into prefixes. + # *prefer*-ipv6-nexthop means that IPv6 nexthops are preferred, + # so IPv4 ones will not be chosen as long as there are IPv6 ones + # available. With this tested there is no need for a test for IPv6-only + # next-hops table. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 ${net_server2_6}::42:0/127 ${net_server2_6}::42:4 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:4" + bad_targets="${net_server2_4}.40 ${net_server2_4}.41 ${net_server2_4}.44 ${net_server2_6}::42:1" + check_random IPv4 "${good_targets}" "${bad_targets}" +} + +prefer_ipv6_nexthop_mixed_af_random_table_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4-only table with random selection for IPv4 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_body() +{ + pf_map_addr_common + + # Similarly to the test pf_map_addr:random_table the algorithm will + # choose IP addresses from the table not diving into prefixes. + # There are no IPv6 nexthops in the table, so the algorithm will + # fall back to IPv4. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_4}.40 ${net_server2_4}.44" + bad_targets="${net_server2_4}.41" + check_random IPv4 "${good_targets}" "${bad_targets}" +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4-only table with random selection for IPv6 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_body() +{ + pf_map_addr_common + + # IPv6 packets can't be forwarded over IPv4 next-hops. + # The failure happens in pf_map_addr() and increases the respective + # error counter. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "table { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4201 + + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_ipv4_random_table_ipv6_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv6 prefix with random selection for IPv4 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv4 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv6 prefix with random selection for IPv6 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_6}::42:0/127) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_6}::42:0 ${net_server2_6}::42:1" + check_random IPv6 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv6_random_prefix_ipv6_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4 prefix with random selection for IPv4 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_body() +{ + pf_map_addr_common + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_4}.40/31) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + good_targets="${net_server2_4}.40 ${net_server2_4}.41" + check_random IPv4 "${good_targets}" +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv4_cleanup() +{ + pft_cleanup +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_head() +{ + atf_set descr 'prefer-ipv6-nexthop option for an IPv4 prefix with random selection for IPv6 packets' + atf_set require.user root +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_body() +{ + pf_map_addr_common + + # IPv6 packets can't be forwarded over IPv4 next-hops. + # The failure happens in pf_map_addr() and increases the respective + # error counter. + + jexec router pfctl -e + pft_set_rules router \ + "set reassemble yes" \ + "set state-policy if-bound" \ + "pass in on ${epair_tester}b \ + route-to { (${epair_server2}a ${net_server2_4}.40/31) } random prefer-ipv6-nexthop \ + proto tcp \ + keep state" + + atf_check -s exit:1 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a --replyif ${epair_tester}a \ + --fromaddr ${net_clients_6}::1 --to ${host_server_6} \ + --ping-type=tcp3way --send-sport=4201 + + atf_check -o "match:map-failed +1 +" -x "jexec router pfctl -qvvsi 2> /dev/null" +} + +prefer_ipv6_nexthop_ipv4_random_prefix_ipv6_cleanup() +{ + pft_cleanup +} + atf_init_test_cases() { atf_add_test_case "v4" @@ -995,5 +1651,25 @@ atf_add_test_case "sticky" atf_add_test_case "ttl" atf_add_test_case "empty_pool" + # Tests for pf_map_addr() without prefer-ipv6-nexthop atf_add_test_case "table_loop" + atf_add_test_case "roundrobin" + atf_add_test_case "random_table" + atf_add_test_case "random_prefix" + # Tests for pf_map_addr() without prefer-ipv6-nexthop + # Next hop is a Single IP address + atf_add_test_case "prefer_ipv6_nexthop_single_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_single_ipv6" + # Next hop is tables and prefixes, accessed by the round-robin algorithm + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_roundrobin_ipv6" + # Next hop is a table, accessed by the random algorithm + atf_add_test_case "prefer_ipv6_nexthop_mixed_af_random_table_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_table_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_table_ipv6" + # Next hop is a prefix, accessed by the random algorithm + atf_add_test_case "prefer_ipv6_nexthop_ipv6_random_prefix_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv6_random_prefix_ipv6" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_prefix_ipv4" + atf_add_test_case "prefer_ipv6_nexthop_ipv4_random_prefix_ipv6" } diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh --- a/tests/sys/netpfil/pf/src_track.sh +++ b/tests/sys/netpfil/pf/src_track.sh @@ -526,10 +526,12 @@ "block" \ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \ "pass in on ${epair_tester}b \ - route-to { (${epair_server1}a ${net_server1_4_host_server}) \ - } sticky-address \ - inet6 proto tcp from any to 64:ff9b::/96 \ - af-to inet from ${net_clients_4}.0/${net_clients_4_mask} round-robin sticky-address" + route-to { \ + (${epair_server1}a ${net_server1_4_host_server}) \ + (${epair_server2}a ${net_server2_6_host_server}) \ + } prefer-ipv6-nexthop sticky-address \ + inet6 proto tcp from any to 64:ff9b::/96 \ + af-to inet from ${net_clients_4}.0/${net_clients_4_mask} round-robin sticky-address" atf_check -s exit:0 ${common_dir}/pft_ping.py \ --sendif ${epair_tester}a \ @@ -538,6 +540,20 @@ --to 64:ff9b::192.0.2.100 \ --ping-type=tcp3way \ --send-sport=4201 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::1 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4202 + atf_check -s exit:0 ${common_dir}/pft_ping.py \ + --sendif ${epair_tester}a \ + --replyif ${epair_tester}a \ + --fromaddr 2001:db8:44::2 \ + --to 64:ff9b::192.0.2.100 \ + --ping-type=tcp3way \ + --send-sport=4203 states=$(mktemp) || exit 1 jexec router pfctl -qvvss | normalize_pfctl_s > $states @@ -546,16 +562,22 @@ # States are checked for proper route-to information. # The route-to gateway is IPv4. + # FIXME: Sticky-address is broken for af-to pools! + # The SN is created but apparently not used, as seen in states. for state_regexp in \ - "${epair_tester}b tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a" \ + "${epair_tester}b tcp 203.0.113.0:4201 \(2001:db8:44::1\[4201\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \ + "${epair_tester}b tcp 203.0.113.1:4202 \(2001:db8:44::1\[4202\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 2001:db8:4202::2@${epair_server2}a" \ + "${epair_tester}b tcp 203.0.113.2:4203 \(2001:db8:44::2\[4203\]\) -> 192.0.2.100:9 \(64:ff9b::c000:264\[9\]\) .* route-to: 198.51.100.18@${epair_server1}a" \ ; do grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'" done # Source nodes map IPv6 source address onto IPv4 gateway and IPv4 SNAT address. for node_regexp in \ - '2001:db8:44::1 -> 203.0.113.0 .* states 1, .* NAT/RDR sticky-address' \ - '2001:db8:44::1 -> 198.51.100.18 .* states 1, .* route sticky-address' \ + '2001:db8:44::2 -> 203.0.113.2 .* states 1, .* NAT/RDR sticky-address' \ + '2001:db8:44::2 -> 198.51.100.18 .* states 1, .* route sticky-address' \ + '2001:db8:44::1 -> 203.0.113.0 .* states 2, .* NAT/RDR sticky-address' \ + '2001:db8:44::1 -> 2001:db8:4202::2 .* states 2, .* route sticky-address' \ ; do grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'" done diff --git a/tests/sys/netpfil/pf/utils.subr b/tests/sys/netpfil/pf/utils.subr --- a/tests/sys/netpfil/pf/utils.subr +++ b/tests/sys/netpfil/pf/utils.subr @@ -274,8 +274,8 @@ jexec server inetd -p ${PWD}/inetd.pid $inetd_conf } -# Create a router and 2 server jails for nat64 and rfc5549 test cases. -# The router is connected to servers, both are dual-stack, and to the +# Create a router and 2 server jails for nat64 and prefer-ipv6-nexthop test +# cases. The router is connected to servers, both are dual-stack, and to the # tester jail. All links are dual stack. setup_router_server_nat64() {