Hi tech,

MAP-E (RFC 7597) transports IPv4 packets across an IPv6 network using
IP encapsulation. MAP-E uses a single IPv4 address for multiple
customers. To distinguish customers at Border Relay, each Customer
Edge must use an exclusive port set. This patch provides an option to
specify the port set used in MAP-E Customer Edge.

I made this patch by referring to the following FreeBSD patch with the
same intention (Though internal codes are different, and thus the
patches are also different).
https://reviews.freebsd.org/D29468

(This patch is also available at
https://github.com/toru-mano/src/compare/master...toru-mano:mape)

Regards,
Toru

diff --git regress/sbin/pfctl/Makefile regress/sbin/pfctl/Makefile
index 7813ad3d7a3..d6436681e27 100644
--- regress/sbin/pfctl/Makefile
+++ regress/sbin/pfctl/Makefile
@@ -17,10 +17,10 @@ PFTESTS=1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
 PFTESTS+=28 29 30 31 32 34 35 36 38 39 40 41 44 46 47 48 49 50
 PFTESTS+=52 53 54 55 56 57 60 61 65 66 67 68 69 70 71 72 73
 PFTESTS+=74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
-PFTESTS+=97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
+PFTESTS+=97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 114
 PFFAIL=1 2 3 4 5 6 7 8 11 12 13 14 15 16 17 19 20 23 25 27
 PFFAIL+=30 37 38 39 40 41 42 43 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
-PFFAIL+=63 64 65 66 67
+PFFAIL+=63 64 65 66 67 68
 PFSIMPLE=1 2
 PFSETUP=1 4
 PFLOAD=1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 17 18 19 20 23 24 25 26 27 28 29
diff --git regress/sbin/pfctl/pf114.in regress/sbin/pfctl/pf114.in
new file mode 100644
index 00000000000..d218c215681
--- /dev/null
+++ regress/sbin/pfctl/pf114.in
@@ -0,0 +1 @@
+pass out on lo0 nat-to (lo0) map-e-portset 1/2/3
diff --git regress/sbin/pfctl/pf114.ok regress/sbin/pfctl/pf114.ok
new file mode 100644
index 00000000000..55555fb041c
--- /dev/null
+++ regress/sbin/pfctl/pf114.ok
@@ -0,0 +1 @@
+pass out on lo0 all flags S/SA nat-to (lo0) round-robin map-e-portset 1/2/3
diff --git regress/sbin/pfctl/pfail68.in regress/sbin/pfctl/pfail68.in
new file mode 100644
index 00000000000..d8ac607f05f
--- /dev/null
+++ regress/sbin/pfctl/pfail68.in
@@ -0,0 +1,9 @@
+match out on lo0 nat-to (lo0) map-e-portset 1/2/3 map-e-portset 4/5/6
+match out on lo0 nat-to (lo0) map-e-portset 0/2/3
+match out on lo0 nat-to (lo0) map-e-portset 16/2/3
+match out on lo0 nat-to (lo0) map-e-portset 8/10/3
+match out on lo0 nat-to (lo0) map-e-portset 1/2/-1
+match out on lo0 nat-to (lo0) map-e-portset 1/10/1024
+match out on lo0 rdr-to (lo0) map-e-portset 1/2/3
+match out on lo0 nat-to (lo0) static-port map-e-portset 1/2/3
+match out on lo0 nat-to (lo0) port 1000:2000 map-e-portset 1/2/3
diff --git regress/sbin/pfctl/pfail68.ok regress/sbin/pfctl/pfail68.ok
new file mode 100644
index 00000000000..314cdcc5dde
--- /dev/null
+++ regress/sbin/pfctl/pfail68.ok
@@ -0,0 +1,15 @@
+stdin:1: map-e-portset cannot be redefined
+stdin:2: MAP-E PSID offset must be 1-15: 0
+stdin:3: MAP-E PSID offset must be 1-15: 16
+stdin:4: invalid MAP-E PSID length: 10
+stdin:5: invalid MAP-E PSID: -1
+stdin:6: invalid MAP-E PSID: 1024
+stdin:7: the 'map-e-portset' option is only valid with nat rules
+stdin:7: skipping rule due to errors
+stdin:7: rule expands to no valid combination
+stdin:8: the 'map-e-portset' option can't be used with 'static-port'
+stdin:8: skipping rule due to errors
+stdin:8: rule expands to no valid combination
+stdin:9: the 'map-e-portset' option can't be used when specifying a port range
+stdin:9: skipping rule due to errors
+stdin:9: rule expands to no valid combination
diff --git sbin/pfctl/parse.y sbin/pfctl/parse.y
index a10bc6f315c..894e59eea0c 100644
--- sbin/pfctl/parse.y
+++ sbin/pfctl/parse.y
@@ -213,6 +213,7 @@ struct pool_opts {
     int             type;
     int             staticport;
     struct pf_poolhashkey    *key;
+    struct pf_mape_port     mape;

 } pool_opts;

@@ -478,6 +479,7 @@ int    parseport(char *, struct range *r, int);
 %token    SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
 %token    ANTISPOOF FOR INCLUDE MATCHES
 %token    BITMASK RANDOM SOURCEHASH ROUNDROBIN LEASTSTATES STATICPORT PROBABILITY
+%token    MAPEPORTSET
 %token    WEIGHT BANDWIDTH FLOWS QUANTUM
 %token    QUEUE PRIORITY QLIMIT RTABLE RDOMAIN MINIMUM BURST PARENT
 %token    LOAD RULESET_OPTIMIZATION RTABLE RDOMAIN PRIO ONCE DEFAULT DELAY
@@ -2622,7 +2624,7 @@ xhost        : not host            {
             $$->tail = $$;
         }
         ;
-
+
 optweight    : WEIGHT NUMBER            {
             if ($2 < 1 || $2 > USHRT_MAX) {
                 yyerror("weight out of range");
@@ -3525,7 +3527,7 @@ portstar    : numberstring            {
         }
         ;

-redirspec    : host optweight        {
+redirspec    : host optweight        {
             if ($2 > 0) {
                 struct node_host    *n;
                 for (n = $1; n != NULL; n = n->next)
@@ -3692,6 +3694,29 @@ pool_opt    : BITMASK    {
             pool_opts.marker |= POM_STICKYADDRESS;
             pool_opts.opts |= PF_POOL_STICKYADDR;
         }
+        | MAPEPORTSET number '/' number '/' number {
+            if (pool_opts.mape.offset) {
+                yyerror("map-e-portset cannot be redefined");
+                YYERROR;
+            }
+            if ($2 <= 0 || $2 >= 16) {
+                yyerror("MAP-E PSID offset must be 1-15: %lld",
+                    $2);
+                YYERROR;
+            }
+            if ($4 <= 0 || $2 + $4 > 16) {
+                yyerror("invalid MAP-E PSID length: %lld", $4);
+                YYERROR;
+            }
+            if ($6 < 0 || $6 >= (1 << $4)) {
+                yyerror("invalid MAP-E PSID: %lld", $6);
+                YYERROR;
+            }
+            pool_opts.mape.offset = $2;
+            pool_opts.mape.psidlen = $4;
+            pool_opts.mape.psid = $6;
+        }
+
         ;

 routespec    : redirspec pool_opts {
@@ -4557,6 +4582,25 @@ apply_redirspec(struct pf_pool *rpool, struct pf_rule *r, struct redirspec *rs,
         rpool->proxy_port[0] = 0;
         rpool->proxy_port[1] = 0;
     }
+    if (rs->pool_opts.mape.offset) {
+        if (isrdr) {
+            yyerror("the 'map-e-portset' option is only valid with "
+                "nat rules");
+            return (1);
+        }
+        if (rs->pool_opts.staticport) {
+            yyerror("the 'map-e-portset' option can't be used with "
+                "'static-port'");
+            return (1);
+        }
+        if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW &&
+            rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) {
+            yyerror("the 'map-e-portset' option can't be used when "
+                "specifying a port range");
+            return (1);
+        }
+        rpool->mape = rs->pool_opts.mape;
+    }

     return (0);
 }
@@ -4974,6 +5018,7 @@ lookup(char *s)
         { "load",        LOAD},
         { "log",        LOG},
         { "loginterface",    LOGINTERFACE},
+        { "map-e-portset",    MAPEPORTSET},
         { "match",        MATCH},
         { "matches",        MATCHES},
         { "max",        MAXIMUM},
@@ -5130,7 +5175,7 @@ lgetc(int quotec)
             c = igetc();
         }
     }
-
+
     return (c);
 }

diff --git sbin/pfctl/pfctl_parser.c sbin/pfctl/pfctl_parser.c
index 6f39ad72384..a630304fc55 100644
--- sbin/pfctl/pfctl_parser.c
+++ sbin/pfctl/pfctl_parser.c
@@ -518,6 +518,10 @@ print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2,
         printf(" sticky-address");
     if (id == PF_POOL_NAT && p1 == 0 && p2 == 0)
         printf(" static-port");
+    if (pool->mape.offset) {
+        printf(" map-e-portset %u/%u/%u", pool->mape.offset,
+            pool->mape.psidlen, pool->mape.psid);
+    }
 }

 const char    *pf_reasons[PFRES_MAX+1] = PFRES_NAMES;
@@ -1640,7 +1644,7 @@ host(const char *s, int opts)
     for (n = h; n != NULL; n = n->next) {
         n->addr.type = PF_ADDR_ADDRMASK;
         n->weight = 0;
-    }
+    }

 error:
     free(ps);
