From: Lars Seipel Date: Sat, 26 Jan 2019 22:51:48 +0000 (+0100) Subject: zebra: support DNS configuration options in rtadv X-Git-Tag: 7.1_pulled~255^2~4 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=3eb4fbb0f5c79955c3a3cfbfdfe5c2d8d57f654f;p=matthieu%2Ffrr.git zebra: support DNS configuration options in rtadv Add support for the RDNSS and DNSSL router advertisement options described in RFC 8106. Signed-off-by: Lars Seipel --- diff --git a/configure.ac b/configure.ac index 0c15b501f0..17227f2e0b 100755 --- a/configure.ac +++ b/configure.ac @@ -1738,7 +1738,8 @@ AC_CHECK_TYPES([ vifi_t, struct sioc_vif_req, struct igmpmsg, struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq, struct nd_opt_adv_interval, struct rt_addrinfo, - struct nd_opt_homeagent_info, struct nd_opt_adv_interval], + struct nd_opt_homeagent_info, struct nd_opt_adv_interval, + struct nd_opt_rdnss, struct nd_opt_dnssl], [], [], FRR_INCLUDES) AC_CHECK_MEMBERS([struct sockaddr.sa_len, diff --git a/zebra/interface.c b/zebra/interface.c index 8bb5c6e8ef..553435d289 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -135,6 +135,8 @@ static int if_zebra_new_hook(struct interface *ifp) rtadv->DefaultPreference = RTADV_PREF_MEDIUM; rtadv->AdvPrefixList = list_new(); + rtadv->AdvRDNSSList = list_new(); + rtadv->AdvDNSSLList = list_new(); } #endif /* HAVE_RTADV */ @@ -175,6 +177,8 @@ static int if_zebra_delete_hook(struct interface *ifp) rtadv = &zebra_if->rtadv; list_delete(&rtadv->AdvPrefixList); + list_delete(&rtadv->AdvRDNSSList); + list_delete(&rtadv->AdvDNSSLList); #endif /* HAVE_RTADV */ THREAD_OFF(zebra_if->speed_update); diff --git a/zebra/interface.h b/zebra/interface.h index 01dd697772..1dbcf33fad 100644 --- a/zebra/interface.h +++ b/zebra/interface.h @@ -168,6 +168,22 @@ struct rtadvconf { int DefaultPreference; #define RTADV_PREF_MEDIUM 0x0 /* Per RFC4191. */ + /* + * List of recursive DNS servers to include in the RDNSS option. + * See [RFC8106 5.1] + * + * Default: empty list; do not emit RDNSS option + */ + struct list *AdvRDNSSList; + + /* + * List of DNS search domains to include in the DNSSL option. + * See [RFC8106 5.2] + * + * Default: empty list; do not emit DNSSL option + */ + struct list *AdvDNSSLList; + uint8_t inFastRexmit; /* True if we're rexmits faster than usual */ /* Track if RA was configured by BGP or by the Operator or both */ @@ -182,6 +198,41 @@ struct rtadvconf { #define RTADV_NUM_FAST_REXMITS 4 /* Fast Rexmit RA 4 times on certain events */ }; +struct rtadv_rdnss { + /* Address of recursive DNS server to advertise */ + struct in6_addr addr; + + /* + * Lifetime in seconds; all-ones means infinity, zero + * stop using it. + */ + uint32_t lifetime; + + /* If lifetime not set, use a default of 3*MaxRtrAdvInterval */ + int lifetime_set; +}; + +/* + * [RFC1035 2.3.4] sets the maximum length of a domain name (a sequence of + * labels, each prefixed by a length octet) at 255 octets. + */ +#define RTADV_MAX_ENCODED_DOMAIN_NAME 255 + +struct rtadv_dnssl { + /* Domain name without trailing root zone dot (NUL-terminated) */ + char name[RTADV_MAX_ENCODED_DOMAIN_NAME - 1]; + + /* Name encoded as in [RFC1035 3.1] */ + uint8_t encoded_name[RTADV_MAX_ENCODED_DOMAIN_NAME]; + + /* Actual length of encoded_name */ + size_t encoded_len; + + /* Lifetime as for RDNSS */ + uint32_t lifetime; + int lifetime_set; +}; + #endif /* HAVE_RTADV */ /* Zebra interface type - ones of interest. */ diff --git a/zebra/rtadv.c b/zebra/rtadv.c index a22f6395c9..c5d28ddfb3 100644 --- a/zebra/rtadv.c +++ b/zebra/rtadv.c @@ -355,6 +355,53 @@ static void rtadv_send_packet(int sock, struct interface *ifp) len += sizeof(struct nd_opt_mtu); } + /* Recursive DNS servers */ + struct rtadv_rdnss *rdnss; + + for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) { + struct nd_opt_rdnss *opt = (struct nd_opt_rdnss *)(buf + len); + + opt->nd_opt_rdnss_type = ND_OPT_RDNSS; + opt->nd_opt_rdnss_len = 3; + opt->nd_opt_rdnss_reserved = 0; + opt->nd_opt_rdnss_lifetime = htonl( + rdnss->lifetime_set + ? rdnss->lifetime + : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval)); + + len += sizeof(struct nd_opt_rdnss); + IPV6_ADDR_COPY(buf + len, &rdnss->addr); + len += sizeof(struct in6_addr); + } + + /* DNS search list */ + struct rtadv_dnssl *dnssl; + + for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) { + size_t names_start; + struct nd_opt_dnssl *opt = (struct nd_opt_dnssl *)(buf + len); + + opt->nd_opt_dnssl_type = ND_OPT_DNSSL; + opt->nd_opt_dnssl_len = 1; + opt->nd_opt_dnssl_reserved = 0; + opt->nd_opt_dnssl_lifetime = htonl( + dnssl->lifetime_set + ? dnssl->lifetime + : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval)); + + len += sizeof(struct nd_opt_dnssl); + + names_start = len; + memcpy(buf + len, dnssl->encoded_name, dnssl->encoded_len); + len += dnssl->encoded_len; + + /* Zero-pad to 8-octet boundary */ + while (len % 8) + buf[len++] = '\0'; + + opt->nd_opt_dnssl_len += (len - names_start) / 8; + } + msg.msg_name = (void *)&addr; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_iov = &iov; @@ -1533,6 +1580,308 @@ DEFUN (no_ipv6_nd_mtu, return CMD_SUCCESS; } +static struct rtadv_rdnss *rtadv_rdnss_new(void) +{ + return XCALLOC(MTYPE_RTADV_RDNSS, sizeof(struct rtadv_rdnss)); +} + +static void rtadv_rdnss_free(struct rtadv_rdnss *rdnss) +{ + XFREE(MTYPE_RTADV_RDNSS, rdnss); +} + +static struct rtadv_rdnss *rtadv_rdnss_lookup(struct list *list, + struct rtadv_rdnss *rdnss) +{ + struct listnode *node; + struct rtadv_rdnss *p; + + for (ALL_LIST_ELEMENTS_RO(list, node, p)) + if (IPV6_ADDR_SAME(&p->addr, &rdnss->addr)) + return p; + return NULL; +} + +static struct rtadv_rdnss *rtadv_rdnss_get(struct list *list, + struct rtadv_rdnss *rdnss) +{ + struct rtadv_rdnss *p; + + p = rtadv_rdnss_lookup(list, rdnss); + if (p) + return p; + + p = rtadv_rdnss_new(); + memcpy(p, rdnss, sizeof(struct rtadv_rdnss)); + listnode_add(list, p); + + return p; +} + +static void rtadv_rdnss_set(struct zebra_if *zif, struct rtadv_rdnss *rdnss) +{ + struct rtadv_rdnss *p; + + p = rtadv_rdnss_get(zif->rtadv.AdvRDNSSList, rdnss); + p->lifetime = rdnss->lifetime; + p->lifetime_set = rdnss->lifetime_set; +} + +static int rtadv_rdnss_reset(struct zebra_if *zif, struct rtadv_rdnss *rdnss) +{ + struct rtadv_rdnss *p; + + p = rtadv_rdnss_lookup(zif->rtadv.AdvRDNSSList, rdnss); + if (p) { + listnode_delete(zif->rtadv.AdvRDNSSList, p); + rtadv_rdnss_free(p); + return 1; + } + + return 0; +} + +static struct rtadv_dnssl *rtadv_dnssl_new(void) +{ + return XCALLOC(MTYPE_RTADV_DNSSL, sizeof(struct rtadv_dnssl)); +} + +static void rtadv_dnssl_free(struct rtadv_dnssl *dnssl) +{ + XFREE(MTYPE_RTADV_DNSSL, dnssl); +} + +static struct rtadv_dnssl *rtadv_dnssl_lookup(struct list *list, + struct rtadv_dnssl *dnssl) +{ + struct listnode *node; + struct rtadv_dnssl *p; + + for (ALL_LIST_ELEMENTS_RO(list, node, p)) + if (!strcasecmp(p->name, dnssl->name)) + return p; + return NULL; +} + +static struct rtadv_dnssl *rtadv_dnssl_get(struct list *list, + struct rtadv_dnssl *dnssl) +{ + struct rtadv_dnssl *p; + + p = rtadv_dnssl_lookup(list, dnssl); + if (p) + return p; + + p = rtadv_dnssl_new(); + memcpy(p, dnssl, sizeof(struct rtadv_dnssl)); + listnode_add(list, p); + + return p; +} + +static void rtadv_dnssl_set(struct zebra_if *zif, struct rtadv_dnssl *dnssl) +{ + struct rtadv_dnssl *p; + + p = rtadv_dnssl_get(zif->rtadv.AdvDNSSLList, dnssl); + memcpy(p, dnssl, sizeof(struct rtadv_dnssl)); +} + +static int rtadv_dnssl_reset(struct zebra_if *zif, struct rtadv_dnssl *dnssl) +{ + struct rtadv_dnssl *p; + + p = rtadv_dnssl_lookup(zif->rtadv.AdvDNSSLList, dnssl); + if (p) { + listnode_delete(zif->rtadv.AdvDNSSLList, p); + rtadv_dnssl_free(p); + return 1; + } + + return 0; +} + +/* + * Convert dotted domain name (with or without trailing root zone dot) to + * sequence of length-prefixed labels, as described in [RFC1035 3.1]. Write up + * to strlen(in) + 2 octets to out. + * + * Returns the number of octets written to out or -1 if in does not constitute + * a valid domain name. + */ +static int rtadv_dnssl_encode(uint8_t *out, const char *in) +{ + const char *label_start, *label_end; + size_t outp; + + outp = 0; + label_start = in; + + while (*label_start) { + size_t label_len; + + label_end = strchr(label_start, '.'); + if (label_end == NULL) + label_end = label_start + strlen(label_start); + + label_len = label_end - label_start; + if (label_len >= 64) + return -1; /* labels must be 63 octets or less */ + + out[outp++] = (uint8_t)label_len; + memcpy(out + outp, label_start, label_len); + outp += label_len; + label_start += label_len; + if (*label_start == '.') + label_start++; + } + + out[outp++] = '\0'; + return outp; +} + +DEFUN(ipv6_nd_rdnss, + ipv6_nd_rdnss_cmd, + "ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Recursive DNS server information\n" + "IPv6 address\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + struct rtadv_rdnss rdnss = {0}; + + if (inet_pton(AF_INET6, argv[3]->arg, &rdnss.addr) != 1) { + vty_out(vty, "Malformed IPv6 address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (argc > 4) { + char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg + : argv[4]->text; + rdnss.lifetime = strmatch(lifetime, "infinite") + ? UINT32_MAX + : strtoll(lifetime, NULL, 10); + rdnss.lifetime_set = 1; + } + + rtadv_rdnss_set(zif, &rdnss); + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_nd_rdnss, + no_ipv6_nd_rdnss_cmd, + "no ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Recursive DNS server information\n" + "IPv6 address\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + struct rtadv_rdnss rdnss = {0}; + + if (inet_pton(AF_INET6, argv[4]->arg, &rdnss.addr) != 1) { + vty_out(vty, "Malformed IPv6 address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (rtadv_rdnss_reset(zif, &rdnss) != 1) { + vty_out(vty, "Non-existant RDNSS address\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN(ipv6_nd_dnssl, + ipv6_nd_dnssl_cmd, + "ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "DNS search list information\n" + "Domain name suffix\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + struct rtadv_dnssl dnssl = {0}; + size_t len; + int ret; + + len = strlcpy(dnssl.name, argv[3]->arg, sizeof(dnssl.name)); + if (len == 0 || len >= sizeof(dnssl.name)) { + vty_out(vty, "Malformed DNS search domain\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (dnssl.name[len - 1] == '.') { + /* + * Allow, but don't require, a trailing dot signifying the root + * zone. Canonicalize by cutting it off if present. + */ + dnssl.name[len - 1] = '\0'; + len--; + } + if (argc > 4) { + char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg + : argv[4]->text; + dnssl.lifetime = strmatch(lifetime, "infinite") + ? UINT32_MAX + : strtoll(lifetime, NULL, 10); + dnssl.lifetime_set = 1; + } + + ret = rtadv_dnssl_encode(dnssl.encoded_name, dnssl.name); + if (ret < 0) { + vty_out(vty, "Malformed DNS search domain\n"); + return CMD_WARNING_CONFIG_FAILED; + } + dnssl.encoded_len = ret; + rtadv_dnssl_set(zif, &dnssl); + + return CMD_SUCCESS; +} + +DEFUN(no_ipv6_nd_dnssl, + no_ipv6_nd_dnssl_cmd, + "no ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "DNS search list information\n" + "Domain name suffix\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + struct rtadv_dnssl dnssl = {0}; + size_t len; + + len = strlcpy(dnssl.name, argv[4]->arg, sizeof(dnssl.name)); + if (len == 0 || len >= sizeof(dnssl.name)) { + vty_out(vty, "Malformed DNS search domain\n"); + return CMD_WARNING_CONFIG_FAILED; + } + if (dnssl.name[len - 1] == '.') { + dnssl.name[len - 1] = '\0'; + len--; + } + if (rtadv_dnssl_reset(zif, &dnssl) != 1) { + vty_out(vty, "Non-existant DNS search domain\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + + /* Dump interface ND information to vty. */ static int nd_dump_vty(struct vty *vty, struct interface *ifp) { @@ -1607,6 +1956,8 @@ static int rtadv_config_write(struct vty *vty, struct interface *ifp) struct zebra_if *zif; struct listnode *node; struct rtadv_prefix *rprefix; + struct rtadv_rdnss *rdnss; + struct rtadv_dnssl *dnssl; char buf[PREFIX_STRLEN]; int interval; @@ -1688,6 +2039,29 @@ static int rtadv_config_write(struct vty *vty, struct interface *ifp) vty_out(vty, " router-address"); vty_out(vty, "\n"); } + for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) { + char buf[INET6_ADDRSTRLEN]; + + vty_out(vty, " ipv6 nd rdnss %s", + inet_ntop(AF_INET6, &rdnss->addr, buf, sizeof(buf))); + if (rdnss->lifetime_set) { + if (rdnss->lifetime == UINT32_MAX) + vty_out(vty, " infinite"); + else + vty_out(vty, " %u", rdnss->lifetime); + } + vty_out(vty, "\n"); + } + for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) { + vty_out(vty, " ipv6 nd dnssl %s", dnssl->name); + if (dnssl->lifetime_set) { + if (dnssl->lifetime == UINT32_MAX) + vty_out(vty, " infinite"); + else + vty_out(vty, " %u", dnssl->lifetime); + } + vty_out(vty, "\n"); + } return 0; } @@ -1782,6 +2156,10 @@ void rtadv_cmd_init(void) install_element(INTERFACE_NODE, &no_ipv6_nd_router_preference_cmd); install_element(INTERFACE_NODE, &ipv6_nd_mtu_cmd); install_element(INTERFACE_NODE, &no_ipv6_nd_mtu_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_rdnss_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_rdnss_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_dnssl_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_dnssl_cmd); } static int if_join_all_router(int sock, struct interface *ifp) diff --git a/zebra/rtadv.h b/zebra/rtadv.h index 03db13fd69..f7c27ebcb3 100644 --- a/zebra/rtadv.h +++ b/zebra/rtadv.h @@ -91,6 +91,37 @@ struct nd_opt_homeagent_info { /* Home Agent info */ } __attribute__((__packed__)); #endif +#ifndef ND_OPT_RDNSS +#define ND_OPT_RDNSS 25 +#endif +#ifndef ND_OPT_DNSSL +#define ND_OPT_DNSSL 31 +#endif + +#ifndef HAVE_STRUCT_ND_OPT_RDNSS +struct nd_opt_rdnss { /* Recursive DNS server option [RFC8106 5.1] */ + uint8_t nd_opt_rdnss_type; + uint8_t nd_opt_rdnss_len; + uint16_t nd_opt_rdnss_reserved; + uint32_t nd_opt_rdnss_lifetime; + /* Followed by one or more IPv6 addresses */ +} __attribute__((__packed__)); +#endif + +#ifndef HAVE_STRUCT_ND_OPT_DNSSL +struct nd_opt_dnssl { /* DNS search list option [RFC8106 5.2] */ + uint8_t nd_opt_dnssl_type; + uint8_t nd_opt_dnssl_len; + uint16_t nd_opt_dnssl_reserved; + uint32_t nd_opt_dnssl_lifetime; + /* + * Followed by one or more domain names encoded as in [RFC1035 3.1]. + * Multiple domain names are concatenated after encoding. In any case, + * the result is zero-padded to a multiple of 8 octets. + */ +} __attribute__((__packed__)); +#endif + extern const char *rtadv_pref_strs[]; #endif /* HAVE_RTADV */ diff --git a/zebra/zebra_memory.c b/zebra/zebra_memory.c index ee041b1c3d..4eaabd237d 100644 --- a/zebra/zebra_memory.c +++ b/zebra/zebra_memory.c @@ -27,6 +27,8 @@ DEFINE_MGROUP(ZEBRA, "zebra") DEFINE_MTYPE(ZEBRA, RTADV_PREFIX, "Router Advertisement Prefix") +DEFINE_MTYPE(ZEBRA, RTADV_RDNSS, "Router Advertisement RDNSS") +DEFINE_MTYPE(ZEBRA, RTADV_DNSSL, "Router Advertisement DNSSL") DEFINE_MTYPE(ZEBRA, ZEBRA_VRF, "ZEBRA VRF") DEFINE_MTYPE(ZEBRA, RE, "Route Entry") DEFINE_MTYPE(ZEBRA, RIB_QUEUE, "RIB process work queue") diff --git a/zebra/zebra_memory.h b/zebra/zebra_memory.h index de55478de2..1f3015186f 100644 --- a/zebra/zebra_memory.h +++ b/zebra/zebra_memory.h @@ -26,6 +26,8 @@ DECLARE_MGROUP(ZEBRA) DECLARE_MTYPE(RTADV_PREFIX) +DECLARE_MTYPE(RTADV_RDNSS) +DECLARE_MTYPE(RTADV_DNSSL) DECLARE_MTYPE(ZEBRA_NS) DECLARE_MTYPE(ZEBRA_VRF) DECLARE_MTYPE(RE)