router: Add PREF64 (RFC 8781) support
authorOndřej Caletka <ondrej@caletka.cz>
Sat, 4 Jun 2022 21:42:59 +0000 (23:42 +0200)
committerChristian Marangi <ansuelsmth@gmail.com>
Fri, 23 Jun 2023 12:09:59 +0000 (14:09 +0200)
This option of IPv6 Router Advertisements allows devices connected to
a IPv6-only network to discover IPv6 prefix of the NAT64 gateway.
Devices can use this information for instance to setup client translator
(CLAT) from IPv4 to IPv6 in 464XLAT (RFC 6877) scenario or to handle
IPv4 address literal on application level.

To enable PREF64 option, a new uci parameter ra_pref64 has to contain
the NAT64 prefix, including prefix length. Only lengths of 96, 64, 56,
48, 40 and 32 bits are supported. For example, to annonce the Well-Known
Prefix:

config dhcp 'lan'
        …
        option ra_pref64 '64:ff9b::/96'

Fixes: #182
Signed-off-by: Ondřej Caletka <ondrej@caletka.cz>
[ remove extra space for Fixes tag ]
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
README
src/config.c
src/odhcpd.h
src/router.c

diff --git a/README b/README
index ef8758e86cbecb28330b46889ec5757264c7a71b..24e57d48519d433d6ff29be35cbc117d7eb3a955 100644 (file)
--- a/README
+++ b/README
@@ -142,6 +142,8 @@ ra_mtu                      integer -                       MTU to be advertised in
                                                        RA messages
 ra_dns                 bool    1                       Announce DNS configuration in
                                                        RA messages (RFC8106)
+ra_pref64              string                          Announce PREF64 option
+                       [IPv6 prefix]                   for NAT64 prefix (RFC8781)
 ndproxy_routing                bool    1                       Learn routes from NDP
 ndproxy_slave          bool    0                       NDProxy external slave
 prefix_filter          string  ::/0                    Only advertise on-link prefixes within