@@ -1811,13 +1815,13 @@ append_addr(struct pfr_buffer *b, char *s, int test, int opts)
     const char        *errstr;
     int             rv, not = 0, i = 0;
     u_int16_t         weight;
-
+
     /* skip weight if given */
     if (strcmp(s, "weight") == 0) {
         expect = 1;
         return (1); /* expecting further call */
     }
-
+
     /* check if previous host is set */
     if (expect) {
         /* parse and append load balancing weight */
@@ -1834,7 +1838,7 @@ append_addr(struct pfr_buffer *b, char *s, int test, int opts)
                 }
             }
         }
-
+
         expect = 0;
         return (0);
     }
diff --git share/man/man5/pf.conf.5 share/man/man5/pf.conf.5
index 7ad9c4abb7f..d82a60d9cbf 100644
--- share/man/man5/pf.conf.5
+++ share/man/man5/pf.conf.5
@@ -1080,6 +1080,20 @@ rules, the
 option prevents
 .Xr pf 4
 from modifying the source port on TCP and UDP packets.
+.It Cm map-e-portset Ar psid-offset Ns / Ns Ar psid-length Ns / Ns Ar psid
+With
+.Cm nat-to
+rules, the
+.Cm map-e-portset
+option enables the source port translation of MAP-E (RFC 7597) Customer
+Edge. The following example sets PSID offset to 4, PSID length to 6, and PSID to
+20:
+.Bd -literal -offset indent
+match out on $gif_if nat-to ($gif_if) map-e-portset 4/6/20
+.Ed
+.Pp
+Note that, to work as a MAP-E Customer Edge, creating and configuring a
+tunneling interface are also required.
 .El
 .Pp
 When more than one redirection address or a table is specified,
