]> git.puffer.fish Git - mirror/frr.git/commitdiff
zebra: implement RFC8781 (NAT64 prefix in RAs)
authorDavid Lamparter <equinox@diac24.net>
Sun, 28 Jul 2019 07:28:45 +0000 (09:28 +0200)
committerDavid Lamparter <equinox@opensourcerouting.org>
Tue, 15 Apr 2025 12:20:05 +0000 (14:20 +0200)
This tells hosts on the subnet if (and which) NAT64 prefix is in use.
Useful for things like xlat464, or local dns64.

Updated from the previous -03 draft implementation.  (PLC field did not
exist before.)

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
doc/user/ipv6.rst
yang/frr-zebra.yang
zebra/rtadv.c
zebra/rtadv.h
zebra/zebra_cli.c
zebra/zebra_nb.c
zebra/zebra_nb.h
zebra/zebra_nb_config.c

index 18aae00bdb3f779ecb65b784f93357bc884ffe0d..3f881132ffd17d37859738b64a3779fe70470055 100644 (file)
@@ -204,6 +204,23 @@ Router Advertisement
 
    Default: do not emit DNSSL option
 
+.. clicmd:: ipv6 nd nat64 [X:X::X:X/M] [lifetime (0-65535)]
+
+   Include in RAs an advertisement of the NAT64 prefix in use (RFC8781).
+   (May be configured multiple times for multiple prefixes.)
+
+   If no prefix is given when configuring, the NAT64 default prefix of
+   64:ff9b::/96 is substituted.  Only prefixes with a prefix length of /96,
+   /64, /56, /48, /40 or /32 can be encoded into advertisements.
+
+   Lifetime is specified in seconds and defaults to ``3 * ra-interval``.  If
+   no value is configured, this is adjusted when ``ra-interval`` is changed.
+   A lifetime of 0 seconds is used to signal NAT64 prefixes that are no longer
+   valid.  Note that this is rounded up to multiples of 8 seconds as a
+   limitation of the option encoding.
+
+   Default: don't advertise any NAT64 prefix.
+
 Router Advertisement Configuration Example
 ==========================================
 A small example:
index 4dd8e98ddb900f108d7271653a9aae025413e02f..6e66e1add31191e771cbd43a29b740897c1eeb11 100644 (file)
@@ -195,6 +195,16 @@ module frr-zebra {
       "Type for VTEP flood type.";
   }
 
+  /* PREF64 only accepts specific prefix lengths */
+  typedef pref64-prefix {
+    type inet:ipv6-prefix {
+      /* rely on inet:ipv6-prefix enforcing validity already */
+      pattern '.*/(96|64|56|48|40|32)';
+    }
+    description
+      "An IPv6 prefix suitable for PREF64 announcement";
+  }
+
   /*
    * Common route data, shared by v4 and v6 routes.
    */
@@ -2829,6 +2839,31 @@ module frr-zebra {
             }
           }
         }