index 30fdc309bbb9f03c8579a70deb5378e0be048c4f..d7cd5dd0aa9c87427698a6d0c94b0ed120e49910 100644 (file)
@@ -82,6 +82,7 @@ enum {
        IFACE_ATTR_RA_HOPLIMIT,
        IFACE_ATTR_RA_MTU,
        IFACE_ATTR_RA_DNS,
+       IFACE_ATTR_RA_PREF64,
        IFACE_ATTR_PD_MANAGER,
        IFACE_ATTR_PD_CER,
        IFACE_ATTR_NDPROXY_ROUTING,
@@ -135,6 +136,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
        [IFACE_ATTR_RA_HOPLIMIT] = { .name = "ra_hoplimit", .type = BLOBMSG_TYPE_INT32 },
        [IFACE_ATTR_RA_MTU] = { .name = "ra_mtu", .type = BLOBMSG_TYPE_INT32 },
        [IFACE_ATTR_RA_DNS] = { .name = "ra_dns", .type = BLOBMSG_TYPE_BOOL },
+       [IFACE_ATTR_RA_PREF64] = { .name = "ra_pref64", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_NDPROXY_ROUTING] = { .name = "ndproxy_routing", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_NDPROXY_SLAVE] = { .name = "ndproxy_slave", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_PREFIX_FILTER] = { .name = "prefix_filter", .type = BLOBMSG_TYPE_STRING },
@@ -958,6 +960,24 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
        if ((c = tb[IFACE_ATTR_RA_DNS]))
                iface->ra_dns = blobmsg_get_bool(c);
 
+       if ((c = tb[IFACE_ATTR_RA_PREF64])) {
+               const char *str = blobmsg_get_string(c);
+               char *astr = malloc(strlen(str) + 1);
+               char *delim;
+               int l;
+
+               if (!astr || !strcpy(astr, str) ||
+                               (delim = strchr(astr, '/')) == NULL || (*(delim++) = 0) ||
+                               sscanf(delim, "%i", &l) == 0 || l > 128 ||
+                               inet_pton(AF_INET6, astr, &iface->pref64_addr) == 0)
+                       iface->pref64_length = 0;
+               else
+                       iface->pref64_length = l;
+
+               if (astr)
+                       free(astr);
+       }
+
        if ((c = tb[IFACE_ATTR_RA_PREFERENCE])) {
                const char *prio = blobmsg_get_string(c);
 
index 420d2a7dd78f0310adce5d311c3539276e745eba..8ab51dc4ceca2288eec2f7488abae7957df6015e 100644 (file)
@@ -34,6 +34,9 @@
 #define ND_OPT_RECURSIVE_DNS 25
 #define ND_OPT_DNS_SEARCH 31
 
+// RFC 8781 defines PREF64 option
+#define ND_OPT_PREF64 38
+
 #define INFINITE_VALID(x) ((x) == 0)
 
 #define _unused __attribute__((unused))
@@ -300,6 +303,8 @@ struct interface {
        bool ra_advrouter;
        bool ra_useleasetime;
        bool ra_dns;
+       uint8_t pref64_length;
+       struct in6_addr pref64_addr;
        bool no_dynamic_dhcp;
        bool have_link_local;
        uint8_t pio_filter_length;
index eca0bf7a282278fed0489bfabf8448003ad3bef3..d5ef7f8a4f582ef3466024da3ff2981a41c2787c 100644 (file)
@@ -390,6 +390,7 @@ enum {
        IOV_RA_ROUTES,
        IOV_RA_DNS,
        IOV_RA_SEARCH,
+       IOV_RA_PREF64,
        IOV_RA_ADV_INTERVAL,
        IOV_RA_TOTAL,
 };
@@ -427,6 +428,13 @@ struct nd_opt_route_info {
        uint32_t addr[4];
 };
 
+struct nd_opt_pref64_info {
+       uint8_t type;
+       uint8_t len;
+       uint16_t lifetime_plc;
+       uint32_t addr[3];
+};
+
 /* Router Advert server mode */
 static int send_router_advert(struct interface *iface, const struct in6_addr *from)
 {
@@ -437,10 +445,12 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        struct nd_opt_dns_server *dns = NULL;
        struct nd_opt_search_list *search = NULL;
        struct nd_opt_route_info *routes = NULL;
+       struct nd_opt_pref64_info *pref64 = NULL;
        struct nd_opt_adv_interval adv_interval;
        struct iovec iov[IOV_RA_TOTAL];
        struct sockaddr_in6 dest;
-       size_t dns_sz = 0, search_sz = 0, pfxs_cnt = 0, routes_cnt = 0;
+       size_t dns_sz = 0, search_sz = 0, pref64_sz = 0;
+       size_t pfxs_cnt = 0, routes_cnt = 0;
        ssize_t valid_addr_cnt = 0, invalid_addr_cnt = 0;
        uint32_t minvalid = UINT32_MAX, maxival, lifetime;
        int msecs, mtu = iface->ra_mtu, hlim = iface->ra_hoplimit;
@@ -698,6 +708,65 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr
        iov[IOV_RA_SEARCH].iov_base = (char *)search;
        iov[IOV_RA_SEARCH].iov_len = search_sz;
 
+       if (iface->pref64_length) {
+               /* RFC 8781 § 4.1 rounding up lifetime to multiply of 8 */
+               uint16_t pref64_lifetime = lifetime < (UINT16_MAX - 7) ? lifetime + 7 : UINT16_MAX;
+               uint8_t prefix_length_code;
+               uint32_t mask_a1, mask_a2;
+
+               switch (iface->pref64_length) {
+               case 96:
+                       prefix_length_code = 0;
+                       mask_a1 = 0xffffffff;
+                       mask_a2 = 0xffffffff;
+                       break;
+               case 64:
+                       prefix_length_code = 1;
+                       mask_a1 = 0xffffffff;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 56:
+                       prefix_length_code = 2;
+                       mask_a1 = 0xffffff00;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 48:
+                       prefix_length_code = 3;
+                       mask_a1 = 0xffff0000;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 40:
+                       prefix_length_code = 4;
+                       mask_a1 = 0xff000000;
+                       mask_a2 = 0x00000000;
+                       break;
+               case 32:
+                       prefix_length_code = 5;
+                       mask_a1 = 0x00000000;
+                       mask_a2 = 0x00000000;
+                       break;
+               default:
+                       syslog(LOG_WARNING, "Invalid PREF64 prefix size (%d), "
+                                       "ignoring ra_pref64 option!", iface->pref64_length);
+                       goto pref64_out;
+                       break;
+               }
+
+               pref64_sz = sizeof(*pref64);
+               pref64 = alloca(pref64_sz);
+               memset(pref64, 0, pref64_sz);
+               pref64->type = ND_OPT_PREF64;
+               pref64->len = 2;
+               pref64->lifetime_plc = htons((0xfff8 & pref64_lifetime) |
+                                               (0x7 & prefix_length_code));
+               pref64->addr[0] = iface->pref64_addr.s6_addr32[0];
+               pref64->addr[1] = iface->pref64_addr.s6_addr32[1] & htonl(mask_a1);
+               pref64->addr[2] = iface->pref64_addr.s6_addr32[2] & htonl(mask_a2);
+       }
+pref64_out:
+       iov[IOV_RA_PREF64].iov_base = (char *)pref64;
+       iov[IOV_RA_PREF64].iov_len = pref64_sz;
+
        /*
         * RFC7084 § 4.3 :
         *    L-3:   An IPv6 CE router MUST advertise itself as a router for the