@@ -2806,7 +2820,8 @@ filteropt      = user | group | flags | icmp-type | icmp6-type |
                  "rdr-to" ( redirhost | "{" redirhost-list "}" )
                  [ portspec ] [ pooltype ] |
                  "nat-to" ( redirhost | "{" redirhost-list "}" )
-                 [ portspec ] [ pooltype ] [ "static-port" ] |
+                 [ portspec ] [ pooltype ] [ "static-port" ]
+                 [ "map-e-portset" number "/" number "/" number ] |
                  [ route ] | [ "set tos" tos ] |
                  [ [ "!" ] "received-on" ( interface-name | interface-group ) ]

diff --git sys/net/pf_lb.c sys/net/pf_lb.c
index 2f74762986e..6cfb229befa 100644
--- sys/net/pf_lb.c
+++ sys/net/pf_lb.c
@@ -95,8 +95,14 @@
 u_int64_t         pf_hash(struct pf_addr *, struct pf_addr *,
                 struct pf_poolhashkey *, sa_family_t);
 int             pf_get_sport(struct pf_pdesc *, struct pf_rule *,
+                struct pf_addr *, u_int16_t *,
+                struct pf_src_node **);
+int             pf_get_sport_range(struct pf_pdesc *, struct pf_rule *,
                 struct pf_addr *, u_int16_t *, u_int16_t,
                 u_int16_t, struct pf_src_node **);