+        container pref64 {
+          description
+            "A list of NAT64 prefixes that are placed in the PREF64 option in
+            Router Advertisement messages sent from the interface.";
+          reference
+            "RFC 8781: Discovering PREF64 in Router Advertisements";
+          list pref64-prefix {
+            key "prefix";
+            description
+              "NAT64 prefix details";
+            leaf prefix {
+              type pref64-prefix;
+              description
+                "NAT64 prefix, typically 64:ff9b::/96";
+            }
+            leaf lifetime {
+              type uint16;
+              units "seconds";
+              description
+                "Lifetime of the NAT64 prefix to be placed in RAs.
+               If omitted, the lifetime will be calculated automatically as
+               MaxRtrAdvInterval * 3";
+            }
+          }
+        }
       }
       leaf ptm-enable {
         if-feature ptm-bfd;
index ce3f0320f5d6430808f18f96c1a469837c8f44e7..f632938f69fa401efffaa9202329710965a4ee41 100644 (file)
@@ -95,6 +95,14 @@ DECLARE_RBTREE_UNIQ(rtadv_prefixes, struct rtadv_prefix, item,
 
 DEFINE_MTYPE_STATIC(ZEBRA, RTADV_RDNSS, "Router Advertisement RDNSS");
 DEFINE_MTYPE_STATIC(ZEBRA, RTADV_DNSSL, "Router Advertisement DNSSL");
+DEFINE_MTYPE_STATIC(ZEBRA, RTADV_PREF64, "Router Advertisement NAT64 Prefix");
+
+static int pref64_cmp(const struct pref64_adv *a, const struct pref64_adv *b)
+{
+       return prefix_cmp(&a->p, &b->p);
+}
+
+DECLARE_SORTLIST_UNIQ(pref64_advs, struct pref64_adv, itm, pref64_cmp);
 
 /* Order is intentional.  Matches RFC4191.  This array is also used for
    command matching, so only modify with care. */
@@ -110,6 +118,29 @@ enum rtadv_event {
        RTADV_READ
 };
 
+#define PREF64_INVALID_PREFIXLEN 0xff
+
+/* RFC8781 NAT64 prefix can encode /96, /64, /56, /48, /40 and /32 only. */
+static uint8_t pref64_get_plc(const struct prefix_ipv6 *p)
+{
+       switch (p->prefixlen) {
+       case 96:
+               return 0;
+       case 64:
+               return 1;
+       case 56:
+               return 2;
+       case 48:
+               return 3;
+       case 40:
+               return 4;
+       case 32:
+               return 5;
+       default:
+               return PREF64_INVALID_PREFIXLEN;
+       }
+}
+
 static void rtadv_event(struct zebra_vrf *, enum rtadv_event, int);
 
 static int if_join_all_router(int, struct interface *);
@@ -444,6 +475,50 @@ static void rtadv_send_packet(int sock, struct interface *ifp,
                        buf[len++] = '\0';
        }
 
+       struct pref64_adv *pref64_adv;
+
+       frr_each (pref64_advs, zif->rtadv.pref64_advs, pref64_adv) {
+               struct nd_opt_pref64__frr *opt;
+               size_t opt_len = sizeof(*opt);
+               uint16_t lifetime_plc;
+
+               if (len + opt_len > max_len) {
+                       zlog_warn("%s(%u): Tx RA: NAT64 option would exceed MTU, omitting it",
+                                 ifp->name, ifp->ifindex);
+                       goto no_more_opts;
+               }
+
+               if (pref64_adv->lifetime == PREF64_LIFETIME_AUTO) {
+                       /* starting in msec, so won't fit in 16bit */
+                       unsigned lifetime;
+
+                       lifetime = zif->rtadv.MaxRtrAdvInterval * 3;
+                       lifetime += 999;
+                       lifetime /= 1000;
+
+                       if (lifetime > 65535)
+                               lifetime = 65535;
+
+                       lifetime_plc = lifetime;
+               } else
+                       lifetime_plc = pref64_adv->lifetime;
+
+               /* rounding up to 8 sec, cap at 16 bits, and clear PLC */
+               lifetime_plc = MIN(lifetime_plc + 0x7, 0xffffU) & ~0x7U;
+               lifetime_plc |= pref64_get_plc(&pref64_adv->p);
+
+               opt = (struct nd_opt_pref64__frr *)(buf + len);
+               memset(opt, 0, opt_len);
+
+               opt->nd_opt_pref64_type = ND_OPT_PREF64;
+               opt->nd_opt_pref64_len = opt_len / 8;
+               opt->nd_opt_pref64_lifetime_plc = htons(lifetime_plc);
+               memcpy(opt->nd_opt_pref64_prefix, &pref64_adv->p.prefix,
+                      sizeof(opt->nd_opt_pref64_prefix));
+
+               len += opt_len;
+       }
+
 no_more_opts:
 
        msg.msg_name = (void *)&addr;
@@ -1723,6 +1798,41 @@ int rtadv_dnssl_encode(uint8_t *out, const char *in)
        return outp;
 }
 
+struct pref64_adv *rtadv_pref64_set(struct zebra_if *zif, struct prefix_ipv6 *p, uint32_t lifetime)
+{
+       struct pref64_adv *item, dummy = {};
+
+       prefix_copy(&dummy.p, p);
+       apply_mask_ipv6(&dummy.p);
+
+       item = pref64_advs_find(zif->rtadv.pref64_advs, &dummy);
+       if (!item) {
+               item = XCALLOC(MTYPE_RTADV_PREF64, sizeof(*item));
+               prefix_copy(&item->p, &dummy.p);
+
+               pref64_advs_add(zif->rtadv.pref64_advs, item);
+       }
+
+       item->lifetime = lifetime;
+       return item;
+}
+
+static void rtadv_pref64_free(struct pref64_adv *item)
+{
+       XFREE(MTYPE_RTADV_PREF64, item);
+}
+
+void rtadv_pref64_update(struct zebra_if *zif, struct pref64_adv *item, uint32_t lifetime)
+{
+       item->lifetime = lifetime;
+}
+
+void rtadv_pref64_reset(struct zebra_if *zif, struct pref64_adv *item)
+{
+       pref64_advs_del(zif->rtadv.pref64_advs, item);
+       rtadv_pref64_free(item);
+}
+
 /* Dump interface ND information to vty. */
 static int nd_dump_vty(struct vty *vty, json_object *json_if, struct interface *ifp)
 {
@@ -1956,12 +2066,16 @@ void rtadv_if_fini(struct zebra_if *zif)
 {
        struct rtadvconf *rtadv;
        struct rtadv_prefix *rp;
+       struct pref64_adv *pref64_adv;
 
        rtadv = &zif->rtadv;
 
        while ((rp = rtadv_prefixes_pop(rtadv->prefixes)))
                rtadv_prefix_free(rp);
 
+       while ((pref64_adv = pref64_advs_pop(rtadv->pref64_advs)))
+               rtadv_pref64_free(pref64_adv);
+
        list_delete(&rtadv->AdvRDNSSList);
        list_delete(&rtadv->AdvDNSSLList);
 }
index 73d737ce418def77c53ff65997ef6ac063dc0cc7..fb34a8402a82c25a6b5e77fe3e999e958233f583 100644 (file)
@@ -35,6 +35,7 @@ struct rtadv {
 };
 
 PREDECL_RBTREE_UNIQ(rtadv_prefixes);
+PREDECL_SORTLIST_UNIQ(pref64_advs);
 
 /* Router advertisement parameter.  From RFC4861, RFC6275 and RFC4191. */
 struct rtadvconf {
@@ -189,6 +190,9 @@ struct rtadvconf {
         */
        struct list *AdvDNSSLList;
 
+       /* NAT64 prefix advertisements [RFC8781] */
+       struct pref64_advs_head pref64_advs[1];
+
        /*
         * rfc4861 states RAs must be sent at least 3 seconds apart.
         * We allow faster retransmits to speed up convergence but can
@@ -333,6 +337,9 @@ struct nd_opt_homeagent_info { /* Home Agent info */
 #ifndef ND_OPT_DNSSL
 #define ND_OPT_DNSSL 31
 #endif
+#ifndef ND_OPT_PREF64
+#define ND_OPT_PREF64 38
+#endif
 
 #ifndef HAVE_STRUCT_ND_OPT_RDNSS
 struct nd_opt_rdnss { /* Recursive DNS server option [RFC8106 5.1] */
@@ -358,6 +365,27 @@ struct nd_opt_dnssl { /* DNS search list option [RFC8106 5.2] */
 } __attribute__((__packed__));
 #endif
 
+/* not in a system header (yet?)
+ * => added "__frr" to avoid future conflicts
+ */
+struct nd_opt_pref64__frr {
+       uint8_t nd_opt_pref64_type;
+       uint8_t nd_opt_pref64_len;
+       uint16_t nd_opt_pref64_lifetime_plc;
+       uint8_t nd_opt_pref64_prefix[12]; /* highest 96 bits only */
+} __attribute__((__packed__));
+
+
+#define PREF64_LIFETIME_AUTO UINT32_MAX
+#define PREF64_DFLT_PREFIX   "64:ff9b::/96"
+
+struct pref64_adv {
+       struct pref64_advs_item itm;
+
+       struct prefix_ipv6 p;
+       uint32_t lifetime;
+};
+
 /*
  * ipv6 nd prefixes can be manually defined, derived from the kernel interface
  * configs or both.  If both, manual flag/timer settings are used.
@@ -405,6 +433,26 @@ struct rtadv_dnssl *rtadv_dnssl_set(struct zebra_if *zif,
 void rtadv_dnssl_reset(struct zebra_if *zif, struct rtadv_dnssl *p);
 int rtadv_dnssl_encode(uint8_t *out, const char *in);
 
+/* lifetime: 0-65535 or PREF64_LIFETIME_AUTO */
+static inline bool rtadv_pref64_valid_prefix(const struct prefix_ipv6 *p)
+{
+       switch (p->prefixlen) {
+       case 96:
+       case 64:
+       case 56:
+       case 48:
+       case 40:
+       case 32:
+               return true;
+       default:
+               return false;
+       }
+}
+
+struct pref64_adv *rtadv_pref64_set(struct zebra_if *zif, struct prefix_ipv6 *p, uint32_t lifetime);
+void rtadv_pref64_update(struct zebra_if *zif, struct pref64_adv *item, uint32_t lifetime);
+void rtadv_pref64_reset(struct zebra_if *zif, struct pref64_adv *item);
+
 void ipv6_nd_suppress_ra_set(struct interface *ifp,
                             enum ipv6_nd_suppress_ra_status status);
 void ipv6_nd_interval_set(struct interface *ifp, uint32_t interval);
index 8b2eab3f5d199513aebb0730778ac4e84f0212a1..775414afa64802b3f7bb1bd1a65c04ad65e9b06d 100644 (file)
@@ -9,6 +9,7 @@
 #include "northbound_cli.h"
 #include "vrf.h"
 
+#include "zebra/rtadv.h"
 #include "zebra_cli.h"
 #include "zebra/zebra_cli_clippy.c"
 
@@ -1823,6 +1824,58 @@ lib_interface_zebra_ipv6_router_advertisements_dnssl_dnssl_domain_cli_write(
 
        vty_out(vty, "\n");
 }
+
+DEFPY_YANG(
+      ipv6_nd_pref64,
+      ipv6_nd_pref64_cmd,
+      "[no] ipv6 nd nat64 [X:X::X:X/M]$prefix [lifetime <(0-65535)|auto>]",
+      NO_STR
+      "Interface IPv6 config commands\n"
+      "Neighbor discovery\n"
+      "NAT64 prefix advertisement (RFC8781)\n"
+      "NAT64 prefix to advertise (default: 64:ff9b::/96)\n"
+      "Specify validity lifetime\n"
+      "Valid lifetime in seconds\n"
+      "Calculate lifetime automatically\n")
+{
+       if (!prefix_str)
+               prefix_str = PREF64_DFLT_PREFIX;
+       else if (!rtadv_pref64_valid_prefix(prefix)) {
+               vty_out(vty,
+                       "Invalid NAT64 prefix length - must be /96, /64, /56, /48, /40 or /32\n");
+               return CMD_WARNING_CONFIG_FAILED;
+       }
+
+       if (!no) {
+               nb_cli_enqueue_change(vty, ".", NB_OP_CREATE, NULL);
+               if (lifetime_str && strcmp(lifetime_str, "auto")) {
+                       nb_cli_enqueue_change(vty, "./lifetime", NB_OP_MODIFY, lifetime_str);
+               } else {
+                       nb_cli_enqueue_change(vty, "./lifetime", NB_OP_DESTROY, NULL);
+               }
+       } else {
+               nb_cli_enqueue_change(vty, ".", NB_OP_DESTROY, NULL);
+       }
+       return nb_cli_apply_changes(vty,
+                                   "./frr-zebra:zebra/ipv6-router-advertisements/pref64/pref64-prefix[prefix='%s']",
+                                   prefix_str);
+}
+
+static void lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_cli_write(
+       struct vty *vty, const struct lyd_node *dnode, bool show_defaults)
+{
+       const char *prefix = yang_dnode_get_string(dnode, "prefix");
+
+       vty_out(vty, " ipv6 nd nat64 %s", prefix);
+
+       if (yang_dnode_exists(dnode, "lifetime")) {
+               uint16_t lifetime = yang_dnode_get_uint16(dnode, "lifetime");
+
+               vty_out(vty, " %u", lifetime);
+       }
+
+       vty_out(vty, "\n");
+}
 #endif /* HAVE_RTADV */
 
 #if HAVE_BFDD == 0
@@ -2874,6 +2927,10 @@ const struct frr_yang_module_info frr_zebra_cli_info = {
                        .xpath = "/frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/rdnss/rdnss-address",
                        .cbs.cli_show = lib_interface_zebra_ipv6_router_advertisements_rdnss_rdnss_address_cli_write,
                },
+               {
+                       .xpath = "/frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/pref64/pref64-prefix",
+                       .cbs.cli_show = lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_cli_write,
+               },
 #endif /* defined(HAVE_RTADV) */
 #if HAVE_BFDD == 0
                {
@@ -2989,6 +3046,7 @@ void zebra_cli_init(void)
        install_element(INTERFACE_NODE, &ipv6_nd_mtu_cmd);
        install_element(INTERFACE_NODE, &ipv6_nd_rdnss_cmd);
        install_element(INTERFACE_NODE, &ipv6_nd_dnssl_cmd);
+       install_element(INTERFACE_NODE, &ipv6_nd_pref64_cmd);
 #endif
 #if HAVE_BFDD == 0
        install_element(INTERFACE_NODE, &zebra_ptm_enable_if_cmd);
index 50fdb8b7aaff7483dfc710ad6a09d6b19b983762..39d07f5120ce9c85e01513388df28dc1e500527b 100644 (file)
@@ -763,6 +763,20 @@ const struct frr_yang_module_info frr_zebra_info = {
                                .destroy = lib_interface_zebra_ipv6_router_advertisements_rdnss_rdnss_address_lifetime_destroy,
                        }
                },
+               {
+                       .xpath = "/frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/pref64/pref64-prefix",
+                       .cbs = {
+                               .create = lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_create,
+                               .destroy = lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_destroy,
+                       }
+               },
+               {
+                       .xpath = "/frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/pref64/pref64-prefix/lifetime",
+                       .cbs = {
+                               .modify = lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_modify,
+                               .destroy = lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_destroy,
+                       }
+               },
 #endif /* defined(HAVE_RTADV) */
 #if HAVE_BFDD == 0
                {
index 628eeab1def27ee7afddbe14402039dcf63ba629..3fd58a805835f6e51589ce46ef7e30afed5f3c3d 100644 (file)
@@ -269,6 +269,14 @@ int lib_interface_zebra_ipv6_router_advertisements_dnssl_dnssl_domain_lifetime_m
        struct nb_cb_modify_args *args);
 int lib_interface_zebra_ipv6_router_advertisements_dnssl_dnssl_domain_lifetime_destroy(
        struct nb_cb_destroy_args *args);
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_create(
+       struct nb_cb_create_args *args);
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_destroy(
+       struct nb_cb_destroy_args *args);
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_modify(
+       struct nb_cb_modify_args *args);
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_destroy(
+       struct nb_cb_destroy_args *args);
 #endif /* defined(HAVE_RTADV) */
 #if HAVE_BFDD == 0
 int lib_interface_zebra_ptm_enable_modify(struct nb_cb_modify_args *args);
index d99010547f4fade9b2d2943b2c0bf80224a813be..6361b88859ba0c9431de801da640e7ea6c303491 100644 (file)
@@ -14,6 +14,7 @@
 #include "libfrr.h"
 #include "lib/command.h"
 #include "lib/routemap.h"
+#include "zebra/rtadv.h"
 #include "zebra/zebra_nb.h"
 #include "zebra/rib.h"
 #include "zebra_nb.h"
@@ -3248,6 +3249,85 @@ int lib_interface_zebra_ipv6_router_advertisements_dnssl_dnssl_domain_lifetime_d
 
        return NB_OK;
 }
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/pref64/pref64-prefix
+ */
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_create(
+       struct nb_cb_create_args *args)
+{
+       struct interface *ifp;
+       struct pref64_adv *entry;
+       struct prefix_ipv6 p;
+       uint32_t lifetime = PREF64_LIFETIME_AUTO;
+
+       if (args->event != NB_EV_APPLY)
+               return NB_OK;
+
+       ifp = nb_running_get_entry(args->dnode, NULL, true);
+
+       yang_dnode_get_ipv6p(&p, args->dnode, "prefix");
+
+       if (yang_dnode_exists(args->dnode, "lifetime"))
+               lifetime = yang_dnode_get_uint16(args->dnode, "lifetime");
+
+       entry = rtadv_pref64_set(ifp->info, &p, lifetime);
+       nb_running_set_entry(args->dnode, entry);
+
+       return NB_OK;
+}
+
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_destroy(
+       struct nb_cb_destroy_args *args)
+{
+       struct interface *ifp;
+       struct pref64_adv *entry;
+
+       if (args->event != NB_EV_APPLY)
+               return NB_OK;
+
+       entry = nb_running_unset_entry(args->dnode);
+       ifp = nb_running_get_entry(args->dnode, NULL, true);
+
+       rtadv_pref64_reset(ifp->info, entry);
+
+       return NB_OK;
+}
+
+/*
+ * XPath: /frr-interface:lib/interface/frr-zebra:zebra/ipv6-router-advertisements/rdnss/rdnss-address/lifetime
+ */
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_modify(
+       struct nb_cb_modify_args *args)
+{
+       struct interface *ifp;
+       struct pref64_adv *entry;
+
+       if (args->event != NB_EV_APPLY)
+               return NB_OK;
+
+       entry = nb_running_get_entry(args->dnode, NULL, true);
+       ifp = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL, true);
+
+       rtadv_pref64_update(ifp->info, entry, yang_dnode_get_uint16(args->dnode, NULL));
+       return NB_OK;
+}
+
+int lib_interface_zebra_ipv6_router_advertisements_pref64_pref64_prefix_lifetime_destroy(
+       struct nb_cb_destroy_args *args)
+{
+       struct interface *ifp;
+       struct pref64_adv *entry;
+
+       if (args->event != NB_EV_APPLY)
+               return NB_OK;
+
+       entry = nb_running_get_entry(args->dnode, NULL, true);
+       ifp = nb_running_get_entry(lyd_parent(lyd_parent(args->dnode)), NULL, true);
+
+       rtadv_pref64_update(ifp->info, entry, PREF64_LIFETIME_AUTO);
+       return NB_OK;
+}
 #endif /* defined(HAVE_RTADV) */
 
 #if HAVE_BFDD == 0