Page MenuHomeFreeBSD

D49221.diff
No OneTemporary

D49221.diff

diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -245,6 +245,7 @@
struct range rport;
struct pool_opts pool_opts;
int af;
+ bool binat;
};
static struct filter_opts {
@@ -381,7 +382,11 @@
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 @@
%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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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_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 @@
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 @@
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 @@
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 @@
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 @@
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
-expand_rule(struct pfctl_rule *r,
+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, bool keeprule,
struct node_if *interfaces, struct redirspec *nat,
struct redirspec *rdr, struct redirspec *route,
struct node_proto *protos,
@@ -6308,18 +6477,26 @@
}
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_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);
+
+ if (nat && nat->binat)
+ error += check_binat_redirspec(src_host, r, af);
}
- error += apply_redirspec(&(r->nat), nat);
- error += apply_redirspec(&(r->rdr), rdr);
- error += apply_redirspec(&(r->route), route);
-
- r->nat.proxy_port[0] = PF_NAT_PROXY_PORT_LOW;
- r->nat.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH;
-
if (rule_consistent(r, anchor_call[0]) < 0 || error)
yyerror("skipping rule due to errors");
else {
@@ -6328,6 +6505,22 @@
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 @@
))))))))));
- 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 @@
{ "bandwidth", BANDWIDTH},
{ "binat", BINAT},
{ "binat-anchor", BINATANCHOR},
+ { "binat-to", BINATTO},
{ "bitmask", BITMASK},
{ "block", BLOCK},
{ "block-policy", BLOCKPOLICY},
@@ -6508,6 +6704,7 @@
{ "modulate", MODULATE},
{ "nat", NAT},
{ "nat-anchor", NATANCHOR},
+ { "nat-to", NATTO},
{ "no", NO},
{ "no-df", NODF},
{ "no-route", NOROUTE},
@@ -6532,6 +6729,7 @@
{ "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
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1739,7 +1739,6 @@
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
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -1240,25 +1240,34 @@
}
#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
--- 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
--- 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
--- 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
--- 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
--- a/sbin/pfctl/tests/files/pf0019.in
+++ b/sbin/pfctl/tests/files/pf0019.in
@@ -3,7 +3,7 @@
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
--- a/sbin/pfctl/tests/files/pf0019.ok
+++ b/sbin/pfctl/tests/files/pf0019.ok
@@ -2,3 +2,12 @@
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
--- a/sbin/pfctl/tests/files/pf0020.in
+++ b/sbin/pfctl/tests/files/pf0020.in
@@ -5,5 +5,5 @@
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
--- a/sbin/pfctl/tests/files/pf0020.ok
+++ b/sbin/pfctl/tests/files/pf0020.ok
@@ -2,3 +2,15 @@
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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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
--- 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 @@
.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.
+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
-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
+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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -2711,6 +2711,13 @@
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
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -348,7 +348,7 @@
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 @@
} \
} 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 @@
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 @@
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 @@
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 @@
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_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 @@
/* 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/*
* 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 @@
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
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -2215,17 +2215,31 @@
}
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))
- error = EINVAL;
- if (rule->action == PF_PASS && rule->rdr.opts & PF_POOL_STICKYADDR &&
- !rule->keep_state) {
+ /*
+ * 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->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
--- a/sys/netpfil/pf/pf_lb.c
+++ b/sys/netpfil/pf/pf_lb.c
@@ -75,7 +75,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
/* 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
--- a/tests/sys/netpfil/pf/Makefile
+++ b/tests/sys/netpfil/pf/Makefile
@@ -21,7 +21,6 @@
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
--- 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
--- 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 @@
}
-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 @@
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 @@
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 @@
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 @@
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
--- 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 @@
#
# 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 @@
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 @@
atf_check_equal " 0" "$count"
}
-tcp_v6_cleanup()
+atf_test_case "tcp_v6_compat" "cleanup"
+tcp_v6_compat_head()
+{
+ 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"
+}
+
+tcp_v6_compat_cleanup()
{
pft_cleanup
}
-
-atf_test_case "srcport" "cleanup"
-srcport_head()
+atf_test_case "tcp_v6_pass" "cleanup"
+tcp_v6_pass_head()
{
- atf_set descr 'TCP rdr srcport modulation'
+ 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 @@
# 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 @@
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 @@
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
--- a/tests/sys/netpfil/pf/src_track.sh
+++ b/tests/sys/netpfil/pf/src_track.sh
@@ -307,14 +307,14 @@
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 @@
! 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_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"
}

File Metadata

Mime Type
text/plain
Expires
Tue, Apr 29, 8:08 PM (3 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17843961
Default Alt Text
D49221.diff (100 KB)

Event Timeline