pf: Add prefer-ipv6-nexthop option for route-to pools

Now that pf is aware of address family of each pool address and source
tracking uses distinct address family for source and redirection
adddresses it is possible to add a new pool option prefer-ipv6-nexthop
which enables routing of IPv4 packets over IPv6 next hops for rules
with the route-to option.

Add a pool option flag PF_POOL_IPV6NH, apply it to pools with a keyword
prefer-ipv6-nexthop.

Modify pf_map_addr() to handle pools with addresses of different
families. Use *naf as a hint about what address family the forwarded
packet is, then pick from the pool addresses of family that can be used
as a next hop for the forwarded packet, controlled by the PF_POOL_IPV6NH
flag. For NAT pools this flag is never set and thus pf_map_addr()
will return an IP address of the same family as the forwarded packet.
For route-to pools when the flag is enabled IPv6 addresses can be
returned or IPv4 packets.

In pf_route() check rt_af, it is not guaranteed to be AF_INET anymore
because pf_map_addr() could have changed it (as *naf).

Add tests for behaviour of pf_map_addr() both with PF_POOL_IPV6NH and
without, for single IP addresses, prefixes and subnets.

Reviewed by:	kp
Sponsored by:	InnoGames GmbH
Differential Revision:	https://reviews.freebsd.org/D50781
This commit is contained in:
Kajetan Staszkiewicz
2025-08-01 21:01:37 +02:00
parent 778bfd4e10
commit 65c3186301
17 changed files with 953 additions and 110 deletions
+32 -18
View File
@@ -238,6 +238,7 @@ static struct pool_opts {
#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 @@ int parseport(char *, struct range *r, int);
%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 <v.string> STRING
%token <v.number> NUMBER
%token <v.i> PORTBINARY
@@ -2648,13 +2649,16 @@ pfrule : action dir logquick interface route af proto fromto
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_opt : USER uids {
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_opt : USER uids {
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_opt : BITMASK {
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 @@ natrule : nataction interface af proto fromto tag tagged rtable
"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 : STRING {
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 @@ lookup(char *s)
{ "pass", PASS},
{ "pflow", PFLOW},
{ "port", PORT},
{ "prefer-ipv6-nexthop", IPV6NH},
{ "prio", PRIO},
{ "priority", PRIORITY},
{ "priq", PRIQ},
+4 -2
View File
@@ -508,6 +508,8 @@ print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, int id)
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 @@ ifa_add_groups_to_map(char *ifa_name)
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 @@ is_a_group(char *name)
{
ENTRY item;
ENTRY *ret_item;
item.key = name;
if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0)
return (0);
+1
View File
@@ -0,0 +1 @@
pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) prefer-ipv6-nexthop inet
+1
View File
@@ -0,0 +1 @@
pass in on vtnet0 route-to (vtnet1 2001:db8::1) prefer-ipv6-nexthop inet all flags S/SA keep state
+1
View File
@@ -0,0 +1 @@
no routing address with matching address family found.
+1
View File
@@ -0,0 +1 @@
pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) inet
+2
View File
@@ -181,3 +181,5 @@ PFCTL_TEST(1069, "max-pkt-size")
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")
+16 -2
View File
@@ -2470,7 +2470,13 @@ NAT address and port.
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 @@ beyond the lifetime of the states, increase the global options with
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 @@ limit-item = ( "states" | "frags" | "src-nodes" ) number
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 ]
+2 -1
View File
@@ -645,6 +645,7 @@ struct pf_kpool {
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 @@ u_short pf_map_addr(sa_family_t, struct pf_krule *,
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 *,
+5 -3
View File
@@ -605,7 +605,8 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
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 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
}
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;
}
+49 -14
View File
@@ -5960,7 +5960,9 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
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 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
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 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
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 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
/* 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_route(struct pf_krule *r, struct ifnet *oifp,
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 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
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 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
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
+1
View File
@@ -131,6 +131,7 @@ enum { PF_ADDR_ADDRMASK, PF_ADDR_NOROUTE, PF_ADDR_DYNIFTL,
#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
+1
View File
@@ -2276,6 +2276,7 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
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++;
+110 -41
View File
@@ -545,11 +545,18 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
} 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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
{
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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
&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 @@ pf_map_addr(sa_family_t saf, struct pf_krule *r, struct pf_addr *saddr,
}
}
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);
+696 -20
View File
@@ -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 @@ empty_pool_cleanup()
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 @@ table_loop_cleanup()
}
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 <rt_targets> { ${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 <rt_targets_empty>) \
(${epair_server1}a ${net_server1_6}::42:0/127) \
(${epair_server2}a <rt_targets_empty>) \
(${epair_server2}a <rt_targets>) \
} \
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 <rt_targets> { ${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 <rt_targets>) } 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 <rt_targets> { ${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 <rt_targets_empty>) \
(${epair_server1}a ${net_server1_4}.24/31) \
(${epair_server2}a <rt_targets>) \
} 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 <rt_targets> { ${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 <rt_targets_empty>) \
(${epair_server1}a ${net_server1_4}.24/31) \
(${epair_server2}a <rt_targets>) \
} 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 <rt_targets> { ${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 <rt_targets>) } 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 <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \
"pass in on ${epair_tester}b \
route-to { (${epair_server2}a <rt_targets>) } 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 <rt_targets> { ${net_server2_4}.40/31 ${net_server2_4}.44 }" \
"pass in on ${epair_tester}b \
route-to { (${epair_server2}a <rt_targets>) } 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_init_test_cases()
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"
}
+29 -7
View File
@@ -526,10 +526,12 @@ mixed_af_body()
"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 @@ mixed_af_body()
--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 @@ mixed_af_body()
# 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
+2 -2
View File
@@ -274,8 +274,8 @@ setup_router_server_ipv6()
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()
{