From: David Lamparter Date: Sun, 28 Jul 2019 07:28:45 +0000 (+0200) Subject: zebra: implement RFC8781 (NAT64 prefix in RAs) X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=38ea0258ae7f59a72628c54544b6114b98b0d0df;p=mirror%2Ffrr.git zebra: implement RFC8781 (NAT64 prefix in RAs) 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 --- 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