aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKajetan Staszkiewicz <ks@FreeBSD.org>2025-03-03 16:57:52 +0000
committerKajetan Staszkiewicz <ks@FreeBSD.org>2025-04-28 20:06:08 +0000
commite0fe26691fc98a16cdda9d4f4beea9c5698ac64a (patch)
tree220566da9362328aad6f28b3cc45120aff59fdad
parent74a9aac40e4283503952489645476155a3593954 (diff)
pf: Add modern NAT syntax
Now that pfctl has separate functions for parsing redirection pools and ports, we can finally add support for nat-to and rdr-to filter_opts. NAT and RDR actions are marked by having the respective pools filled in. Function pf_rule_apply_nat() is responsible for both NAT/RDR and af-to address translations. It is called both for match rules and the final pass rule. Use FreeBSD's original address translation code by splitting it into pf_translate_compat(). Call this function for old-style NAT ruleset and for modern NAT rules via pf_rule_apply_nat(). Initialize pfctl_rule's redirection pools on rule allocation, also for code paths not using expand_rule(), so that they can be safely checked for being empty in filter_consistent(). Move map-e NAT test to nat.sh for convenience, duplicate critical NAT tests into _compat (for old-style NAT ruleset) and _pass (for match/ pass) variants. Reviewed by: kp Approved by: kp (mentor) Sponsored by: InnoGames GmbH Differential Revision: https://reviews.freebsd.org/D49221
-rw-r--r--sbin/pfctl/parse.y292
-rw-r--r--sbin/pfctl/pfctl.c1
-rw-r--r--sbin/pfctl/pfctl_parser.c35
-rw-r--r--sbin/pfctl/tests/files/pf0016.in6
-rw-r--r--sbin/pfctl/tests/files/pf0016.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0018.in18
-rw-r--r--sbin/pfctl/tests/files/pf0018.ok19
-rw-r--r--sbin/pfctl/tests/files/pf0019.in4
-rw-r--r--sbin/pfctl/tests/files/pf0019.ok9
-rw-r--r--sbin/pfctl/tests/files/pf0020.in4
-rw-r--r--sbin/pfctl/tests/files/pf0020.ok12
-rw-r--r--sbin/pfctl/tests/files/pf0048.in12
-rw-r--r--sbin/pfctl/tests/files/pf0048.ok8
-rw-r--r--sbin/pfctl/tests/files/pf0069.in3
-rw-r--r--sbin/pfctl/tests/files/pf0069.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0070.in3
-rw-r--r--sbin/pfctl/tests/files/pf0070.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0071.in3
-rw-r--r--sbin/pfctl/tests/files/pf0071.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0072.in3
-rw-r--r--sbin/pfctl/tests/files/pf0072.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0084.in12
-rw-r--r--sbin/pfctl/tests/files/pf0084.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0098.in3
-rw-r--r--sbin/pfctl/tests/files/pf0098.ok1
-rw-r--r--share/man/man5/pf.conf.5285
-rw-r--r--sys/net/pfvar.h7
-rw-r--r--sys/netpfil/pf/pf.c473
-rw-r--r--sys/netpfil/pf/pf_ioctl.c32
-rw-r--r--sys/netpfil/pf/pf_lb.c116
-rw-r--r--tests/sys/netpfil/pf/Makefile1
-rw-r--r--tests/sys/netpfil/pf/map_e.sh90
-rw-r--r--tests/sys/netpfil/pf/nat.sh374
-rw-r--r--tests/sys/netpfil/pf/rdr.sh109
-rwxr-xr-xtests/sys/netpfil/pf/src_track.sh110
35 files changed, 1451 insertions, 606 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index 804d80b04152..f1ed5444cadd 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -245,6 +245,7 @@ struct redirspec {
struct range rport;
struct pool_opts pool_opts;
int af;
+ bool binat;
};
static struct filter_opts {
@@ -381,7 +382,11 @@ void expand_eth_rule(struct pfctl_eth_rule *,
int apply_rdr_ports(struct pfctl_rule *r, struct pfctl_pool *, struct redirspec *);
int apply_nat_ports(struct pfctl_pool *, struct redirspec *);
int apply_redirspec(struct pfctl_pool *, struct redirspec *);
-void expand_rule(struct pfctl_rule *, struct node_if *,
+int check_binat_redirspec(struct node_host *, struct pfctl_rule *, int);
+void add_binat_rdr_rule(struct pfctl_rule *, struct redirspec *,
+ struct node_host *, struct pfctl_rule *, struct redirspec **,
+ struct node_host **);
+void expand_rule(struct pfctl_rule *, bool, struct node_if *,
struct redirspec *, struct redirspec *, struct redirspec *,
struct node_proto *, struct node_os *, struct node_host *,
struct node_port *, struct node_host *, struct node_port *,
@@ -525,7 +530,8 @@ int parseport(char *, struct range *r, int);
%token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
%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
+%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO
+%token BINATTO
%token <v.string> STRING
%token <v.number> NUMBER
%token <v.i> PORTBINARY
@@ -1080,7 +1086,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
decide_address_family($8.src.host, &r.af);
decide_address_family($8.dst.host, &r.af);
- expand_rule(&r, $5, NULL, NULL, NULL,
+ expand_rule(&r, false, $5, NULL, NULL, NULL,
$7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host,
$8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec,
pf->astack[pf->asd + 1] ? pf->alast->name : $2);
@@ -1104,7 +1110,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
decide_address_family($6.src.host, &r.af);
decide_address_family($6.dst.host, &r.af);
- expand_rule(&r, $3, NULL, NULL, NULL,
+ expand_rule(&r, false, $3, NULL, NULL, NULL,
$5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host,
$6.dst.port, 0, 0, 0, 0, $2);
free($2);
@@ -1147,7 +1153,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
r.dst.port_op = $6.dst.port->op;
}
- expand_rule(&r, $3, NULL, NULL, NULL,
+ expand_rule(&r, false, $3, NULL, NULL, NULL,
$5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host,
$6.dst.port, 0, 0, 0, 0, $2);
free($2);
@@ -1471,7 +1477,7 @@ scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts
r.match_tag_not = $8.match_tag_not;
r.rtableid = $8.rtableid;
- expand_rule(&r, $4, NULL, NULL, NULL,
+ expand_rule(&r, false, $4, NULL, NULL, NULL,
$6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host,
$7.dst.port, NULL, NULL, NULL, NULL, "");
}
@@ -1636,9 +1642,9 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
}
if (h != NULL)
- expand_rule(&r, j, NULL, NULL, NULL,
- NULL, NULL, h, NULL, NULL, NULL,
- NULL, NULL, NULL, NULL, "");
+ expand_rule(&r, false, j, NULL, NULL,
+ NULL, NULL, NULL, h, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, "");
if ((i->ifa_flags & IFF_LOOPBACK) == 0) {
bzero(&r, sizeof(r));
@@ -1658,10 +1664,10 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
else
h = ifa_lookup(i->ifname, 0);
if (h != NULL)
- expand_rule(&r, NULL, NULL,
- NULL, NULL, NULL, NULL, h,
+ expand_rule(&r, false, NULL,
NULL, NULL, NULL, NULL,
- NULL, NULL, NULL, "");
+ NULL, h, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, "");
} else
free(hh);
}
@@ -2797,9 +2803,36 @@ pfrule : action dir logquick interface route af proto fromto
if ($9.marker & FOM_AFTO) {
r.naf = $9.nat->af;
+ } else {
+ if ($9.nat) {
+ if (!r.af && ! $9.nat->host->ifindex)
+ r.af = $9.nat->host->af;
+ remove_invalid_hosts(&($9.nat->host), &r.af);
+ if (invalid_redirect($9.nat->host, r.af))
+ YYERROR;
+ if ($9.nat->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9.nat->host = gen_dynnode($9.nat->host, r.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9.nat->host, r.af))
+ YYERROR;
+ }
+ if ($9.rdr) {
+ if (!r.af && ! $9.rdr->host->ifindex)
+ r.af = $9.rdr->host->af;
+ remove_invalid_hosts(&($9.rdr->host), &r.af);
+ if (invalid_redirect($9.rdr->host, r.af))
+ YYERROR;
+ if ($9.rdr->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9.rdr->host = gen_dynnode($9.rdr->host, r.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9.rdr->host, r.af))
+ YYERROR;
+ }
}
- expand_rule(&r, $4, $9.nat, $9.rdr, $5.redirspec,
+ expand_rule(&r, false, $4, $9.nat, $9.rdr, $5.redirspec,
$7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host,
$8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec, "");
}
@@ -3016,6 +3049,29 @@ filter_opt : USER uids {
filter_opts.marker |= FOM_SCRUB_TCP;
filter_opts.marker |= $3.marker;
}
+ | NATTO port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify nat-to/binat-to");
+ YYERROR;
+ }
+ filter_opts.nat = $2;
+ }
+ | RDRTO port_redirspec {
+ if (filter_opts.rdr) {
+ yyerror("cannot respecify rdr-to");
+ YYERROR;
+ }
+ filter_opts.rdr = $2;
+ }
+ | BINATTO port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify nat-to/binat-to");
+ YYERROR;
+ }
+ filter_opts.nat = $2;
+ filter_opts.nat->binat = 1;
+ filter_opts.nat->pool_opts.staticport = 1;
+ }
| AFTO af FROM port_redirspec {
if (filter_opts.nat) {
yyerror("cannot respecify af-to");
@@ -4859,7 +4915,7 @@ natrule : nataction interface af proto fromto tag tagged rtable
o = o->next;
}
- expand_rule(&r, $2, NULL, $9, NULL, $4,
+ expand_rule(&r, false, $2, NULL, $9, NULL, $4,
$5.src_os, $5.src.host, $5.src.port, $5.dst.host,
$5.dst.port, 0, 0, 0, 0, "");
}
@@ -5028,8 +5084,6 @@ binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag
YYERROR;
}
- TAILQ_INIT(&binat.rdr.list);
- TAILQ_INIT(&binat.nat.list);
pa = calloc(1, sizeof(struct pf_pooladdr));
if (pa == NULL)
err(1, "binat: calloc");
@@ -5389,9 +5443,18 @@ filter_consistent(struct pfctl_rule *r, int anchor_call)
break;
default:;
}
- if (r->rdr.opts & PF_POOL_STICKYADDR && !r->keep_state) {
+ if (!TAILQ_EMPTY(&(r->nat.list)) || !TAILQ_EMPTY(&(r->rdr.list))) {
+ if (r->action != PF_MATCH && !r->keep_state) {
+ yyerror("nat-to and rdr-to require keep state");
+ problems++;
+ }
+ if (r->direction == PF_INOUT) {
+ yyerror("nat-to and rdr-to require a direction");
+ problems++;
+ }
+ }
+ if (r->route.opts & PF_POOL_STICKYADDR && !r->keep_state) {
yyerror("'sticky-address' requires 'keep state'");
- problems++;
}
return (-problems);
}
@@ -6135,7 +6198,6 @@ apply_redirspec(struct pfctl_pool *rpool, struct redirspec *rs)
memcpy(&(rpool->key), rs->pool_opts.key,
sizeof(struct pf_poolhashkey));
- TAILQ_INIT(&(rpool->list));
for (h = rs->host; h != NULL; h = h->next) {
pa = calloc(1, sizeof(struct pf_pooladdr));
if (pa == NULL)
@@ -6153,8 +6215,115 @@ apply_redirspec(struct pfctl_pool *rpool, struct redirspec *rs)
return 0;
}
+int
+check_binat_redirspec(struct node_host *src_host, struct pfctl_rule *r, int af)
+{
+ struct pf_pooladdr *nat_pool = TAILQ_FIRST(&(r->nat.list));
+ int error = 0;
+
+ /* XXX: FreeBSD allows syntax like "{ host1 host2 }" for redirection
+ * pools but does not covert them to tables automatically, because
+ * syntax "{ (iface1 host1), (iface2 iface2) }" is allowed for route-to
+ * redirection. Add a FreeBSD-specific guard against using multiple
+ * hosts for source and redirection.
+ */
+ if (src_host->next) {
+ yyerror("invalid use of table as the source address "
+ "of a binat-to rule");
+ error++;
+ }
+ if (TAILQ_NEXT(nat_pool, entries)) {
+ yyerror ("tables cannot be used as the redirect "
+ "address of a binat-to rule");
+ error++;
+ }
+
+ if (disallow_table(src_host, "invalid use of table "
+ "<%s> as the source address of a binat-to rule") ||
+ disallow_alias(src_host, "invalid use of interface "
+ "(%s) as the source address of a binat-to rule")) {
+ error++;
+ } else if ((r->src.addr.type != PF_ADDR_ADDRMASK &&
+ r->src.addr.type != PF_ADDR_DYNIFTL) ||
+ (nat_pool->addr.type != PF_ADDR_ADDRMASK &&
+ nat_pool->addr.type != PF_ADDR_DYNIFTL)) {
+ yyerror("binat-to requires a specified "
+ "source and redirect address");
+ error++;
+ }
+ if (DYNIF_MULTIADDR(r->src.addr) ||
+ DYNIF_MULTIADDR(nat_pool->addr)) {
+ yyerror ("dynamic interfaces must be "
+ "used with:0 in a binat-to rule");
+ error++;
+ }
+ if (PF_AZERO(&r->src.addr.v.a.mask, af) ||
+ PF_AZERO(&(nat_pool->addr.v.a.mask), af)) {
+ yyerror ("source and redir addresess must have "
+ "a matching network mask in binat-rule");
+ error++;
+ }
+ if (nat_pool->addr.type == PF_ADDR_TABLE) {
+ yyerror ("tables cannot be used as the redirect "
+ "address of a binat-to rule");
+ error++;
+ }
+ if (r->direction != PF_INOUT) {
+ yyerror("binat-to cannot be specified "
+ "with a direction");
+ error++;
+ }
+
+ /* first specify outbound NAT rule */
+ r->direction = PF_OUT;
+
+ return (error);
+}
+
+void
+add_binat_rdr_rule(
+ struct pfctl_rule *binat_rule,
+ struct redirspec *binat_nat_redirspec, struct node_host *binat_src_host,
+ struct pfctl_rule *rdr_rule, struct redirspec **rdr_redirspec,
+ struct node_host **rdr_dst_host)
+{
+ struct node_host *rdr_src_host;
+
+ /*
+ * We're copying the whole rule, but we must re-init redir pools.
+ * FreeBSD uses lists of pf_pooladdr, we can't just overwrite them.
+ */
+ bcopy(binat_rule, rdr_rule, sizeof(struct pfctl_rule));
+ TAILQ_INIT(&(rdr_rule->rdr.list));
+ TAILQ_INIT(&(rdr_rule->nat.list));
+
+ /* now specify inbound rdr rule */
+ rdr_rule->direction = PF_IN;
+
+ if ((rdr_src_host = calloc(1, sizeof(*rdr_src_host))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(binat_src_host, rdr_src_host, sizeof(*rdr_src_host));
+ rdr_src_host->ifname = NULL;
+ rdr_src_host->next = NULL;
+ rdr_src_host->tail = NULL;
+
+ if (((*rdr_dst_host) = calloc(1, sizeof(**rdr_dst_host))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(&(binat_nat_redirspec->host->addr), &((*rdr_dst_host)->addr),
+ sizeof((*rdr_dst_host)->addr));
+ (*rdr_dst_host)->ifname = NULL;
+ (*rdr_dst_host)->next = NULL;
+ (*rdr_dst_host)->tail = NULL;
+
+ if (((*rdr_redirspec) = calloc(1, sizeof(**rdr_redirspec))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(binat_nat_redirspec, (*rdr_redirspec), sizeof(**rdr_redirspec));
+ (*rdr_redirspec)->pool_opts.staticport = 0;
+ (*rdr_redirspec)->host = rdr_src_host;
+}
+
void
-expand_rule(struct pfctl_rule *r,
+expand_rule(struct pfctl_rule *r, bool keeprule,
struct node_if *interfaces, struct redirspec *nat,
struct redirspec *rdr, struct redirspec *route,
struct node_proto *protos,
@@ -6308,17 +6477,25 @@ expand_rule(struct pfctl_rule *r,
}
if (r->action == PF_RDR) {
+ /* Pre-FreeBSD 15 "rdr" rule */
error += apply_rdr_ports(r, &(r->rdr), rdr);
+ error += apply_redirspec(&(r->rdr), rdr);
} else if (r->action == PF_NAT) {
+ /* Pre-FreeBSD 15 "nat" rule */
error += apply_nat_ports(&(r->rdr), rdr);
- }
+ error += apply_redirspec(&(r->rdr), rdr);
+ } else {
+ /* Modern rule with optional NAT, BINAT, RDR or ROUTE*/
+ error += apply_redirspec(&(r->route), route);
- error += apply_redirspec(&(r->nat), nat);
- error += apply_redirspec(&(r->rdr), rdr);
- error += apply_redirspec(&(r->route), route);
+ error += apply_nat_ports(&(r->nat), nat);
+ error += apply_redirspec(&(r->nat), nat);
+ error += apply_rdr_ports(r, &(r->rdr), rdr);
+ error += apply_redirspec(&(r->rdr), rdr);
- r->nat.proxy_port[0] = PF_NAT_PROXY_PORT_LOW;
- r->nat.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH;
+ if (nat && nat->binat)
+ error += check_binat_redirspec(src_host, r, af);
+ }
if (rule_consistent(r, anchor_call[0]) < 0 || error)
yyerror("skipping rule due to errors");
@@ -6328,6 +6505,22 @@ expand_rule(struct pfctl_rule *r,
added++;
}
+ /* Generate binat's matching inbound rule */
+ if (!error && nat && nat->binat) {
+ struct pfctl_rule rdr_rule;
+ struct redirspec *rdr_redirspec;
+ struct node_host *rdr_dst_host;
+
+ add_binat_rdr_rule(
+ r, nat, src_hosts,
+ &rdr_rule, &rdr_redirspec, &rdr_dst_host);
+
+ expand_rule(&rdr_rule, true, interface, NULL, rdr_redirspec,
+ NULL, proto, src_os, dst_host, dst_port,
+ rdr_dst_host, src_port, uid, gid, rcv, icmp_type,
+ "");
+ }
+
if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) {
free(src_host);
src_host = osrch;
@@ -6339,27 +6532,29 @@ expand_rule(struct pfctl_rule *r,
))))))))));
- FREE_LIST(struct node_if, interfaces);
- FREE_LIST(struct node_proto, protos);
- FREE_LIST(struct node_host, src_hosts);
- FREE_LIST(struct node_port, src_ports);
- FREE_LIST(struct node_os, src_oses);
- FREE_LIST(struct node_host, dst_hosts);
- FREE_LIST(struct node_port, dst_ports);
- FREE_LIST(struct node_uid, uids);
- FREE_LIST(struct node_gid, gids);
- FREE_LIST(struct node_icmp, icmp_types);
- if (nat) {
- FREE_LIST(struct node_host, nat->host);
- free(nat);
- }
- if (rdr) {
- FREE_LIST(struct node_host, rdr->host);
- free(rdr);
- }
- if (route) {
- FREE_LIST(struct node_host, route->host);
- free(route);
+ if (!keeprule) {
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_proto, protos);
+ FREE_LIST(struct node_host, src_hosts);
+ FREE_LIST(struct node_port, src_ports);
+ FREE_LIST(struct node_os, src_oses);
+ FREE_LIST(struct node_host, dst_hosts);
+ FREE_LIST(struct node_port, dst_ports);
+ FREE_LIST(struct node_uid, uids);
+ FREE_LIST(struct node_gid, gids);
+ FREE_LIST(struct node_icmp, icmp_types);
+ if (nat) {
+ FREE_LIST(struct node_host, nat->host);
+ free(nat);
+ }
+ if (rdr) {
+ FREE_LIST(struct node_host, rdr->host);
+ free(rdr);
+ }
+ if (route) {
+ FREE_LIST(struct node_host, route->host);
+ free(route);
+ }
}
if (!added)
@@ -6445,6 +6640,7 @@ lookup(char *s)
{ "bandwidth", BANDWIDTH},
{ "binat", BINAT},
{ "binat-anchor", BINATANCHOR},
+ { "binat-to", BINATTO},
{ "bitmask", BITMASK},
{ "block", BLOCK},
{ "block-policy", BLOCKPOLICY},
@@ -6508,6 +6704,7 @@ lookup(char *s)
{ "modulate", MODULATE},
{ "nat", NAT},
{ "nat-anchor", NATANCHOR},
+ { "nat-to", NATTO},
{ "no", NO},
{ "no-df", NODF},
{ "no-route", NOROUTE},
@@ -6532,6 +6729,7 @@ lookup(char *s)
{ "random-id", RANDOMID},
{ "rdr", RDR},
{ "rdr-anchor", RDRANCHOR},
+ { "rdr-to", RDRTO},
{ "realtime", REALTIME},
{ "reassemble", REASSEMBLE},
{ "received-on", RECEIVEDON},
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index a9fc33525dd6..6e0be926eff0 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1739,7 +1739,6 @@ pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af, int which
void
pfctl_init_rule(struct pfctl_rule *r)
{
-
memset(r, 0, sizeof(struct pfctl_rule));
TAILQ_INIT(&(r->rdr.list));
TAILQ_INIT(&(r->nat.list));
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
index eb3a0826578e..8a64578b136d 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -1240,25 +1240,34 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
}
#endif
}
- if (!anchor_call[0] && ! TAILQ_EMPTY(&r->nat.list) &&
- r->rule_flag & PFRULE_AFTO) {
- printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6");
- print_pool(&r->nat, r->nat.proxy_port[0], r->nat.proxy_port[1],
- r->naf ? r->naf : r->af, PF_NAT);
+ if (anchor_call[0])
+ return;
+ if (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR) {
+ printf(" -> ");
+ print_pool(&r->rdr, r->rdr.proxy_port[0],
+ r->rdr.proxy_port[1], r->af, r->action);
+ } else {
+ if (!TAILQ_EMPTY(&r->nat.list)) {
+ if (r->rule_flag & PFRULE_AFTO) {
+ printf(" af-to %s from ", r->naf == AF_INET ? "inet" : "inet6");
+ } else {
+ printf(" nat-to ");
+ }
+ print_pool(&r->nat, r->nat.proxy_port[0],
+ r->nat.proxy_port[1], r->naf ? r->naf : r->af,
+ PF_NAT);
+ }
if (!TAILQ_EMPTY(&r->rdr.list)) {
- printf(" to ");
+ if (r->rule_flag & PFRULE_AFTO) {
+ printf(" to ");
+ } else {
+ printf(" rdr-to ");
+ }
print_pool(&r->rdr, r->rdr.proxy_port[0],
r->rdr.proxy_port[1], r->naf ? r->naf : r->af,
PF_RDR);
}
}
- if (!anchor_call[0] &&
- (r->action == PF_NAT || r->action == PF_BINAT ||
- r->action == PF_RDR)) {
- printf(" -> ");
- print_pool(&r->rdr, r->rdr.proxy_port[0],
- r->rdr.proxy_port[1], r->af, r->action);
- }
}
void
diff --git a/sbin/pfctl/tests/files/pf0016.in b/sbin/pfctl/tests/files/pf0016.in
index 738bfb664395..7dbc53aa6a21 100644
--- a/sbin/pfctl/tests/files/pf0016.in
+++ b/sbin/pfctl/tests/files/pf0016.in
@@ -1,5 +1,5 @@
# Test rule order processing: should fail unless nat -> filter
-#match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
-#match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
-#match on lo0 from 192.168.1.1 to any binat-to 10.0.0.1
+match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
+match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
+match on lo0 from 192.168.1.1 to any binat-to 10.0.0.1
pass in on lo1000000 from any to any no state
diff --git a/sbin/pfctl/tests/files/pf0016.ok b/sbin/pfctl/tests/files/pf0016.ok
index 6f0c211a5b8a..d65374a16475 100644
--- a/sbin/pfctl/tests/files/pf0016.ok
+++ b/sbin/pfctl/tests/files/pf0016.ok
@@ -1 +1,5 @@
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1
+match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1 static-port
+match in on lo0 inet from any to 10.0.0.1 rdr-to 192.168.1.1
pass in on lo1000000 all no state
diff --git a/sbin/pfctl/tests/files/pf0018.in b/sbin/pfctl/tests/files/pf0018.in
index 46606b476d79..ab3c81f86c5f 100644
--- a/sbin/pfctl/tests/files/pf0018.in
+++ b/sbin/pfctl/tests/files/pf0018.in
@@ -3,17 +3,17 @@
TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }"
TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }"
-#match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
-#match out on lo0 proto tcp from 192.168.1.2 to any nat-to 10.0.0.2
-#match out on lo0 proto udp from 192.168.1.3 to any nat-to 10.0.0.3
-#match out on lo0 proto icmp from 192.168.1.4 to any nat-to 10.0.0.4
+match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
+match out on lo0 proto tcp from 192.168.1.2 to any nat-to 10.0.0.2
+match out on lo0 proto udp from 192.168.1.3 to any nat-to 10.0.0.3
+match out on lo0 proto icmp from 192.168.1.4 to any nat-to 10.0.0.4
-#match out on lo0 inet from $TEST_LIST1 to $TEST_LIST2 nat-to lo0
+match out on lo0 inet from $TEST_LIST1 to $TEST_LIST2 nat-to lo0
-#match out on lo0 inet from 192.168.0.1/24 to any nat-to (lo0)
+match out on lo0 inet from 192.168.0.1/24 to any nat-to (lo0)
-#match out on lo0 from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8
+match out on lo0 from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8
-#match out on ! lo0 proto { udp, tcp } from any to any nat-to 10.0.0.8 static-port
+match out on ! lo0 proto { udp, tcp } from any to any nat-to 10.0.0.8 static-port
-#match out on { lo0, tun1000000 } from any to any nat-to 10.0.0.8
+match out on { lo0, tun1000000 } from any to any nat-to 10.0.0.8
diff --git a/sbin/pfctl/tests/files/pf0018.ok b/sbin/pfctl/tests/files/pf0018.ok
index c19ead6da1f0..6ba137ae84f8 100644
--- a/sbin/pfctl/tests/files/pf0018.ok
+++ b/sbin/pfctl/tests/files/pf0018.ok
@@ -1,2 +1,21 @@
TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }"
TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }"
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1
+match out on lo0 inet proto tcp from 192.168.1.2 to any nat-to 10.0.0.2
+match out on lo0 inet proto udp from 192.168.1.3 to any nat-to 10.0.0.3
+match out on lo0 inet proto icmp from 192.168.1.4 to any nat-to 10.0.0.4
+match out on lo0 inet from 192.168.1.5 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.5 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.5 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.0.0/24 to any nat-to (lo0) round-robin
+match out on lo0 inet from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8
+match out on ! lo0 inet proto udp all nat-to 10.0.0.8 static-port
+match out on ! lo0 inet proto tcp all nat-to 10.0.0.8 static-port
+match out on lo0 inet all nat-to 10.0.0.8
+match out on tun1000000 inet all nat-to 10.0.0.8
diff --git a/sbin/pfctl/tests/files/pf0019.in b/sbin/pfctl/tests/files/pf0019.in
index 0b1456e6fd03..e2bedbb64bd0 100644
--- a/sbin/pfctl/tests/files/pf0019.in
+++ b/sbin/pfctl/tests/files/pf0019.in
@@ -3,7 +3,7 @@ GOOD = "{ lo0, lo1000000 }"
GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
-#match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
+match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
# Test list processing
-#match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
+match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0019.ok b/sbin/pfctl/tests/files/pf0019.ok
index 16c845aa2cd6..a5afc374d19f 100644
--- a/sbin/pfctl/tests/files/pf0019.ok
+++ b/sbin/pfctl/tests/files/pf0019.ok
@@ -2,3 +2,12 @@ EVIL = "lo0"
GOOD = "{ lo0, lo1000000 }"
GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0020.in b/sbin/pfctl/tests/files/pf0020.in
index b00125bbcdb8..c973785bc9c5 100644
--- a/sbin/pfctl/tests/files/pf0020.in
+++ b/sbin/pfctl/tests/files/pf0020.in
@@ -5,5 +5,5 @@ GOOD = "{ lo0, lo1000000 }"
GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
-#match out on $EVIL inet from $GOOD_NET to $DEST_NET nat-to $EVIL
-#match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
+match out on $EVIL inet from $GOOD_NET to $DEST_NET nat-to $EVIL
+match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0020.ok b/sbin/pfctl/tests/files/pf0020.ok
index 16c845aa2cd6..bd2c6cf2055d 100644
--- a/sbin/pfctl/tests/files/pf0020.ok
+++ b/sbin/pfctl/tests/files/pf0020.ok
@@ -2,3 +2,15 @@ EVIL = "lo0"
GOOD = "{ lo0, lo1000000 }"
GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+match out on lo0 inet from 127.0.0.0/24 to 1.2.3.0/25 nat-to 127.0.0.1
+match out on lo0 inet from 127.0.0.0/24 to 2.4.6.8/30 nat-to 127.0.0.1
+match out on lo0 inet from 10.0.1.0/24 to 1.2.3.0/25 nat-to 127.0.0.1
+match out on lo0 inet from 10.0.1.0/24 to 2.4.6.8/30 nat-to 127.0.0.1
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0048.in b/sbin/pfctl/tests/files/pf0048.in
index e97a819de945..a0dd143c8dd2 100644
--- a/sbin/pfctl/tests/files/pf0048.in
+++ b/sbin/pfctl/tests/files/pf0048.in
@@ -1,12 +1,12 @@
table < regress > { 1.2.3.4 !5.6.7.8 10/8 lo0 }
table <regress.1> const { ::1 fe80::/64 }
table <regress.a> { 1.2.3.4 !5.6.7.8 } { ::1 ::2 ::3 } file "/dev/null" const { 4.3.2.1 }
-#match out on lo0 inet from < regress.1> to <regress.2> nat-to lo0:0
-#match out on !lo0 inet from !<regress.1 > to <regress.2> nat-to lo0:0
-#match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to lo0:0
-#match in on !lo0 inet6 from !< regress.1 > to <regress.2> rdr-to lo0:0
-#match in from { <regress.1> !<regress.2> } to any
-#match out from any to { !<regress.1>, <regress.2> }
+match out on lo0 inet from < regress.1> to <regress.2> nat-to lo0:0
+match out on !lo0 inet from !<regress.1 > to <regress.2> nat-to lo0:0
+match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to lo0:0
+match in on !lo0 inet6 from !< regress.1 > to <regress.2> rdr-to lo0:0
+match in from { <regress.1> !<regress.2> } to any
+match out from any to { !<regress.1>, <regress.2> }
pass in from <regress> to any
pass out from any to <regress >
pass in from { <regress.1> <regress.2> } to any
diff --git a/sbin/pfctl/tests/files/pf0048.ok b/sbin/pfctl/tests/files/pf0048.ok
index f3536f566d35..89569fb4f8ba 100644
--- a/sbin/pfctl/tests/files/pf0048.ok
+++ b/sbin/pfctl/tests/files/pf0048.ok
@@ -1,6 +1,14 @@
table <regress> { 1.2.3.4 !5.6.7.8 10.0.0.0/8 ::1 fe80::1 127.0.0.1 }
table <regress.1> const { ::1 fe80::/64 }
table <regress.a> const { 1.2.3.4 !5.6.7.8 ::1 ::2 ::3 } file "/dev/null" { 4.3.2.1 }
+match out on lo0 inet from <regress.1> to <regress.2> nat-to 127.0.0.1
+match out on ! lo0 inet from ! <regress.1> to <regress.2> nat-to 127.0.0.1
+match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to ::1
+match in on ! lo0 inet6 from ! <regress.1> to <regress.2> rdr-to ::1
+match in from <regress.1> to any
+match in from ! <regress.2> to any
+match out from any to ! <regress.1>
+match out from any to <regress.2>
pass in from <regress> to any flags S/SA keep state
pass out from any to <regress> flags S/SA keep state
pass in from <regress.1> to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0069.in b/sbin/pfctl/tests/files/pf0069.in
index 1298954bbeda..85847b9bd6b2 100644
--- a/sbin/pfctl/tests/files/pf0069.in
+++ b/sbin/pfctl/tests/files/pf0069.in
@@ -1,3 +1,2 @@
-#match out on lo0 inet all tag regress nat-to lo0
+match out on lo0 inet all tag regress nat-to lo0
pass out quick on lo0 keep state tagged regress
-
diff --git a/sbin/pfctl/tests/files/pf0069.ok b/sbin/pfctl/tests/files/pf0069.ok
index 33e0519645fc..2bf34c04baa7 100644
--- a/sbin/pfctl/tests/files/pf0069.ok
+++ b/sbin/pfctl/tests/files/pf0069.ok
@@ -1 +1,2 @@
+match out on lo0 inet all tag regress nat-to 127.0.0.1
pass out quick on lo0 all flags S/SA keep state tagged regress
diff --git a/sbin/pfctl/tests/files/pf0070.in b/sbin/pfctl/tests/files/pf0070.in
index 8d5e34a13ff8..1ccec9302436 100644
--- a/sbin/pfctl/tests/files/pf0070.in
+++ b/sbin/pfctl/tests/files/pf0070.in
@@ -1,3 +1,2 @@
-#match out on lo0 from 10.0.0.0/8 to any nat-to lo0
+match out on lo0 from 10.0.0.0/8 to any nat-to lo0
block out on lo0 tagged regress
-
diff --git a/sbin/pfctl/tests/files/pf0070.ok b/sbin/pfctl/tests/files/pf0070.ok
index d30b70ff3e5a..cf79485b40c1 100644
--- a/sbin/pfctl/tests/files/pf0070.ok
+++ b/sbin/pfctl/tests/files/pf0070.ok
@@ -1 +1,2 @@
+match out on lo0 inet from 10.0.0.0/8 to any nat-to 127.0.0.1
block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0071.in b/sbin/pfctl/tests/files/pf0071.in
index 48976b61ed3d..8975a8ebc943 100644
--- a/sbin/pfctl/tests/files/pf0071.in
+++ b/sbin/pfctl/tests/files/pf0071.in
@@ -1,3 +1,2 @@
-#match in on lo0 proto tcp from 10.0.0.0/8 to port 80 rdr-to lo0
+match in on lo0 proto tcp from 10.0.0.0/8 to port 80 rdr-to lo0
block out on lo0 tagged regress
-
diff --git a/sbin/pfctl/tests/files/pf0071.ok b/sbin/pfctl/tests/files/pf0071.ok
index d30b70ff3e5a..2bae94fc8fac 100644
--- a/sbin/pfctl/tests/files/pf0071.ok
+++ b/sbin/pfctl/tests/files/pf0071.ok
@@ -1 +1,2 @@
+match in on lo0 inet proto tcp from 10.0.0.0/8 to any port = http rdr-to 127.0.0.1
block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0072.in b/sbin/pfctl/tests/files/pf0072.in
index fd037f31ef27..d23843b799d5 100644
--- a/sbin/pfctl/tests/files/pf0072.in
+++ b/sbin/pfctl/tests/files/pf0072.in
@@ -1,4 +1,3 @@
# test binat tagging
-#match on lo0 from 192.168.1.1 to any tag regress binat-to 10.0.0.1
+match on lo0 from 192.168.1.1 to any tag regress binat-to 10.0.0.1
block out on lo0 tagged regress
-
diff --git a/sbin/pfctl/tests/files/pf0072.ok b/sbin/pfctl/tests/files/pf0072.ok
index d30b70ff3e5a..02e676dadc06 100644
--- a/sbin/pfctl/tests/files/pf0072.ok
+++ b/sbin/pfctl/tests/files/pf0072.ok
@@ -1 +1,3 @@
+match out on lo0 inet from 192.168.1.1 to any tag regress nat-to 10.0.0.1 static-port
+match in on lo0 inet from any to 10.0.0.1 tag regress rdr-to 192.168.1.1
block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0084.in b/sbin/pfctl/tests/files/pf0084.in
index c0390df889e3..17140a786d73 100644
--- a/sbin/pfctl/tests/files/pf0084.in
+++ b/sbin/pfctl/tests/files/pf0084.in
@@ -1,9 +1,9 @@
-#match out on tun1000000 from 10.0.0.0/24 to any \
-# nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address
-#match in on tun1000000 from any to 10.0.1.1 \
-# rdr-to { 10.0.0.0/24 } sticky-address random
-#match in on tun1000000 from any to 10.0.1.2 \
-# rdr-to { 10.0.0.1, 10.0.0.2 } sticky-address
+match out on tun1000000 from 10.0.0.0/24 to any \
+ nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address
+match in on tun1000000 from any to 10.0.1.1 \
+ rdr-to { 10.0.0.0/24 } sticky-address random
+match in on tun1000000 from any to 10.0.1.2 \
+ rdr-to { 10.0.0.1, 10.0.0.2 } sticky-address
pass in proto tcp from any to any port 22 \
keep state (source-track)
diff --git a/sbin/pfctl/tests/files/pf0084.ok b/sbin/pfctl/tests/files/pf0084.ok
index 272fd6052023..1ca89e515a3d 100644
--- a/sbin/pfctl/tests/files/pf0084.ok
+++ b/sbin/pfctl/tests/files/pf0084.ok
@@ -1,3 +1,6 @@
+match out on tun1000000 inet from 10.0.0.0/24 to any nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address
+match in on tun1000000 inet from any to 10.0.1.1 rdr-to 10.0.0.0/24 random sticky-address
+match in on tun1000000 inet from any to 10.0.1.2 rdr-to { 10.0.0.1, 10.0.0.2 } round-robin sticky-address
pass in proto tcp from any to any port = ssh flags S/SA keep state (source-track global)
pass in proto tcp from any to any port = smtp flags S/SA keep state (source-track global)
pass in proto tcp from any to any port = http flags S/SA keep state (source-track rule, max-src-states 3, max-src-nodes 1000)
diff --git a/sbin/pfctl/tests/files/pf0098.in b/sbin/pfctl/tests/files/pf0098.in
index b2b642be2026..c26f0fcfe4d3 100644
--- a/sbin/pfctl/tests/files/pf0098.in
+++ b/sbin/pfctl/tests/files/pf0098.in
@@ -1,4 +1,3 @@
# Test rule order processing should pass (require-order no longer required)
pass in on lo1000000 all
-#match out on lo0 inet6 all nat-to lo0
-
+match out on lo0 inet6 all nat-to lo0
diff --git a/sbin/pfctl/tests/files/pf0098.ok b/sbin/pfctl/tests/files/pf0098.ok
index 62016c91d60b..105bb46b4ae5 100644
--- a/sbin/pfctl/tests/files/pf0098.ok
+++ b/sbin/pfctl/tests/files/pf0098.ok
@@ -1 +1,2 @@
pass in on lo1000000 all flags S/SA keep state
+match out on lo0 inet6 all nat-to { ::1, fe80::1 } round-robin
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index 6ff3d50e6dc3..b5e3e1126978 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -27,7 +27,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd April 22, 2025
+.Dd April 27, 2025
.Dt PF.CONF 5
.Os
.Sh NAME
@@ -1333,29 +1333,18 @@ If the
.Xr dummynet 4
module is not loaded any traffic sent into a queue or pipe will be dropped.
.Sh TRANSLATION
-Translation rules modify either the source or destination address of the
-packets associated with a stateful connection.
-A stateful connection is automatically created to track packets matching
-such a rule as long as they are not blocked by the filtering section of
-.Nm pf.conf .
-The translation engine modifies the specified address and/or port in the
-packet, recalculates IP, TCP and UDP checksums as necessary, and passes
-it to the packet filter for evaluation.
-.Pp
-Since translation occurs before filtering the filter
-engine will see packets as they look after any
-addresses and ports have been translated.
-Filter rules will therefore have to filter based on the translated
+Translation options modify either the source or destination address and
+port of the packets associated with a stateful connection.
+.Xr pf 4
+modifies the specified address and/or port in the packet and recalculates
+IP, TCP, and UDP checksums as necessary.
+.Pp
+If specified on a
+.Ic match
+rule, subsequent rules will see packets as they look
+after any addresses and ports have been translated.
+These rules will therefore have to filter based on the translated
address and port number.
-Packets that match a translation rule are only automatically passed if
-the
-.Ar pass
-modifier is given, otherwise they are
-still subject to
-.Ar block
-and
-.Ar pass
-rules.
.Pp
The state entry created permits
.Xr pf 4
@@ -1418,13 +1407,18 @@ The current implementation will only extract IPv4 addresses from the
IPv6 addresses with a prefix length of /96 and greater.
.It Ar binat
A
-.Ar binat
+.Ar binat-to
rule specifies a bidirectional mapping between an external IP netblock
and an internal IP netblock.
-.It Ar nat
+It expands to an outbound
+.Ar nat-to
+rule and an inbound
+.Ar rdr-to
+rule.
+.It Ar nat-to
A
-.Ar nat
-rule specifies that IP addresses are to be changed as the packet
+.Ar nat-to
+option specifies that IP addresses are to be changed as the packet
traverses the given interface.
This technique allows one or more IP addresses
on the translating host to support network traffic for a larger range of
@@ -1432,44 +1426,116 @@ machines on an "inside" network.
Although in theory any IP address can be used on the inside, it is strongly
recommended that one of the address ranges defined by RFC 1918 be used.
These netblocks are:
-.Bd -literal
-10.0.0.0 - 10.255.255.255 (all of net 10, i.e., 10/8)
-172.16.0.0 - 172.31.255.255 (i.e., 172.16/12)
-192.168.0.0 - 192.168.255.255 (i.e., 192.168/16)
+.Bd -literal -offset indent
+10.0.0.0 - 10.255.255.255 (all of net 10.0.0.0, i.e., 10.0.0.0/8)
+172.16.0.0 - 172.31.255.255 (i.e., 172.16.0.0/12)
+192.168.0.0 - 192.168.255.255 (i.e., 192.168.0.0/16)
.Ed
-.It Pa rdr
+.Pp
+.Ar nat-to
+is usually applied outbound.
+If applied inbound, nat-to to a local IP address is not supported.
+.It Pa rdr-to
The packet is redirected to another destination and possibly a
different port.
-.Ar rdr
-rules can optionally specify port ranges instead of single ports.
-rdr ... port 2000:2999 -> ... port 4000
+.Ar rdr-to
+can optionally specify port ranges instead of single ports.
+For instance:
+.Bd -literal -offset indent
+match in ... port 2000:2999 rdr-to ... port 4000
+.Ed
redirects ports 2000 to 2999 (inclusive) to port 4000.
-rdr ... port 2000:2999 -> ... port 4000:*
+.Bd -literal -offset indent
+qmatch in ... port 2000:2999 rdr-to ... port 4000:*
+.Ed
redirects port 2000 to 4000, 2001 to 4001, ..., 2999 to 4999.
.El
.Pp
+.Ar rdr-to
+is usually applied inbound.
+If applied outbound, rdr-to to a local IP address is not supported.
In addition to modifying the address, some translation rules may modify
source or destination ports for
.Xr tcp 4
or
.Xr udp 4
connections; implicitly in the case of
-.Ar nat
-rules and both implicitly and explicitly in the case of
-.Ar rdr
-rules.
+.Ar nat-to
+options and both implicitly and explicitly in the case of
+.Ar rdr-to
+ones.
A
-.Ar rdr
-rule may cause the source port to be modified if doing so avoids a conflict
+.Ar rdr-to
+opion may cause the source port to be modified if doing so avoids a conflict
with an existing connection.
A random source port in the range 50001-65535 is chosen in this case; to
avoid excessive CPU consumption, the number of searches for a free port is
limited by the
-.Va net.pf.rdr_srcport_rewrite_tries
+.Va net.pf.rdqr_srcport_rewrite_tries
sysctl.
Port numbers are never translated with a
-.Ar binat
-rule.
+.Ar binat-to
+option.
+.Pp
+Note that redirecting external incoming connections to the loopback
+address, as in
+.Bd -literal -offset indent
+pass in on egress proto tcp from any to any port smtp \e
+ rdr-to 127.0.0.1 port spamd
+.Ed
+.Pp
+will effectively allow an external host to connect to daemons
+bound solely to the loopback address, circumventing the traditional
+blocking of such connections on a real interface.
+Unless this effect is desired, any of the local non-loopback addresses
+should be used as redirection target instead, which allows external
+connections only to daemons bound to this address or not bound to
+any address.
+.Pp
+See
+.Sx TRANSLATION EXAMPLES
+below.
+.Ss NAT ruleset (pre-FreeBSD 15)
+In order to maintain compatibility with older releases of FreeBSD
+.Ar NAT
+rules can also be specified in their own ruleset.
+A stateful connection is automatically created to track packets matching
+such a rule as long as they are not blocked by the filtering section of
+.Nm pf.conf .
+Since translation occurs before filtering the filter
+engine will see packets as they look after any
+addresses and ports have been translated.
+Filter rules will therefore have to filter based on the translated
+address and port number.
+Packets that match a translation rule are only automatically passed if
+the
+.Ar pass
+modifier is given, otherwise they are
+still subject to
+.Ar block
+and
+.Ar pass
+rules.
+.Pp
+The following rules can be defined in the NAT ruleset:
+.Ar binat ,
+.Ar nat ,
+and
+.Ar rdr .
+They have the same effect as
+.Ar binat-to ,
+.Ar nat-to
+and
+.Ar rdr-to
+options for filter rules.
+.Pp
+The
+.Ar no
+option prefixed to a translation rule causes packets to remain untranslated,
+much in the same way as
+.Ar drop quick
+works in the packet filter.
+If no rule matches the packet it is passed to the filter engine unmodified.
.Pp
Evaluation order of the translation rules is dependent on the type
of the translation rules and of the direction of a packet.
@@ -1484,14 +1550,6 @@ Rules of the same type are evaluated in the same order in which they
appear in the ruleset.
The first matching rule decides what action is taken.
.Pp
-The
-.Ar no
-option prefixed to a translation rule causes packets to remain untranslated,
-much in the same way as
-.Ar drop quick
-works in the packet filter (see below).
-If no rule matches the packet it is passed to the filter engine unmodified.
-.Pp
Translation rules apply only to packets that pass through
the specified interface, and if no interface is specified,
translation is applied to packets on all interfaces.
@@ -1504,22 +1562,8 @@ Redirections cannot reflect packets back through the interface they arrive
on, they can only be redirected to hosts connected to different interfaces
or to the firewall itself.
.Pp
-Note that redirecting external incoming connections to the loopback
-address, as in
-.Bd -literal -offset indent
-rdr on ne3 inet proto tcp to port smtp -> 127.0.0.1 port spamd
-.Ed
-.Pp
-will effectively allow an external host to connect to daemons
-bound solely to the loopback address, circumventing the traditional
-blocking of such connections on a real interface.
-Unless this effect is desired, any of the local non-loopback addresses
-should be used as redirection target instead, which allows external
-connections only to daemons bound to this address or not bound to
-any address.
-.Pp
See
-.Sx TRANSLATION EXAMPLES
+.Sx COMPATIBILITY TRANSLATION EXAMPLES
below.
.Sh PACKET FILTERING
.Xr pf 4
@@ -1615,6 +1659,9 @@ rules in that parameters are set for every rule a packet matches, not only
on the last matching rule.
For the following parameters, this means that the parameter effectively becomes
"sticky" until explicitly overridden:
+.Ar nat-to ,
+.Ar binat-to ,
+.Ar rdr-to ,
.Ar queue ,
.Ar dnpipe ,
.Ar dnqueue ,
@@ -2037,8 +2084,8 @@ if one flushes the state table.
However, states created from such intermediate packets may be missing
connection details such as the TCP window scaling factor.
States which modify the packet flow, such as those affected by
-.Ar af-to,
-.Ar nat,
+.Ar af-to ,
+.Ar nat ,
.Ar binat or
.Ar rdr
rules,
@@ -3051,21 +3098,24 @@ Doing so would break SCTP multihoming.
This example maps incoming requests on port 80 to port 8080, on
which a daemon is running (because, for example, it is not run as root,
and therefore lacks permission to bind to port 80).
-.Bd -literal
+.Bd -literal -offset indent
# use a macro for the interface name, so it can be changed easily
ext_if = \&"ne3\&"
# map daemon on 8080 to appear to be on 80
-rdr on $ext_if proto tcp from any to any port 80 -> 127.0.0.1 port 8080
+match in on $ext_if proto tcp from any to any port 80 \e
+ rdr-to 127.0.0.1 port 8080
.Ed
.Pp
-If the
+If a
.Ar pass
-modifier is given, packets matching the translation rule are passed without
-inspecting the filter rules:
-.Bd -literal
-rdr pass on $ext_if proto tcp from any to any port 80 -> 127.0.0.1 \e
- port 8080
+rule is used with the
+.Ar quick
+modifier, packets matching the translation rule are passed without
+inspecting subsequent filter rules:
+.Bd -literal -offset indent
+pass in quick on $ext_if proto tcp from any to any port 80 \e
+ rdr-to 127.0.0.1 port 8080
.Ed
.Pp
In the example below, vlan12 is configured as 192.168.168.1;
@@ -3076,29 +3126,8 @@ network appear as though it is the Internet routable address
204.92.77.111 to nodes behind any interface on the router except
for the nodes on vlan12.
(Thus, 192.168.168.1 can talk to the 192.168.168.0/24 nodes.)
-.Bd -literal
-nat on ! vlan12 from 192.168.168.0/24 to any -> 204.92.77.111
-.Ed
-.Pp
-In the example below, the machine sits between a fake internal 144.19.74.*
-network, and a routable external IP of 204.92.77.100.
-The
-.Ar no nat
-rule excludes protocol AH from being translated.
-.Bd -literal
-# NO NAT
-no nat on $ext_if proto ah from 144.19.74.0/24 to any
-nat on $ext_if from 144.19.74.0/24 to any -> 204.92.77.100
-.Ed
-.Pp
-In the example below, packets bound for one specific server, as well as those
-generated by the sysadmins are not proxied; all other connections are.
-.Bd -literal
-# NO RDR
-no rdr on $int_if proto { tcp, udp } from any to $server port 80
-no rdr on $int_if proto { tcp, udp } from $sysadmins to any port 80
-rdr on $int_if proto { tcp, udp } from any to any port 80 -> 127.0.0.1 \e
- port 80
+.Bd -literal -offset indent
+match out on ! vlan12 from 192.168.168.0/24 to any nat-to 204.92.77.111
.Ed
.Pp
This longer example uses both a NAT and a redirection.
@@ -3111,62 +3140,84 @@ The three mandatory anchors for
are omitted from this example; see the
.Xr ftp-proxy 8
manpage.
-.Bd -literal
+.Bd -literal -offset indent
# NAT
# Translate outgoing packets' source addresses (any protocol).
# In this case, any address but the gateway's external address is mapped.
-nat on $ext_if inet from ! ($ext_if) to any -> ($ext_if)
+pass out on $ext_if inet from ! ($ext_if) to any nat-to ($ext_if)
# NAT PROXYING
# Map outgoing packets' source port to an assigned proxy port instead of
# an arbitrary port.
# In this case, proxy outgoing isakmp with port 500 on the gateway.
-nat on $ext_if inet proto udp from any port = isakmp to any -> ($ext_if) \e
- port 500
+pass out on $ext_if inet proto udp from any port = isakmp to any \e
+ nat-to ($ext_if) port 500
# BINAT
# Translate outgoing packets' source address (any protocol).
# Translate incoming packets' destination address to an internal machine
# (bidirectional).
-binat on $ext_if from 10.1.2.150 to any -> $ext_if
+pass on $ext_if from 10.1.2.150 to any binat-to $ext_if
# Translate packets arriving on $peer_if addressed to 172.22.16.0/20
# to the corresponding address in 172.21.16.0/20 (bidirectional).
-binat on $peer_if from 172.21.16.0/20 to any -> 172.22.16.0/20
+pass on $peer_if from 172.21.16.0/20 to any binat-to 172.22.16.0/20
# RDR
# Translate incoming packets' destination addresses.
# As an example, redirect a TCP and UDP port to an internal machine.
-rdr on $ext_if inet proto tcp from any to ($ext_if) port 8080 \e
- -> 10.1.2.151 port 22
-rdr on $ext_if inet proto udp from any to ($ext_if) port 8080 \e
- -> 10.1.2.151 port 53
+pass in on $ext_if inet proto tcp from any to ($ext_if) port 8080 \e
+ rdr-to 10.1.2.151 port 22
+pass in on $ext_if inet proto udp from any to ($ext_if) port 8080 \e
+ rdr-to 10.1.2.151 port 53
# RDR
# Translate outgoing ftp control connections to send them to localhost
# for proxying with ftp-proxy(8) running on port 8021.
-rdr on $int_if proto tcp from any to any port 21 -> 127.0.0.1 port 8021
+pass in on $int_if proto tcp from any to any port 21 \e
+ rdr-to 127.0.0.1 port 8021
.Ed
.Pp
In this example, a NAT gateway is set up to translate internal addresses
using a pool of public addresses (192.0.2.16/28) and to redirect
incoming web server connections to a group of web servers on the internal
network.
-.Bd -literal
+.Bd -literal -offset indent
# NAT LOAD BALANCE
# Translate outgoing packets' source addresses using an address pool.
# A given source address is always translated to the same pool address by
# using the source-hash keyword.
-nat on $ext_if inet from any to any -> 192.0.2.16/28 source-hash
+pass out on $ext_if inet from any to any nat-to 192.0.2.16/28 source-hash
# RDR ROUND ROBIN
# Translate incoming web server connections to a group of web servers on
# the internal network.
-rdr on $ext_if proto tcp from any to any port 80 \e
- -> { 10.1.2.155, 10.1.2.160, 10.1.2.161 } round-robin
+pass in on $ext_if proto tcp from any to any port 80 \e
+ rdr-to { 10.1.2.155, 10.1.2.160, 10.1.2.161 } round-robin
+.Ed
+.Sh COMPATIBILITY TRANSLATION EXAMPLES
+In the example below, the machine sits between a fake internal 144.19.74.*
+network, and a routable external IP of 204.92.77.100.
+The
+.Ar no nat
+rule excludes protocol AH from being translated.
+.Bd -literal -offset indent
+# NAT
+no nat on $ext_if proto ah from 144.19.74.0/24 to any
+nat on $ext_if from 144.19.74.0/24 to any -> 204.92.77.100
+.Ed
+.Pp
+In the example below, packets bound for one specific server, as well as those
+generated by the sysadmins are not proxied; all other connections are.
+.Bd -literal -offset indent
+# RDR
+no rdr on $int_if proto { tcp, udp } from any to $server port 80
+no rdr on $int_if proto { tcp, udp } from $sysadmins to any port 80
+rdr on $int_if proto { tcp, udp } from any to any port 80 \e
+ -> 127.0.0.1 port 80
.Ed
.Sh FILTER EXAMPLES
-.Bd -literal
+.Bd -literal -offset indent
# The external interface is kue0
# (157.161.48.183, the only routable address)
# and the private network is 10.0.0.0/8, for which we are doing NAT.
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index a94ac62558ca..91fdcfcbec1d 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2711,6 +2711,13 @@ u_short pf_get_translation(struct pf_pdesc *,
int, struct pf_state_key **, struct pf_state_key **,
struct pf_kanchor_stackframe *, struct pf_krule **,
struct pf_udp_mapping **udp_mapping);
+u_short pf_get_transaddr(struct pf_pdesc *,
+ struct pf_state_key **, struct pf_state_key **,
+ struct pf_krule *, struct pf_udp_mapping **,
+ u_int8_t, struct pf_kpool *);
+int pf_translate_compat(struct pf_pdesc *,
+ struct pf_state_key *, struct pf_state_key *,
+ struct pf_krule *, u_int16_t);
int pf_state_key_setup(struct pf_pdesc *,
u_int16_t, u_int16_t,
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 06ced7b055b3..a1301e59533b 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -348,7 +348,7 @@ static int pf_create_state(struct pf_krule *, struct pf_krule *,
struct pf_state_key *, struct pf_state_key *, int *,
struct pf_kstate **, int, u_int16_t, u_int16_t,
struct pf_krule_slist *, struct pf_udp_mapping *,
- u_short *);
+ struct pf_kpool *, u_short *);
static int pf_state_key_addr_setup(struct pf_pdesc *,
struct pf_state_key_cmp *, int);
static int pf_tcp_track_full(struct pf_kstate *,
@@ -5474,6 +5474,58 @@ pf_test_eth_rule(int dir, struct pfi_kkif *kif, struct mbuf **m0)
} \
} while (0)
+static __inline u_short
+pf_rule_apply_nat(struct pf_pdesc *pd, struct pf_state_key **skp,
+ struct pf_state_key **nkp, struct pf_krule *r, struct pf_krule **nr,
+ struct pf_udp_mapping **udp_mapping, u_int16_t virtual_type, int *rewrite,
+ struct pf_kpool **nat_pool)
+{
+ u_short transerror;
+ u_int8_t nat_action;
+
+ if (r->rule_flag & PFRULE_AFTO) {
+ /* Don't translate if there was an old style NAT rule */
+ if (*nr != NULL)
+ return (PFRES_TRANSLATE);
+
+ /* pass af-to rules, unsupported on match rules */
+ KASSERT(r->action != PF_MATCH, ("%s: af-to on match rule", __func__));
+ /* XXX I can imagine scenarios where we have both NAT and RDR source tracking */
+ *nat_pool = &(r->nat);
+ (*nr) = r;
+ pd->naf = r->naf;
+ if (pf_get_transaddr_af(*nr, pd) == -1) {
+ return (PFRES_TRANSLATE);
+ }
+ return (PFRES_MATCH);
+ } else if (r->rdr.cur || r->nat.cur) {
+ /* Don't translate if there was an old style NAT rule */
+ if (*nr != NULL)
+ return (PFRES_TRANSLATE);
+
+ /* match/pass nat-to/rdr-to rules */
+ (*nr) = r;
+ if (r->nat.cur) {
+ nat_action = PF_NAT;
+ *nat_pool = &(r->nat);
+ } else {
+ nat_action = PF_RDR;
+ *nat_pool = &(r->rdr);
+ }
+
+ transerror = pf_get_transaddr(pd, skp, nkp, *nr, udp_mapping,
+ nat_action, *nat_pool);
+ if (transerror == PFRES_MATCH) {
+ (*rewrite) += pf_translate_compat(pd, *skp, *nkp, *nr,
+ virtual_type);
+ return(PFRES_MATCH);
+ }
+ return (transerror);
+ }
+
+ return (PFRES_MAX);
+}
+
static int
pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
struct pf_pdesc *pd, struct pf_krule **am,
@@ -5498,6 +5550,7 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
u_int8_t icmptype = 0, icmpcode = 0;
struct pf_kanchor_stackframe anchor_stack[PF_ANCHOR_STACKSIZE];
struct pf_udp_mapping *udp_mapping = NULL;
+ struct pf_kpool *nat_pool = NULL;
PF_RULES_RASSERT();
@@ -5513,12 +5566,17 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
pd->lookup.done = 1;
}
+ if (pd->ip_sum)
+ bip_sum = *pd->ip_sum;
+
switch (pd->virtual_proto) {
case IPPROTO_TCP:
+ bproto_sum = th->th_sum;
pd->nsport = th->th_sport;
pd->ndport = th->th_dport;
break;
case IPPROTO_UDP:
+ bproto_sum = pd->hdr.udp.uh_sum;
pd->nsport = pd->hdr.udp.uh_sport;
pd->ndport = pd->hdr.udp.uh_dport;
break;
@@ -5582,169 +5640,13 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
case PFRES_MATCH:
KASSERT(sk != NULL, ("%s: null sk", __func__));
KASSERT(nk != NULL, ("%s: null nk", __func__));
-
if (nr->log) {
PFLOG_PACKET(nr->action, PFRES_MATCH, nr, a,
ruleset, pd, 1, NULL);
}
- if (pd->ip_sum)
- bip_sum = *pd->ip_sum;
-
- switch (pd->proto) {
- case IPPROTO_TCP:
- bproto_sum = th->th_sum;
-
- if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
- nk->port[pd->sidx] != pd->nsport) {
- pf_change_ap(pd, pd->src, &th->th_sport,
- &nk->addr[pd->sidx], nk->port[pd->sidx]);
- pd->sport = &th->th_sport;
- pd->nsport = th->th_sport;
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
- nk->port[pd->didx] != pd->ndport) {
- pf_change_ap(pd, pd->dst, &th->th_dport,
- &nk->addr[pd->didx], nk->port[pd->didx]);
- pd->dport = &th->th_dport;
- pd->ndport = th->th_dport;
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
- rewrite++;
- break;
- case IPPROTO_UDP:
- bproto_sum = pd->hdr.udp.uh_sum;
-
- if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
- nk->port[pd->sidx] != pd->nsport) {
- pf_change_ap(pd, pd->src,
- &pd->hdr.udp.uh_sport,
- &nk->addr[pd->sidx],
- nk->port[pd->sidx]);
- pd->sport = &pd->hdr.udp.uh_sport;
- pd->nsport = pd->hdr.udp.uh_sport;
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
- nk->port[pd->didx] != pd->ndport) {
- pf_change_ap(pd, pd->dst,
- &pd->hdr.udp.uh_dport,
- &nk->addr[pd->didx],
- nk->port[pd->didx]);
- pd->dport = &pd->hdr.udp.uh_dport;
- pd->ndport = pd->hdr.udp.uh_dport;
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
- rewrite++;
- break;
- case IPPROTO_SCTP: {
- if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
- nk->port[pd->sidx] != pd->nsport) {
- pf_change_ap(pd, pd->src,
- &pd->hdr.sctp.src_port,
- &nk->addr[pd->sidx],
- nk->port[pd->sidx]);
- pd->sport = &pd->hdr.sctp.src_port;
- pd->nsport = pd->hdr.sctp.src_port;
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
- if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
- nk->port[pd->didx] != pd->ndport) {
- pf_change_ap(pd, pd->dst,
- &pd->hdr.sctp.dest_port,
- &nk->addr[pd->didx],
- nk->port[pd->didx]);
- pd->dport = &pd->hdr.sctp.dest_port;
- pd->ndport = pd->hdr.sctp.dest_port;
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
- break;
- }
-#ifdef INET
- case IPPROTO_ICMP:
- if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET)) {
- pf_change_a(&pd->src->v4.s_addr, pd->ip_sum,
- nk->addr[pd->sidx].v4.s_addr, 0);
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET)) {
- pf_change_a(&pd->dst->v4.s_addr, pd->ip_sum,
- nk->addr[pd->didx].v4.s_addr, 0);
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
-
- if (virtual_type == htons(ICMP_ECHO) &&
- nk->port[pd->sidx] != pd->hdr.icmp.icmp_id) {
- pd->hdr.icmp.icmp_cksum = pf_cksum_fixup(
- pd->hdr.icmp.icmp_cksum, pd->nsport,
- nk->port[pd->sidx], 0);
- pd->hdr.icmp.icmp_id = nk->port[pd->sidx];
- pd->sport = &pd->hdr.icmp.icmp_id;
- }
- m_copyback(pd->m, pd->off, ICMP_MINLEN, (caddr_t)&pd->hdr.icmp);
- break;
-#endif /* INET */
-#ifdef INET6
- case IPPROTO_ICMPV6:
- if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET6)) {
- pf_change_a6(pd->src, &pd->hdr.icmp6.icmp6_cksum,
- &nk->addr[pd->sidx], 0);
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET6)) {
- pf_change_a6(pd->dst, &pd->hdr.icmp6.icmp6_cksum,
- &nk->addr[pd->didx], 0);
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
- rewrite++;
- break;
-#endif /* INET */
- default:
- switch (pd->af) {
-#ifdef INET
- case AF_INET:
- if (PF_ANEQ(&pd->nsaddr,
- &nk->addr[pd->sidx], AF_INET)) {
- pf_change_a(&pd->src->v4.s_addr,
- pd->ip_sum,
- nk->addr[pd->sidx].v4.s_addr, 0);
- PF_ACPY(&pd->nsaddr, pd->src, pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr,
- &nk->addr[pd->didx], AF_INET)) {
- pf_change_a(&pd->dst->v4.s_addr,
- pd->ip_sum,
- nk->addr[pd->didx].v4.s_addr, 0);
- PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
- }
- break;
-#endif /* INET */
-#ifdef INET6
- case AF_INET6:
- if (PF_ANEQ(&pd->nsaddr,
- &nk->addr[pd->sidx], AF_INET6)) {
- PF_ACPY(&pd->nsaddr, &nk->addr[pd->sidx], pd->af);
- PF_ACPY(pd->src, &nk->addr[pd->sidx], pd->af);
- }
-
- if (PF_ANEQ(&pd->ndaddr,
- &nk->addr[pd->didx], AF_INET6)) {
- PF_ACPY(&pd->ndaddr, &nk->addr[pd->didx], pd->af);
- PF_ACPY(pd->dst, &nk->addr[pd->didx], pd->af);
- }
- break;
-#endif /* INET6 */
- }
- break;
- }
- if (nr->natpass)
- r = NULL;
+ rewrite += pf_translate_compat(pd, sk, nk, nr, virtual_type);
+ nat_pool = &(nr->rdr);
}
while (r != NULL) {
@@ -5850,6 +5752,24 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
tag = r->tag;
if (r->anchor == NULL) {
if (r->action == PF_MATCH) {
+ /*
+ * Apply translations before increasing counters,
+ * in case it fails.
+ */
+ transerror = pf_rule_apply_nat(pd, &sk, &nk, r,
+ &nr, &udp_mapping, virtual_type, &rewrite,
+ &nat_pool);
+ switch (transerror) {
+ case PFRES_MATCH:
+ /* Translation action found in rule and applied successfully */
+ case PFRES_MAX:
+ /* No translation action found in rule */
+ break;
+ default:
+ /* Translation action found in rule but failed to apply */
+ REASON_SET(reason, transerror);
+ goto cleanup;
+ }
ri = malloc(sizeof(struct pf_krule_item), M_PF_RULE_ITEM, M_NOWAIT | M_ZERO);
if (ri == NULL) {
REASON_SET(reason, PFRES_MEMORY);
@@ -5862,14 +5782,6 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
pf_counter_u64_add_protected(&r->bytes[pd->dir == PF_OUT], pd->tot_len);
pf_counter_u64_critical_exit();
pf_rule_to_actions(r, &pd->act);
- if (r->rule_flag & PFRULE_AFTO)
- pd->naf = r->naf;
- if (pd->af != pd->naf) {
- if (pf_get_transaddr_af(r, pd) == -1) {
- REASON_SET(reason, PFRES_TRANSLATE);
- goto cleanup;
- }
- }
if (r->log)
PFLOG_PACKET(r->action, PFRES_MATCH, r,
a, ruleset, pd, 1, NULL);
@@ -5900,13 +5812,18 @@ nextrule:
/* apply actions for last matching pass/block rule */
pf_rule_to_actions(r, &pd->act);
- if (r->rule_flag & PFRULE_AFTO)
- pd->naf = r->naf;
- if (pd->af != pd->naf) {
- if (pf_get_transaddr_af(r, pd) == -1) {
- REASON_SET(reason, PFRES_TRANSLATE);
- goto cleanup;
- }
+ transerror = pf_rule_apply_nat(pd, &sk, &nk, r, &nr, &udp_mapping,
+ virtual_type, &rewrite, &nat_pool);
+ switch (transerror) {
+ case PFRES_MATCH:
+ /* Translation action found in rule and applied successfully */
+ case PFRES_MAX:
+ /* No translation action found in rule */
+ break;
+ default:
+ /* Translation action found in rule but failed to apply */
+ REASON_SET(reason, transerror);
+ goto cleanup;
}
if (r->log) {
@@ -5938,12 +5855,6 @@ nextrule:
if (r->rt) {
struct pf_ksrc_node *sn = NULL;
struct pf_srchash *snh = NULL;
- struct pf_kpool *pool = &r->route;
-
- /* Backwards compatibility. */
- if (TAILQ_EMPTY(&pool->list))
- pool = &r->rdr;
-
/*
* Set act.rt here instead of in pf_rule_to_actions() because
* it is applied only from the last pass rule.
@@ -5951,7 +5862,7 @@ nextrule:
pd->act.rt = r->rt;
/* Don't use REASON_SET, pf_map_addr increases the reason counters */
*reason = pf_map_addr_sn(pd->af, r, pd->src, &pd->act.rt_addr,
- &pd->act.rt_kif, NULL, &sn, &snh, pool, PF_SN_ROUTE);
+ &pd->act.rt_kif, NULL, &sn, &snh, &(r->route), PF_SN_ROUTE);
if (*reason != 0)
goto cleanup;
}
@@ -5963,7 +5874,7 @@ nextrule:
action = pf_create_state(r, nr, a, pd, nk, sk,
&rewrite, sm, tag, bproto_sum, bip_sum,
- &match_rules, udp_mapping, reason);
+ &match_rules, udp_mapping, nat_pool, reason);
sk = nk = NULL;
if (action != PF_PASS) {
pf_udp_mapping_release(udp_mapping);
@@ -6054,7 +5965,8 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
struct pf_pdesc *pd, struct pf_state_key *nk, struct pf_state_key *sk,
int *rewrite, struct pf_kstate **sm, int tag, u_int16_t bproto_sum,
u_int16_t bip_sum, struct pf_krule_slist *match_rules,
- struct pf_udp_mapping *udp_mapping, u_short *reason)
+ struct pf_udp_mapping *udp_mapping, struct pf_kpool *nat_pool,
+ u_short *reason)
{
struct pf_kstate *s = NULL;
struct pf_ksrc_node *sns[PF_SN_MAX] = { NULL };
@@ -6068,7 +5980,6 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
u_int16_t mss = V_tcp_mssdflt;
u_short sn_reason;
struct pf_krule_item *ri;
- struct pf_kpool *pool_route = &r->route;
/* check maximums */
if (r->max_states &&
@@ -6085,20 +5996,25 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
goto csfailed;
}
/* src node for route-to rule */
- if (TAILQ_EMPTY(&pool_route->list)) /* Backwards compatibility. */
- pool_route = &r->rdr;
- if ((pool_route->opts & PF_POOL_STICKYADDR) &&
- (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src, pd->af,
- &pd->act.rt_addr, pd->act.rt_kif, PF_SN_ROUTE)) != 0) {
- REASON_SET(reason, sn_reason);
- goto csfailed;
+ if (r->rt) {
+ if ((r->route.opts & PF_POOL_STICKYADDR) &&
+ (sn_reason = pf_insert_src_node(sns, snhs, r, pd->src,
+ pd->af, &pd->act.rt_addr, pd->act.rt_kif,
+ PF_SN_ROUTE)) != 0) {
+ REASON_SET(reason, sn_reason);
+ goto csfailed;
+ }
}
/* src node for translation rule */
- if (nr != NULL && (nr->rdr.opts & PF_POOL_STICKYADDR) &&
- (sn_reason = pf_insert_src_node(sns, snhs, nr, &sk->addr[pd->sidx],
- pd->af, &nk->addr[1], NULL, PF_SN_NAT)) != 0 ) {
- REASON_SET(reason, sn_reason);
- goto csfailed;
+ if (nr != NULL) {
+ KASSERT(nat_pool != NULL, ("%s: nat_pool is NULL", __func__));
+ if ((nat_pool->opts & PF_POOL_STICKYADDR) &&
+ (sn_reason = pf_insert_src_node(sns, snhs, nr,
+ &sk->addr[pd->sidx], pd->af, &nk->addr[1], NULL,
+ PF_SN_NAT)) != 0 ) {
+ REASON_SET(reason, sn_reason);
+ goto csfailed;
+ }
}
s = pf_alloc_state(M_NOWAIT);
if (s == NULL) {
@@ -6213,9 +6129,7 @@ pf_create_state(struct pf_krule *r, struct pf_krule *nr, struct pf_krule *a,
/*
* sk/nk could already been setup by pf_get_translation().
*/
- if (nr == NULL) {
- KASSERT((sk == NULL && nk == NULL), ("%s: nr %p sk %p, nk %p",
- __func__, nr, sk, nk));
+ if (sk == NULL && nk == NULL) {
MPASS(pd->sport == NULL || (pd->osport == *pd->sport));
MPASS(pd->dport == NULL || (pd->odport == *pd->dport));
if (pf_state_key_setup(pd, pd->nsport, pd->ndport, &sk, &nk)) {
@@ -6390,6 +6304,167 @@ pf_translate(struct pf_pdesc *pd, struct pf_addr *saddr, u_int16_t sport,
return (rewrite);
}
+int
+pf_translate_compat(struct pf_pdesc *pd, struct pf_state_key *sk,
+ struct pf_state_key *nk, struct pf_krule *nr, u_int16_t virtual_type)
+{
+ struct tcphdr *th = &pd->hdr.tcp;
+ int rewrite = 0;
+
+ KASSERT(sk != NULL, ("%s: null sk", __func__));
+ KASSERT(nk != NULL, ("%s: null nk", __func__));
+
+ switch (pd->proto) {
+ case IPPROTO_TCP:
+ if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
+ nk->port[pd->sidx] != pd->nsport) {
+ pf_change_ap(pd, pd->src, &th->th_sport,
+ &nk->addr[pd->sidx], nk->port[pd->sidx]);
+ pd->sport = &th->th_sport;
+ pd->nsport = th->th_sport;
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
+ nk->port[pd->didx] != pd->ndport) {
+ pf_change_ap(pd, pd->dst, &th->th_dport,
+ &nk->addr[pd->didx], nk->port[pd->didx]);
+ pd->dport = &th->th_dport;
+ pd->ndport = th->th_dport;
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+ rewrite++;
+ break;
+ case IPPROTO_UDP:
+ if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
+ nk->port[pd->sidx] != pd->nsport) {
+ pf_change_ap(pd, pd->src,
+ &pd->hdr.udp.uh_sport,
+ &nk->addr[pd->sidx],
+ nk->port[pd->sidx]);
+ pd->sport = &pd->hdr.udp.uh_sport;
+ pd->nsport = pd->hdr.udp.uh_sport;
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
+ nk->port[pd->didx] != pd->ndport) {
+ pf_change_ap(pd, pd->dst,
+ &pd->hdr.udp.uh_dport,
+ &nk->addr[pd->didx],
+ nk->port[pd->didx]);
+ pd->dport = &pd->hdr.udp.uh_dport;
+ pd->ndport = pd->hdr.udp.uh_dport;
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+ rewrite++;
+ break;
+ case IPPROTO_SCTP: {
+ if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) ||
+ nk->port[pd->sidx] != pd->nsport) {
+ pf_change_ap(pd, pd->src,
+ &pd->hdr.sctp.src_port,
+ &nk->addr[pd->sidx],
+ nk->port[pd->sidx]);
+ pd->sport = &pd->hdr.sctp.src_port;
+ pd->nsport = pd->hdr.sctp.src_port;
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+ if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], pd->af) ||
+ nk->port[pd->didx] != pd->ndport) {
+ pf_change_ap(pd, pd->dst,
+ &pd->hdr.sctp.dest_port,
+ &nk->addr[pd->didx],
+ nk->port[pd->didx]);
+ pd->dport = &pd->hdr.sctp.dest_port;
+ pd->ndport = pd->hdr.sctp.dest_port;
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+ break;
+ }
+#ifdef INET
+ case IPPROTO_ICMP:
+ if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET)) {
+ pf_change_a(&pd->src->v4.s_addr, pd->ip_sum,
+ nk->addr[pd->sidx].v4.s_addr, 0);
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET)) {
+ pf_change_a(&pd->dst->v4.s_addr, pd->ip_sum,
+ nk->addr[pd->didx].v4.s_addr, 0);
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+
+ if (virtual_type == htons(ICMP_ECHO) &&
+ nk->port[pd->sidx] != pd->hdr.icmp.icmp_id) {
+ pd->hdr.icmp.icmp_cksum = pf_cksum_fixup(
+ pd->hdr.icmp.icmp_cksum, pd->nsport,
+ nk->port[pd->sidx], 0);
+ pd->hdr.icmp.icmp_id = nk->port[pd->sidx];
+ pd->sport = &pd->hdr.icmp.icmp_id;
+ }
+ m_copyback(pd->m, pd->off, ICMP_MINLEN, (caddr_t)&pd->hdr.icmp);
+ break;
+#endif /* INET */
+#ifdef INET6
+ case IPPROTO_ICMPV6:
+ if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], AF_INET6)) {
+ pf_change_a6(pd->src, &pd->hdr.icmp6.icmp6_cksum,
+ &nk->addr[pd->sidx], 0);
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr, &nk->addr[pd->didx], AF_INET6)) {
+ pf_change_a6(pd->dst, &pd->hdr.icmp6.icmp6_cksum,
+ &nk->addr[pd->didx], 0);
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+ rewrite++;
+ break;
+#endif /* INET */
+ default:
+ switch (pd->af) {
+#ifdef INET
+ case AF_INET:
+ if (PF_ANEQ(&pd->nsaddr,
+ &nk->addr[pd->sidx], AF_INET)) {
+ pf_change_a(&pd->src->v4.s_addr,
+ pd->ip_sum,
+ nk->addr[pd->sidx].v4.s_addr, 0);
+ PF_ACPY(&pd->nsaddr, pd->src, pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr,
+ &nk->addr[pd->didx], AF_INET)) {
+ pf_change_a(&pd->dst->v4.s_addr,
+ pd->ip_sum,
+ nk->addr[pd->didx].v4.s_addr, 0);
+ PF_ACPY(&pd->ndaddr, pd->dst, pd->af);
+ }
+ break;
+#endif /* INET */
+#ifdef INET6
+ case AF_INET6:
+ if (PF_ANEQ(&pd->nsaddr,
+ &nk->addr[pd->sidx], AF_INET6)) {
+ PF_ACPY(&pd->nsaddr, &nk->addr[pd->sidx], pd->af);
+ PF_ACPY(pd->src, &nk->addr[pd->sidx], pd->af);
+ }
+
+ if (PF_ANEQ(&pd->ndaddr,
+ &nk->addr[pd->didx], AF_INET6)) {
+ PF_ACPY(&pd->ndaddr, &nk->addr[pd->didx], pd->af);
+ PF_ACPY(pd->dst, &nk->addr[pd->didx], pd->af);
+ }
+ break;
+#endif /* INET6 */
+ }
+ break;
+ }
+ return (rewrite);
+}
+
static int
pf_tcp_track_full(struct pf_kstate *state, struct pf_pdesc *pd,
u_short *reason, int *copyback, struct pf_state_peer *src,
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 44da2e156ce2..5c3aec906b79 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -2215,17 +2215,31 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
}
pf_mv_kpool(&V_pf_pabuf[0], &rule->nat.list);
- pf_mv_kpool(&V_pf_pabuf[1], &rule->rdr.list);
- pf_mv_kpool(&V_pf_pabuf[2], &rule->route.list);
- if (((((rule->action == PF_NAT) || (rule->action == PF_RDR) ||
- (rule->action == PF_BINAT)) && rule->anchor == NULL) ||
- (rule->rt > PF_NOPFROUTE)) &&
- (TAILQ_FIRST(&rule->rdr.list) == NULL &&
- TAILQ_FIRST(&rule->route.list) == NULL))
+
+ /*
+ * Old version of pfctl provide route redirection pools in single
+ * common redirection pool rdr. New versions use rdr only for
+ * rdr-to rules.
+ */
+ if (rule->rt > PF_NOPFROUTE && TAILQ_EMPTY(&V_pf_pabuf[2])) {
+ pf_mv_kpool(&V_pf_pabuf[1], &rule->route.list);
+ } else {
+ pf_mv_kpool(&V_pf_pabuf[1], &rule->rdr.list);
+ pf_mv_kpool(&V_pf_pabuf[2], &rule->route.list);
+ }
+
+ if (((rule->action == PF_NAT) || (rule->action == PF_RDR) ||
+ (rule->action == PF_BINAT)) && rule->anchor == NULL &&
+ TAILQ_FIRST(&rule->rdr.list) == NULL) {
error = EINVAL;
+ }
+
+ if (rule->rt > PF_NOPFROUTE && (TAILQ_FIRST(&rule->route.list) == NULL)) {
+ error = EINVAL;
+ }
- if (rule->action == PF_PASS && rule->rdr.opts & PF_POOL_STICKYADDR &&
- !rule->keep_state) {
+ if (rule->action == PF_PASS && (rule->rdr.opts & PF_POOL_STICKYADDR ||
+ rule->nat.opts & PF_POOL_STICKYADDR) && !rule->keep_state) {
error = EINVAL;
}
diff --git a/sys/netpfil/pf/pf_lb.c b/sys/netpfil/pf/pf_lb.c
index d40f4828eb62..43edfc806c1c 100644
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -75,7 +75,7 @@ VNET_DEFINE_STATIC(int, pf_rdr_srcport_rewrite_tries) = 16;
static uint64_t pf_hash(struct pf_addr *, struct pf_addr *,
struct pf_poolhashkey *, sa_family_t);
-static struct pf_krule *pf_match_translation(struct pf_pdesc *,
+struct pf_krule *pf_match_translation(struct pf_pdesc *,
int, struct pf_kanchor_stackframe *);
static int pf_get_sport(struct pf_pdesc *, struct pf_krule *,
struct pf_addr *, uint16_t *, uint16_t, uint16_t,
@@ -128,7 +128,7 @@ pf_hash(struct pf_addr *inaddr, struct pf_addr *hash,
return (res);
}
-static struct pf_krule *
+struct pf_krule *
pf_match_translation(struct pf_pdesc *pd,
int rs_num, struct pf_kanchor_stackframe *anchor_stack)
{
@@ -428,19 +428,19 @@ static int
pf_get_mape_sport(struct pf_pdesc *pd, struct pf_krule *r,
struct pf_addr *naddr, uint16_t *nport,
struct pf_ksrc_node **sn, struct pf_srchash **sh,
- struct pf_udp_mapping **udp_mapping)
+ struct pf_udp_mapping **udp_mapping, struct pf_kpool *rpool)
{
uint16_t psmask, low, highmask;
uint16_t i, ahigh, cut;
int ashift, psidshift;
- ashift = 16 - r->rdr.mape.offset;
- psidshift = ashift - r->rdr.mape.psidlen;
- psmask = r->rdr.mape.psid & ((1U << r->rdr.mape.psidlen) - 1);
+ ashift = 16 - rpool->mape.offset;
+ psidshift = ashift - rpool->mape.psidlen;
+ psmask = rpool->mape.psid & ((1U << rpool->mape.psidlen) - 1);
psmask = psmask << psidshift;
highmask = (1U << psidshift) - 1;
- ahigh = (1U << r->rdr.mape.offset) - 1;
+ ahigh = (1U << rpool->mape.offset) - 1;
cut = arc4random() & ahigh;
if (cut == 0)
cut = 1;
@@ -448,14 +448,14 @@ pf_get_mape_sport(struct pf_pdesc *pd, struct pf_krule *r,
for (i = cut; i <= ahigh; i++) {
low = (i << ashift) | psmask;
if (!pf_get_sport(pd, r,
- naddr, nport, low, low | highmask, sn, sh, &r->rdr,
+ naddr, nport, low, low | highmask, sn, sh, rpool,
udp_mapping, PF_SN_NAT))
return (0);
}
for (i = cut - 1; i > 0; i--) {
low = (i << ashift) | psmask;
if (!pf_get_sport(pd, r,
- naddr, nport, low, low | highmask, sn, sh, &r->rdr,
+ naddr, nport, low, low | highmask, sn, sh, rpool,
udp_mapping, PF_SN_NAT))
return (0);
}
@@ -776,12 +776,7 @@ pf_get_translation(struct pf_pdesc *pd, int off,
struct pf_udp_mapping **udp_mapping)
{
struct pf_krule *r = NULL;
- struct pf_addr *naddr;
- struct pf_ksrc_node *sn = NULL;
- struct pf_srchash *sh = NULL;
- uint16_t *nportp;
- uint16_t low, high;
- u_short reason;
+ u_short transerror;
PF_RULES_RASSERT();
KASSERT(*skp == NULL, ("*skp not NULL"));
@@ -809,38 +804,64 @@ pf_get_translation(struct pf_pdesc *pd, int off,
return (PFRES_MAX);
}
- if (pf_state_key_setup(pd, pd->nsport, pd->ndport, skp, nkp))
- return (PFRES_MEMORY);
+ transerror = pf_get_transaddr(pd, skp, nkp, r, udp_mapping, r->action, &(r->rdr));
+ if (transerror == PFRES_MATCH)
+ *rp = r;
+
+ return (transerror);
+}
+
+u_short
+pf_get_transaddr(struct pf_pdesc *pd, struct pf_state_key **skp,
+ struct pf_state_key **nkp, struct pf_krule *r,
+ struct pf_udp_mapping **udp_mapping, uint8_t nat_action,
+ struct pf_kpool *rpool)
+{
+ struct pf_addr *naddr;
+ struct pf_ksrc_node *sn = NULL;
+ struct pf_srchash *sh = NULL;
+ uint16_t *nportp;
+ uint16_t low, high;
+ u_short reason;
+
+ PF_RULES_RASSERT();
+ KASSERT(r != NULL, ("r is NULL"));
+ KASSERT(!(r->rule_flag & PFRULE_AFTO), ("AFTO rule"));
+
+ if (*skp == NULL && *nkp == NULL) {
+ if (pf_state_key_setup(pd, pd->nsport, pd->ndport, skp, nkp))
+ return (PFRES_MEMORY);
+ }
naddr = &(*nkp)->addr[1];
nportp = &(*nkp)->port[1];
- switch (r->action) {
+ switch (nat_action) {
case PF_NAT:
if (pd->proto == IPPROTO_ICMP) {
low = 1;
high = 65535;
} else {
- low = r->rdr.proxy_port[0];
- high = r->rdr.proxy_port[1];
+ low = rpool->proxy_port[0];
+ high = rpool->proxy_port[1];
}
- if (r->rdr.mape.offset > 0) {
+ if (rpool->mape.offset > 0) {
if (pf_get_mape_sport(pd, r, naddr, nportp, &sn,
- &sh, udp_mapping)) {
+ &sh, udp_mapping, rpool)) {
DPFPRINTF(PF_DEBUG_MISC,
("pf: MAP-E port allocation (%u/%u/%u)"
" failed\n",
- r->rdr.mape.offset,
- r->rdr.mape.psidlen,
- r->rdr.mape.psid));
+ rpool->mape.offset,
+ rpool->mape.psidlen,
+ rpool->mape.psid));
reason = PFRES_MAPFAILED;
goto notrans;
}
} else if (pf_get_sport(pd, r, naddr, nportp, low, high, &sn,
- &sh, &r->rdr, udp_mapping, PF_SN_NAT)) {
+ &sh, rpool, udp_mapping, PF_SN_NAT)) {
DPFPRINTF(PF_DEBUG_MISC,
("pf: NAT proxy port allocation (%u-%u) failed\n",
- r->rdr.proxy_port[0], r->rdr.proxy_port[1]));
+ rpool->proxy_port[0], rpool->proxy_port[1]));
reason = PFRES_MAPFAILED;
goto notrans;
}
@@ -848,41 +869,39 @@ pf_get_translation(struct pf_pdesc *pd, int off,
case PF_BINAT:
switch (pd->dir) {
case PF_OUT:
- if (r->rdr.cur->addr.type == PF_ADDR_DYNIFTL){
+ if (rpool->cur->addr.type == PF_ADDR_DYNIFTL){
switch (pd->af) {
#ifdef INET
case AF_INET:
- if (r->rdr.cur->addr.p.dyn->
+ if (rpool->cur->addr.p.dyn->
pfid_acnt4 < 1) {
reason = PFRES_MAPFAILED;
goto notrans;
}
PF_POOLMASK(naddr,
- &r->rdr.cur->addr.p.dyn->
- pfid_addr4,
- &r->rdr.cur->addr.p.dyn->
- pfid_mask4, &pd->nsaddr, AF_INET);
+ &rpool->cur->addr.p.dyn->pfid_addr4,
+ &rpool->cur->addr.p.dyn->pfid_mask4,
+ &pd->nsaddr, AF_INET);
break;
#endif /* INET */
#ifdef INET6
case AF_INET6:
- if (r->rdr.cur->addr.p.dyn->
+ if (rpool->cur->addr.p.dyn->
pfid_acnt6 < 1) {
reason = PFRES_MAPFAILED;
goto notrans;
}
PF_POOLMASK(naddr,
- &r->rdr.cur->addr.p.dyn->
- pfid_addr6,
- &r->rdr.cur->addr.p.dyn->
- pfid_mask6, &pd->nsaddr, AF_INET6);
+ &rpool->cur->addr.p.dyn->pfid_addr6,
+ &rpool->cur->addr.p.dyn->pfid_mask6,
+ &pd->nsaddr, AF_INET6);
break;
#endif /* INET6 */
}
} else
PF_POOLMASK(naddr,
- &r->rdr.cur->addr.v.a.addr,
- &r->rdr.cur->addr.v.a.mask, &pd->nsaddr,
+ &rpool->cur->addr.v.a.addr,
+ &rpool->cur->addr.v.a.mask, &pd->nsaddr,
pd->af);
break;
case PF_IN:
@@ -925,30 +944,30 @@ pf_get_translation(struct pf_pdesc *pd, int off,
uint16_t cut, low, high, nport;
reason = pf_map_addr_sn(pd->af, r, &pd->nsaddr, naddr, NULL,
- NULL, &sn, &sh, &r->rdr, PF_SN_NAT);
+ NULL, &sn, &sh, rpool, PF_SN_NAT);
if (reason != 0)
goto notrans;
- if ((r->rdr.opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
- PF_POOLMASK(naddr, naddr, &r->rdr.cur->addr.v.a.mask,
+ if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_BITMASK)
+ PF_POOLMASK(naddr, naddr, &rpool->cur->addr.v.a.mask,
&pd->ndaddr, pd->af);
/* Do not change SCTP ports. */
if (pd->proto == IPPROTO_SCTP)
break;
- if (r->rdr.proxy_port[1]) {
+ if (rpool->proxy_port[1]) {
uint32_t tmp_nport;
tmp_nport = ((ntohs(pd->ndport) - ntohs(r->dst.port[0])) %
- (r->rdr.proxy_port[1] - r->rdr.proxy_port[0] +
- 1)) + r->rdr.proxy_port[0];
+ (rpool->proxy_port[1] - rpool->proxy_port[0] +
+ 1)) + rpool->proxy_port[0];
/* Wrap around if necessary. */
if (tmp_nport > 65535)
tmp_nport -= 65535;
nport = htons((uint16_t)tmp_nport);
- } else if (r->rdr.proxy_port[0])
- nport = htons(r->rdr.proxy_port[0]);
+ } else if (rpool->proxy_port[0])
+ nport = htons(rpool->proxy_port[0]);
else
nport = pd->ndport;
@@ -1023,7 +1042,6 @@ out:
/* Return success only if translation really happened. */
if (bcmp(*skp, *nkp, sizeof(struct pf_state_key_cmp))) {
- *rp = r;
return (PFRES_MATCH);
}
diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile
index e3110d0e5df7..db8b4f5130d7 100644
--- a/tests/sys/netpfil/pf/Makefile
+++ b/tests/sys/netpfil/pf/Makefile
@@ -21,7 +21,6 @@ ATF_TESTS_SH+= altq \
loginterface \
killstate \
macro \
- map_e \
match \
max_states \
mbuf \
diff --git a/tests/sys/netpfil/pf/map_e.sh b/tests/sys/netpfil/pf/map_e.sh
deleted file mode 100644
index 59f9e7f7e14c..000000000000
--- a/tests/sys/netpfil/pf/map_e.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# SPDX-License-Identifier: BSD-2-Clause
-#
-# Copyright (c) 2021 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-
-. $(atf_get_srcdir)/utils.subr
-
-atf_test_case "map_e" "cleanup"
-map_e_head()
-{
- atf_set descr 'map-e-portset test'
- atf_set require.user root
-}
-
-map_e_body()
-{
- NC_TRY_COUNT=12
-
- pft_init
-
- epair_map_e=$(vnet_mkepair)
- epair_echo=$(vnet_mkepair)
-
- vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a
- vnet_mkjail echo ${epair_echo}b
-
- ifconfig ${epair_map_e}a 192.0.2.2/24 up
- route add -net 198.51.100.0/24 192.0.2.1
-
- jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up
- jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up
- jexec map_e sysctl net.inet.ip.forwarding=1
-
- jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
- jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
-
- # Enable pf!
- jexec map_e pfctl -e
- pft_set_rules map_e \
- "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342"
-
- # Only allow specified ports.
- jexec echo pfctl -e
- pft_set_rules echo "block return all" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
- "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
- "set skip on lo"
-
- i=0
- while [ ${i} -lt ${NC_TRY_COUNT} ]
- do
- echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
- if [ $? -ne 0 ]; then
- atf_fail "nc failed (${i})"
- fi
- i=$((${i}+1))
- done
-}
-
-map_e_cleanup()
-{
- pft_cleanup
-}
-
-atf_init_test_cases()
-{
- atf_add_test_case "map_e"
-}
diff --git a/tests/sys/netpfil/pf/nat.sh b/tests/sys/netpfil/pf/nat.sh
index f7026feb5078..f1fdf6405d97 100644
--- a/tests/sys/netpfil/pf/nat.sh
+++ b/tests/sys/netpfil/pf/nat.sh
@@ -2,6 +2,8 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2018 Kristof Provost <kp@FreeBSD.org>
+# Copyright (c) 2025 Kajetan Staszkiewicz <ks@FreeBSD.org>
+# Copyright (c) 2021 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -112,14 +114,7 @@ nested_anchor_body()
}
-atf_test_case "endpoint_independent" "cleanup"
-endpoint_independent_head()
-{
- atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
- atf_set require.user root
-}
-
-endpoint_independent_body()
+endpoint_independent_setup()
{
pft_init
filter="udp and dst port 1234" # only capture udp pings
@@ -153,13 +148,15 @@ endpoint_independent_body()
jexec server1 ifconfig ${epair_server1}a 198.51.100.32/24 up
jexec server2 ifconfig ${epair_server2}a 198.51.100.22/24 up
+}
+endpoint_independent_common()
+{
# Enable pf!
jexec nat pfctl -e
# validate non-endpoint independent nat rule behaviour
- pft_set_rules nat \
- "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)"
+ pft_set_rules nat "${1}"
jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
--immediate-mode $filter &
@@ -198,8 +195,7 @@ endpoint_independent_body()
fi
# validate endpoint independent nat rule behaviour
- pft_set_rules nat \
- "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent"
+ pft_set_rules nat "${2}"
jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
--immediate-mode $filter &
@@ -238,7 +234,47 @@ endpoint_independent_body()
fi
}
-endpoint_independent_cleanup()
+atf_test_case "endpoint_independent_compat" "cleanup"
+endpoint_independent_compat_head()
+{
+ atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
+ atf_set require.user root
+}
+
+endpoint_independent_compat_body()
+{
+ endpoint_independent_setup # Sets ${epair_…} variables
+
+ endpoint_independent_common \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" \
+ "nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent"
+}
+
+endpoint_independent_compat_cleanup()
+{
+ pft_cleanup
+ rm -f server1.out
+ rm -f server2.out
+}
+
+atf_test_case "endpoint_independent_pass" "cleanup"
+endpoint_independent_pass_head()
+{
+ atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
+ atf_set require.user root
+}
+
+endpoint_independent_pass_body()
+{
+ endpoint_independent_setup # Sets ${epair_…} variables
+
+ endpoint_independent_common \
+ "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) keep state" \
+ "pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) endpoint-independent keep state"
+
+}
+
+endpoint_independent_pass_cleanup()
{
pft_cleanup
rm -f server1.out
@@ -438,14 +474,324 @@ no_addrs_random_cleanup()
pft_cleanup
}
+nat_pass_head()
+{
+ atf_set descr 'IPv4 NAT on pass rule'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+nat_pass_body()
+{
+ setup_router_server_ipv4
+ # Delete the route back to make sure that the traffic has been NAT-ed
+ jexec server route del -net ${net_tester} ${net_server_host_router}
+
+ pft_set_rules router \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp keep state" \
+ "pass out on ${epair_server}a inet proto tcp nat-to ${epair_server}a keep state"
+
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ jexec router pfctl -qvvsr
+ jexec router pfctl -qvvss
+ jexec router ifconfig
+ jexec router netstat -rn
+}
+
+nat_pass_cleanup()
+{
+ pft_cleanup
+}
+
+nat_match_head()
+{
+ atf_set descr 'IPv4 NAT on match rule'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+nat_match_body()
+{
+ setup_router_server_ipv4
+ # Delete the route back to make sure that the traffic has been NAT-ed
+ jexec server route del -net ${net_tester} ${net_server_host_router}
+
+ # NAT is applied during ruleset evaluation:
+ # rules after "match" match on NAT-ed address
+ pft_set_rules router \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp keep state" \
+ "match out on ${epair_server}a inet proto tcp nat-to ${epair_server}a" \
+ "pass out on ${epair_server}a inet proto tcp from ${epair_server}a keep state"
+
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ jexec router pfctl -qvvsr
+ jexec router pfctl -qvvss
+ jexec router ifconfig
+ jexec router netstat -rn
+}
+
+nat_match_cleanup()
+{
+ pft_cleanup
+}
+
+map_e_common()
+{
+ NC_TRY_COUNT=12
+
+ pft_init
+
+ epair_map_e=$(vnet_mkepair)
+ epair_echo=$(vnet_mkepair)
+
+ vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a
+ vnet_mkjail echo ${epair_echo}b
+
+ ifconfig ${epair_map_e}a 192.0.2.2/24 up
+ route add -net 198.51.100.0/24 192.0.2.1
+
+ jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up
+ jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up
+ jexec map_e sysctl net.inet.ip.forwarding=1
+
+ jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
+ jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
+
+ # Enable pf!
+ jexec map_e pfctl -e
+}
+
+atf_test_case "map_e_compat" "cleanup"
+map_e_compat_head()
+{
+ atf_set descr 'map-e-portset test'
+ atf_set require.user root
+}
+
+map_e_compat_body()
+{
+ map_e_common
+
+ pft_set_rules map_e \
+ "nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342"
+
+ # Only allow specified ports.
+ jexec echo pfctl -e
+ pft_set_rules echo "block return all" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
+ "set skip on lo"
+
+ i=0
+ while [ ${i} -lt ${NC_TRY_COUNT} ]
+ do
+ echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
+ if [ $? -ne 0 ]; then
+ atf_fail "nc failed (${i})"
+ fi
+ i=$((${i}+1))
+ done
+}
+
+map_e_compat_cleanup()
+{
+ pft_cleanup
+}
+
+
+atf_test_case "map_e_pass" "cleanup"
+map_e_pass_head()
+{
+ atf_set descr 'map-e-portset test'
+ atf_set require.user root
+}
+
+map_e_pass_body()
+{
+ map_e_common
+
+ pft_set_rules map_e \
+ "pass out on ${epair_echo}a inet from 192.0.2.0/24 to any nat-to (${epair_echo}a) map-e-portset 2/12/0x342 keep state"
+
+ jexec map_e pfctl -qvvsr
+
+ # Only allow specified ports.
+ jexec echo pfctl -e
+ pft_set_rules echo "block return all" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
+ "pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
+ "set skip on lo"
+
+ i=0
+ while [ ${i} -lt ${NC_TRY_COUNT} ]
+ do
+ echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
+ if [ $? -ne 0 ]; then
+ atf_fail "nc failed (${i})"
+ fi
+ i=$((${i}+1))
+ done
+}
+
+map_e_pass_cleanup()
+{
+ pft_cleanup
+}
+
+binat_compat_head()
+{
+ atf_set descr 'IPv4 BINAT with nat ruleset'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+binat_compat_body()
+{
+ setup_router_server_ipv4
+ # Delete the route back to make sure that the traffic has been NAT-ed
+ jexec server route del -net ${net_tester} ${net_server_host_router}
+
+ pft_set_rules router \
+ "set state-policy if-bound" \
+ "set ruleset-optimization none" \
+ "binat on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag -> ${epair_server}a" \
+ "block" \
+ "pass in on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
+ "pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass in on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
+
+ # Test the outbound NAT part of BINAT.
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 1" \
+ "${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 2" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Test the inbound RDR part of BINAT.
+ # The "tester" becomes "server" and vice versa.
+ inetd_conf=$(mktemp)
+ echo "discard stream tcp nowait root internal" > $inetd_conf
+ inetd -p ${PWD}/inetd_tester.pid $inetd_conf
+
+ atf_check -s exit:0 \
+ jexec server ${common_dir}/pft_ping.py \
+ --ping-type=tcp3way --send-sport=4202 \
+ --sendif ${epair_server}b \
+ --to ${net_server_host_router} \
+ --replyif ${epair_server}b
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 3" \
+ "${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 4" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+}
+
+binat_compat_cleanup()
+{
+ pft_cleanup
+ kill $(cat ${PWD}/inetd_tester.pid)
+}
+
+binat_match_head()
+{
+ atf_set descr 'IPv4 BINAT with nat ruleset'
+ atf_set require.user root
+ atf_set require.progs scapy
+}
+
+binat_match_body()
+{
+ setup_router_server_ipv4
+ # Delete the route back to make sure that the traffic has been NAT-ed
+ jexec server route del -net ${net_tester} ${net_server_host_router}
+
+ # The "binat-to" rule expands to 2 rules so the ""pass" rules start at 3!
+ pft_set_rules router \
+ "set state-policy if-bound" \
+ "set ruleset-optimization none" \
+ "block" \
+ "match on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag binat-to ${epair_server}a" \
+ "pass in on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
+ "pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass in on ${epair_server}a inet proto tcp tagged sometag keep state" \
+ "pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
+
+ # Test the outbound NAT part of BINAT.
+ ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 3" \
+ "${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 4" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Test the inbound RDR part of BINAT.
+ # The "tester" becomes "server" and vice versa.
+ inetd_conf=$(mktemp)
+ echo "discard stream tcp nowait root internal" > $inetd_conf
+ inetd -p ${PWD}/inetd_tester.pid $inetd_conf
+
+ atf_check -s exit:0 \
+ jexec server ${common_dir}/pft_ping.py \
+ --ping-type=tcp3way --send-sport=4202 \
+ --sendif ${epair_server}b \
+ --to ${net_server_host_router} \
+ --replyif ${epair_server}b
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+
+ for state_regexp in \
+ "${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 5" \
+ "${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 6" \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+}
+
+binat_match_cleanup()
+{
+ pft_cleanup
+ kill $(cat ${PWD}/inetd_tester.pid)
+}
+
atf_init_test_cases()
{
atf_add_test_case "exhaust"
atf_add_test_case "nested_anchor"
- atf_add_test_case "endpoint_independent"
+ atf_add_test_case "endpoint_independent_compat"
+ atf_add_test_case "endpoint_independent_pass"
atf_add_test_case "nat6_nolinklocal"
atf_add_test_case "empty_table_source_hash"
atf_add_test_case "no_addrs_source_hash"
atf_add_test_case "empty_table_random"
atf_add_test_case "no_addrs_random"
+ atf_add_test_case "map_e_compat"
+ atf_add_test_case "map_e_pass"
+ atf_add_test_case "nat_pass"
+ atf_add_test_case "nat_match"
+ atf_add_test_case "binat_compat"
+ atf_add_test_case "binat_match"
}
diff --git a/tests/sys/netpfil/pf/rdr.sh b/tests/sys/netpfil/pf/rdr.sh
index a7a8c77c0515..4c08b4973891 100644
--- a/tests/sys/netpfil/pf/rdr.sh
+++ b/tests/sys/netpfil/pf/rdr.sh
@@ -27,14 +27,6 @@
. $(atf_get_srcdir)/utils.subr
-atf_test_case "tcp_v6" "cleanup"
-tcp_v6_head()
-{
- atf_set descr 'TCP rdr with IPv6'
- atf_set require.user root
- atf_set require.progs python3
-}
-
#
# Test that rdr works for TCP with IPv6.
#
@@ -47,7 +39,7 @@ tcp_v6_head()
#
# Test for incorrect checksums after the rewrite by looking at a packet capture (see bug 210860)
#
-tcp_v6_body()
+tcp_v6_setup()
{
pft_init
@@ -83,9 +75,11 @@ tcp_v6_body()
jexec ${j}c route add -inet6 2001:db8:a::0/64 2001:db8:b::1
jexec ${j}b pfctl -e
+}
- pft_set_rules ${j}b \
- "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+tcp_v6_common()
+{
+ pft_set_rules ${j}b "${1}"
# Check that a can reach c over the router
atf_check -s exit:0 -o ignore \
@@ -116,19 +110,44 @@ tcp_v6_body()
atf_check_equal " 0" "$count"
}
-tcp_v6_cleanup()
+atf_test_case "tcp_v6_compat" "cleanup"
+tcp_v6_compat_head()
{
- pft_cleanup
+ atf_set descr 'TCP rdr with IPv6 with NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3
}
+tcp_v6_compat_body()
+{
+ tcp_v6_setup # Sets ${epair_…} variables
+ tcp_v6_common \
+ "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+}
-atf_test_case "srcport" "cleanup"
-srcport_head()
+tcp_v6_compat_cleanup()
{
- atf_set descr 'TCP rdr srcport modulation'
+ pft_cleanup
+}
+
+atf_test_case "tcp_v6_pass" "cleanup"
+tcp_v6_pass_head()
+{
+ atf_set descr 'TCP rdr with IPv6 with pass/match rules'
atf_set require.user root
atf_set require.progs python3
- atf_set timeout 9999
+}
+
+tcp_v6_pass_body()
+{
+ tcp_v6_setup # Sets ${epair_…} variables
+ tcp_v6_common \
+ "rdr on ${epair_one}a proto tcp from any to any port 80 -> 2001:db8:b::2 port 8000"
+}
+
+tcp_v6_pass_cleanup()
+{
+ pft_cleanup
}
#
@@ -145,7 +164,7 @@ srcport_head()
# In this case, the rdr rule should also rewrite the source port (again) to
# resolve the state conflict.
#
-srcport_body()
+srcport_setup()
{
pft_init
@@ -188,14 +207,17 @@ srcport_body()
jexec ${j}c sysctl net.inet.ip.forwarding=1
jexec ${j}b pfctl -e
jexec ${j}c pfctl -e
+}
+srcport_common()
+{
pft_set_rules ${j}b \
"set debug misc" \
- "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port"
+ "${1}"
pft_set_rules ${j}c \
"set debug misc" \
- "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888"
+ "${2}"
jexec ${j}a route add default 198.51.100.1
jexec ${j}c route add 198.51.100.0/24 198.51.101.2
@@ -215,13 +237,54 @@ srcport_body()
atf_check -o match:"[0-9]+" -o not-inline:"1234" cat port3
}
-srcport_cleanup()
+atf_test_case "srcport_compat" "cleanup"
+srcport_compat_head()
+{
+ atf_set descr 'TCP rdr srcport modulation with NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3
+ atf_set timeout 9999
+}
+
+srcport_compat_body()
+{
+ srcport_setup # Sets ${epair_…} variables
+ srcport_common \
+ "nat on ${epair2}a inet from 198.51.100.0/24 to any -> ${epair2}a static-port" \
+ "rdr on ${epair2}b proto tcp from any to ${epair2}b port 7777 -> 203.0.113.50 port 8888"
+}
+
+srcport_compat_cleanup()
+{
+ pft_cleanup
+}
+
+atf_test_case "srcport_pass" "cleanup"
+srcport_pass_head()
+{
+ atf_set descr 'TCP rdr srcport modulation with pass/match rules'
+ atf_set require.user root
+ atf_set require.progs python3
+ atf_set timeout 9999
+}
+
+srcport_pass_body()
+{
+ srcport_setup # Sets ${epair_…} variables
+ srcport_common \
+ "pass out on ${epair2}a inet from 198.51.100.0/24 to any nat-to ${epair2}a static-port" \
+ "pass in on ${epair2}b proto tcp from any to ${epair2}b port 7777 rdr-to 203.0.113.50 port 8888"
+}
+
+srcport_pass_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
- atf_add_test_case "tcp_v6"
- atf_add_test_case "srcport"
+ atf_add_test_case "tcp_v6_compat"
+ atf_add_test_case "tcp_v6_pass"
+ atf_add_test_case "srcport_compat"
+ atf_add_test_case "srcport_pass"
}
diff --git a/tests/sys/netpfil/pf/src_track.sh b/tests/sys/netpfil/pf/src_track.sh
index 3668898682ff..c24f88062c4d 100755
--- a/tests/sys/netpfil/pf/src_track.sh
+++ b/tests/sys/netpfil/pf/src_track.sh
@@ -307,14 +307,14 @@ max_src_states_global_cleanup()
pft_cleanup
}
-route_to_head()
+sn_types_compat_head()
{
- atf_set descr 'Max states per source per rule with route-to'
+ atf_set descr 'Combination of source node types with compat NAT rules'
atf_set require.user root
atf_set require.progs python3 scapy
}
-route_to_body()
+sn_types_compat_body()
{
setup_router_dummy_ipv6
@@ -398,11 +398,110 @@ route_to_body()
! grep -q 'filter rule 3' $nodes || atf_fail "Source node found for rule 3"
}
-route_to_cleanup()
+sn_types_compat_cleanup()
{
pft_cleanup
}
+sn_types_pass_head()
+{
+ atf_set descr 'Combination of source node types with pass NAT rules'
+ atf_set require.user root
+ atf_set require.progs python3 scapy
+}
+
+sn_types_pass_body()
+{
+ setup_router_dummy_ipv6
+
+ # Clients will connect from another network behind the router.
+ # This allows for using multiple source addresses.
+ jexec router route add -6 2001:db8:44::0/64 2001:db8:42::2
+
+ # Additional gateways for route-to.
+ rtgw=${net_server_host_server%::*}::2:1
+ jexec router ndp -s ${rtgw} 00:01:02:03:04:05
+
+ # This test will check for proper source node creation for:
+ # max-src-states -> PF_SN_LIMIT
+ # sticky-address -> PF_SN_NAT
+ # route-to -> PF_SN_ROUTE
+ # The test expands to all 8 combinations of those source nodes being
+ # present or not.
+
+ pft_set_rules router \
+ "table <rtgws> { ${rtgw} }" \
+ "table <rdrgws> { 2001:db8:45::1 }" \
+ "block" \
+ "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
+ "match in on ${epair_tester}b inet6 proto tcp from 2001:db8:44::10/124 to 2001:db8:45::1 rdr-to <rdrgws> port 4242 sticky-address label rule_3" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) inet6 proto tcp from port 4211 keep state label rule_4" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) sticky-address inet6 proto tcp from port 4212 keep state label rule_5" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) inet6 proto tcp from port 4213 keep state (max-src-states 3 source-track rule) label rule_6" \
+ "pass in quick on ${epair_tester}b route-to ( ${epair_server}a <rtgws>) sticky-address inet6 proto tcp from port 4214 keep state (max-src-states 3 source-track rule) label rule_7" \
+ "pass out quick on ${epair_server}a keep state"
+
+ # We don't check if state limits are properly enforced, this is tested
+ # by other tests in this file.
+ # Source address will not match the NAT rule
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::01 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::02 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::03 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::04 --to 2001:db8:45::1
+ # Source address will match the NAT rule
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4211 --fromaddr 2001:db8:44::11 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4212 --fromaddr 2001:db8:44::12 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4213 --fromaddr 2001:db8:44::13 --to 2001:db8:45::1
+ ping_dummy_check_request exit:0 --ping-type=tcpsyn --send-sport=4214 --fromaddr 2001:db8:44::14 --to 2001:db8:45::1
+
+ states=$(mktemp) || exit 1
+ jexec router pfctl -qvss | normalize_pfctl_s > $states
+ nodes=$(mktemp) || exit 1
+ jexec router pfctl -qvvsS | normalize_pfctl_s > $nodes
+
+ echo " === states ==="
+ cat $states
+ echo " === nodes ==="
+ cat $nodes
+ echo " === end === "
+
+ # Order of states in output is not guaranteed, find each one separately.
+ for state_regexp in \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::1\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::2\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, route sticky-address$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::3\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track$' \
+ 'all tcp 2001:db8:45::1\[9\] <- 2001:db8:44::4\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, route sticky-address$' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::11\[4211\] .* 1:0 pkts, 76:0 bytes, rule 4, NAT/RDR sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::12\[4212\] .* 1:0 pkts, 76:0 bytes, rule 5, NAT/RDR sticky-address, route sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::13\[4213\] .* 1:0 pkts, 76:0 bytes, rule 6, limit source-track, NAT/RDR sticky-address' \
+ 'all tcp 2001:db8:45::1\[4242\] \(2001:db8:45::1\[9\]\) <- 2001:db8:44::14\[4214\] .* 1:0 pkts, 76:0 bytes, rule 7, limit source-track, NAT/RDR sticky-address, route sticky-address' \
+ ; do
+ grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
+ done
+
+ # Order of source nodes in output is not guaranteed, find each one separately.
+ for node_regexp in \
+ '2001:db8:44::2 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \
+ '2001:db8:44::3 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \
+ '2001:db8:44::4 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \
+ '2001:db8:44::4 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \
+ '2001:db8:44::11 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::12 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::12 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 5, route sticky-address' \
+ '2001:db8:44::13 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::13 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 6, limit source-track' \
+ '2001:db8:44::14 -> 2001:db8:45::1 \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 3, NAT/RDR sticky-address' \
+ '2001:db8:44::14 -> 2001:db8:43::2:1 \( states 1, connections 0, rate 0.0/0s ) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, route sticky-address' \
+ '2001:db8:44::14 -> :: \( states 1, connections 0, rate 0.0/0s \) age [0-9:]+, 1 pkts, 76 bytes, filter rule 7, limit source-track' \
+ ; do
+ grep -qE "${node_regexp}" $nodes || atf_fail "Source node not found for '${node_regexp}'"
+ done
+}
+
+sn_types_pass_cleanup()
+{
+ pft_cleanup
+}
atf_init_test_cases()
{
@@ -411,5 +510,6 @@ atf_init_test_cases()
atf_add_test_case "max_src_conn_rule"
atf_add_test_case "max_src_states_rule"
atf_add_test_case "max_src_states_global"
- atf_add_test_case "route_to"
+ atf_add_test_case "sn_types_compat"
+ atf_add_test_case "sn_types_pass"
}