diff --git a/doc/user/ipv6.rst b/doc/user/ipv6.rst index 18aae00bdb..3f881132ff 100644 --- a/doc/user/ipv6.rst +++ b/doc/user/ipv6.rst @@ -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: diff --git a/yang/frr-zebra.yang b/yang/frr-zebra.yang index 4dd8e98ddb..6e66e1add3 100644 --- a/yang/frr-zebra.yang +++ b/yang/frr-zebra.yang @@ -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; diff --git a/zebra/rtadv.c b/zebra/rtadv.c index ce3f0320f5..f632938f69 100644 --- a/zebra/rtadv.c +++ b/zebra/rtadv.c @@ -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); } diff --git a/zebra/rtadv.h b/zebra/rtadv.h index 73d737ce41..fb34a8402a 100644 --- a/zebra/rtadv.h +++ b/zebra/rtadv.h @@ -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); diff --git a/zebra/zebra_cli.c b/zebra/zebra_cli.c index 8b2eab3f5d..775414afa6 100644 --- a/zebra/zebra_cli.c +++ b/zebra/zebra_cli.c @@ -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); diff --git a/zebra/zebra_nb.c b/zebra/zebra_nb.c index 50fdb8b7aa..39d07f5120 100644 --- a/zebra/zebra_nb.c +++ b/zebra/zebra_nb.c @@ -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 { diff --git a/zebra/zebra_nb.h b/zebra/zebra_nb.h index 628eeab1de..3fd58a8058 100644 --- a/zebra/zebra_nb.h +++ b/zebra/zebra_nb.h @@ -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); diff --git a/zebra/zebra_nb_config.c b/zebra/zebra_nb_config.c index d99010547f..6361b88859 100644 --- a/zebra/zebra_nb_config.c +++ b/zebra/zebra_nb_config.c @@ -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