+int             pf_get_sport_mape(struct pf_pdesc *, struct pf_rule *,
+                struct pf_addr *, u_int16_t *,
+                struct pf_src_node **);
 int             pf_map_addr_states_increase(sa_family_t,
                 struct pf_pool *, struct pf_addr *);
 int             pf_get_transaddr_af(struct pf_rule *,
@@ -147,20 +153,12 @@ pf_hash(struct pf_addr *inaddr, struct pf_addr *hash,

 int
 pf_get_sport(struct pf_pdesc *pd, struct pf_rule *r,
-    struct pf_addr *naddr, u_int16_t *nport, u_int16_t low, u_int16_t high,
-    struct pf_src_node **sn)
+    struct pf_addr *naddr, u_int16_t *nport, struct pf_src_node **sn)
 {
-    struct pf_state_key_cmp    key;
-    struct pf_addr        init_addr;
-    u_int16_t        cut;
-    int            dir = (pd->dir == PF_IN) ? PF_OUT : PF_IN;
-    int            sidx = pd->sidx;
-    int            didx = pd->didx;
+    u_int16_t low, high;

-    memset(&init_addr, 0, sizeof(init_addr));
-    if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr, &init_addr, sn, &r->nat,
-        PF_SN_NAT))
-        return (1);
+    low = r->nat.proxy_port[0];
+    high = r->nat.proxy_port[1];

     if (pd->proto == IPPROTO_ICMP) {
         if (pd->ndport == htons(ICMP_ECHO)) {
@@ -179,6 +177,39 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_rule *r,
     }
 #endif /* INET6 */

+    if (r->nat.mape.offset) {
+        if (pf_get_sport_mape(pd, r, naddr, nport, sn)) {
+            DPFPRINTF(LOG_NOTICE,
+                "pf: MAP-E port allocation (%u/%u/%u) failed",
+                r->nat.mape.offset, r->nat.mape.psidlen,
+                r->nat.mape.psid);
+            return (-1);
+        }
+    } else if (pf_get_sport_range(pd, r, naddr, nport, low, high, sn)) {
+        DPFPRINTF(LOG_NOTICE,
+            "pf: NAT proxy port allocation (%u-%u) failed", low, high);
+        return (-1);
+    }
+    return (0);
+}
+
+int
+pf_get_sport_range(struct pf_pdesc *pd, struct pf_rule *r,
+    struct pf_addr *naddr, u_int16_t *nport, u_int16_t low, u_int16_t high,
+    struct pf_src_node **sn)
+{
+    struct pf_state_key_cmp    key;
+    struct pf_addr        init_addr;
+    u_int16_t        cut;
+    int            dir = (pd->dir == PF_IN) ? PF_OUT : PF_IN;
+    int            sidx = pd->sidx;
+    int            didx = pd->didx;
+
+    memset(&init_addr, 0, sizeof(init_addr));
+    if (pf_map_addr(pd->naf, r, &pd->nsaddr, naddr, &init_addr, sn, &r->nat,
+        PF_SN_NAT))
+        return (1);
+
     do {
         key.af = pd->naf;
         key.proto = pd->proto;
@@ -264,6 +295,40 @@ pf_get_sport(struct pf_pdesc *pd, struct pf_rule *r,
     return (1);                    /* none available */
 }

+int
+pf_get_sport_mape(struct pf_pdesc *pd, struct pf_rule *r,
+    struct pf_addr *naddr, u_int16_t *nport, struct pf_src_node **sn)
+{
+    u_int16_t     psmask, low, high,highmask;
+    u_int16_t     alow, ahigh, cut, tmp;
+    int         ashift, psidshift;
+
+    ashift = 16 - r->nat.mape.offset;
+    psidshift = ashift - r->nat.mape.psidlen;
+    psmask = r->nat.mape.psid & ((1U << r->nat.mape.psidlen) - 1);
+    psmask = psmask << psidshift;
+    highmask = (1U << psidshift) - 1;
+
+    alow = 1;
+    ahigh = (1U << r->nat.mape.offset) - 1;
+    cut = arc4random_uniform(1 + ahigh - alow) + alow;
+
+    for (tmp = cut; tmp <= ahigh; ++tmp) {
+        low = (tmp << ashift) | psmask;
+        high = low | highmask;
+        if(!pf_get_sport_range(pd, r, naddr, nport, low, high, sn))
+            return (0);
+    }
+    for (tmp = cut - 1; tmp >= alow; --tmp) {
+        low = (tmp << ashift) | psmask;
+        high = low | highmask;
+        if(!pf_get_sport_range(pd, r, naddr, nport, low, high, sn))
+            return (0);
+    }
+    return (1);
+}
+
+
 int
 pf_map_addr_sticky(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,      struct pf_addr *naddr, struct pf_src_node **sns, struct pf_pool *rpool, @@ -521,7 +586,7 @@ pf_map_addr(sa_family_t af, struct pf_rule *r, struct pf_addr *saddr,
                     break;
                 pf_addr_inc(&rpool->counter, af);
             } while (1);
-
+
             weight = rpool->weight;
         }

@@ -683,14 +748,8 @@ pf_get_transaddr(struct pf_rule *r, struct pf_pdesc *pd,
         /* XXX is this right? what if rtable is changed at the same
          * XXX time? where do I need to figure out the sport? */
         nport = 0;
-        if (pf_get_sport(pd, r, &naddr, &nport,
-            r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
-            DPFPRINTF(LOG_NOTICE,
-                "pf: NAT proxy port allocation (%u-%u) failed",
-                r->nat.proxy_port[0],
-                r->nat.proxy_port[1]);
+        if (pf_get_sport(pd, r, &naddr, &nport, sns))
             return (-1);
-        }
         *nr = r;
         pf_addrcpy(&pd->nsaddr, &naddr, pd->af);
         pd->nsport = nport;
@@ -752,14 +811,8 @@ pf_get_transaddr_af(struct pf_rule *r, struct pf_pdesc *pd,

     /* get source address and port */
     nport = 0;
-    if (pf_get_sport(pd, r, &nsaddr, &nport,
-        r->nat.proxy_port[0], r->nat.proxy_port[1], sns)) {
-        DPFPRINTF(LOG_NOTICE,
-            "pf: af-to NAT proxy port allocation (%u-%u) failed",
-            r->nat.proxy_port[0],
-            r->nat.proxy_port[1]);
+    if (pf_get_sport(pd, r, &nsaddr, &nport, sns))
         return (-1);
-    }
     pd->nsport = nport;

     if (pd->proto == IPPROTO_ICMPV6 && pd->naf == AF_INET) {
diff --git sys/net/pfvar.h sys/net/pfvar.h
index d2f2464bc25..b97decc7ce7 100644
--- sys/net/pfvar.h
+++ sys/net/pfvar.h
@@ -339,10 +339,17 @@ struct pf_poolhashkey {
 #define key32    pfk.key32
 };

+struct pf_mape_port {
+    u_int8_t         offset;
+    u_int8_t         psidlen;
+    u_int16_t         psid;
+};
+
 struct pf_pool {
     struct pf_addr_wrap     addr;
     struct pf_poolhashkey     key;
     struct pf_addr         counter;
+    struct pf_mape_port     mape;
     char             ifname[IFNAMSIZ];
     struct pfi_kif        *kif;
     int             tblidx;

Reply via email to