Hi,

Looking at some corner-cases I've realised that collapse_redirspec
does a poor job of weeding out incompatibilities in the redirect
pool specifications and hence inferring the rule address family
from it.

An example is a rule like this:

 pass out nat-to em0

Let's say that em0 has an IPv4 address 192.168.20.221 and an IPv6
link-local address fe80::221:86ff:fefa:5fc1.  What this rule is
supposed to do?  Since it cannot create two rules out of this
specification (the parser works), you have to choose whether this
will be an IPv4 rule or an IPv6 rule.  Quite some time ago for
some reasons it translated to an IPv4 rule:

 pass out inet nat-to 192.168.20.221

Then the behaviour was changed (rather silently) and now it
translates to an IPv6 rule:

 pass out inet6 nat-to fe80::221:86ff:fefa:5fc1

I think that both is somewhat unexpected, even less so the
current behaviour.

I propose to avoid the confusion by flagging such situations as
errors, e.g.:

 % echo 'pass out nat-to { ::1 1.1.1.1 }' | ./obj/pfctl -o none -vnf - 
 stdin:1: translation spec contains addresses with different address families
 stdin:1: skipping rule due to errors
 stdin:1: rule expands to no valid combination

While previously it would pick only one of them (the first one):

 % echo 'pass out nat-to { ::1 1.1.1.1 }' | pfctl -o none -vnf -      
 pass out inet6 all flags S/SA nat-to ::1

You are still able to use "em0" as a source/destination filter
however:

 % echo 'pass out from em0' | pfctl -o none -vnf -
 pass out on em0 inet6 from fe80::221:86ff:fefa:5fc1 to any flags S/SA
 pass out inet from 192.168.20.221 to any flags S/SA

meaning the diff only affects how nat-to/rdr-to pools are processed.

I have carefully tested that and do not expect any unrelated
fallout.  And for the reasons stated above I don't believe
anyone is using this since it's largely error prone.

OK?

P.S.

binat bzero'ing is actually necessary since otherwise it
contains garbage and we're now checking for it more carefully.

diff --git sbin/pfctl/parse.y sbin/pfctl/parse.y
index de9537e..ce80f8e 100644
--- sbin/pfctl/parse.y
+++ sbin/pfctl/parse.y
@@ -4295,11 +4295,11 @@ collapse_redirspec(struct pf_pool *rpool, struct 
pf_rule *r,
     struct redirspec *rs, u_int8_t allow_if)
 {
        struct pf_opt_tbl *tbl = NULL;
        struct node_host *h;
        struct pf_rule_addr ra;
-       int     i = 0;
+       int af = 0, i = 0;
 
        if (!rs || !rs->rdr || rs->rdr->host == NULL) {
                rpool->addr.type = PF_ADDR_NONE;
                return (0);
        }
@@ -4307,17 +4307,45 @@ collapse_redirspec(struct pf_pool *rpool, struct 
pf_rule *r,
        if (r->rule_flag & PFRULE_AFTO)
                r->naf = rs->af;
 
        /* count matching addresses */
        for (h = rs->rdr->host; h != NULL; h = h->next) {
-               if (!r->af || !h->af || rs->af || h->af == r->af) {
+               if (h->af) {
+                       /* check that af-to target address has correct af */
+                       if (rs->af && rs->af != h->af) {
+                               yyerror("af mismatch in %s spec",
+                                   allow_if ? "routing" : "translation");
+                               return (1);
+                       }
+                       /* skip if the rule af doesn't match redirect af */
+                       if ((r->af && r->af != h->af) && /* exclude af-to */
+                           !(r->naf && h->af == r->naf))
+                               continue;
+                       /*
+                        * fail if the chosen af is not universal for the
+                        * whole supplied redirect address pool
+                        */
+                       if (!r->af && af && af != h->af) {
+                               yyerror("%s spec contains addresses with "
+                                   "different address families",
+                                   allow_if ? "routing" : "translation");
+                               return (1);
+                       }
+                       if (!r->af)
+                               af = h->af;
                        i++;
-                       if (h->af && !r->af)
-                               r->af = h->af;
-               } else if (r->naf && h->af == r->naf)
+               } else {
+                       /*
+                        * we silently allow any not af-specific host specs,
+                        * e.g. (em0) and let the kernel deal with them
+                        */
                        i++;
+               }
        }
+       /* set rule af to one chosen above */
+       if (!r->af && af)
+               r->af = af;
 
        if (i == 0) {           /* no pool address */
                yyerror("af mismatch in %s spec",
                    allow_if ? "routing" : "translation");
                return (1);
@@ -4695,10 +4723,11 @@ expand_rule(struct pf_rule *r, int keeprule, struct 
node_if *interfaces,
                        bcopy(&rb.nat.addr, &dsth->addr, sizeof(dsth->addr));
                        dsth->ifname = NULL;
                        dsth->next = NULL;
                        dsth->tail = NULL;
 
+                       bzero(&binat, sizeof(binat));
                        if ((binat.rdr =
                            calloc(1, sizeof(*binat.rdr))) == NULL)
                                err(1, "expand_rule: calloc");
                        bcopy(nat->rdr, binat.rdr, sizeof(*binat.rdr));
                        bcopy(&nat->pool_opts, &binat.pool_opts,

Reply via email to