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>
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:
"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.
*/
}
}
}
+ 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;
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. */
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 *);
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;
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)
{
{
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);
}
};
PREDECL_RBTREE_UNIQ(rtadv_prefixes);
+PREDECL_SORTLIST_UNIQ(pref64_advs);
/* Router advertisement parameter. From RFC4861, RFC6275 and RFC4191. */
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
#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] */
} __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.
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);
#include "northbound_cli.h"
#include "vrf.h"
+#include "zebra/rtadv.h"
#include "zebra_cli.h"
#include "zebra/zebra_cli_clippy.c"
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
.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
{
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);
.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
{
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);
#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"
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