diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /zebra/rtadv.c | |
parent | Initial commit. (diff) | |
download | frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip |
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'zebra/rtadv.c')
-rw-r--r-- | zebra/rtadv.c | 3071 |
1 files changed, 3071 insertions, 0 deletions
diff --git a/zebra/rtadv.c b/zebra/rtadv.c new file mode 100644 index 0000000..9af41cb --- /dev/null +++ b/zebra/rtadv.c @@ -0,0 +1,3071 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Router advertisement + * Copyright (C) 2016 Cumulus Networks + * Copyright (C) 2005 6WIND <jean-mickael.guerin@6wind.com> + * Copyright (C) 1999 Kunihiro Ishiguro + */ + +#include <zebra.h> + +#include "memory.h" +#include "sockopt.h" +#include "frrevent.h" +#include "if.h" +#include "stream.h" +#include "log.h" +#include "prefix.h" +#include "linklist.h" +#include "command.h" +#include "privs.h" +#include "vrf.h" +#include "ns.h" +#include "lib_errors.h" + +#include "zebra/interface.h" +#include "zebra/rtadv.h" +#include "zebra/debug.h" +#include "zebra/rib.h" +#include "zebra/zapi_msg.h" +#include "zebra/zebra_vrf.h" +#include "zebra/zebra_errors.h" +#include "zebra/zebra_router.h" + +extern struct zebra_privs_t zserv_privs; + +static uint32_t interfaces_configured_for_ra_from_bgp; + +#if defined(HAVE_RTADV) + +#include "zebra/rtadv_clippy.c" + +DEFINE_MTYPE_STATIC(ZEBRA, RTADV_PREFIX, "Router Advertisement Prefix"); +DEFINE_MTYPE_STATIC(ZEBRA, ADV_IF, "Advertised Interface"); + +#ifdef OPEN_BSD +#include <netinet/icmp6.h> +#endif + +/* If RFC2133 definition is used. */ +#ifndef IPV6_JOIN_GROUP +#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP +#endif +#ifndef IPV6_LEAVE_GROUP +#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP +#endif + +#define ALLNODE "ff02::1" +#define ALLROUTER "ff02::2" + +/* adv list node */ +struct adv_if { + char name[INTERFACE_NAMSIZ]; + struct adv_if_list_item list_item; +}; + +static int adv_if_cmp(const struct adv_if *a, const struct adv_if *b) +{ + return if_cmp_name_func(a->name, b->name); +} + +DECLARE_SORTLIST_UNIQ(adv_if_list, struct adv_if, list_item, adv_if_cmp); + +static int rtadv_prefix_cmp(const struct rtadv_prefix *a, + const struct rtadv_prefix *b) +{ + return prefix_cmp(&a->prefix, &b->prefix); +} + +DECLARE_RBTREE_UNIQ(rtadv_prefixes, struct rtadv_prefix, item, + rtadv_prefix_cmp); + +DEFINE_MTYPE_STATIC(ZEBRA, RTADV_RDNSS, "Router Advertisement RDNSS"); +DEFINE_MTYPE_STATIC(ZEBRA, RTADV_DNSSL, "Router Advertisement DNSSL"); + +/* Order is intentional. Matches RFC4191. This array is also used for + command matching, so only modify with care. */ +static const char *const rtadv_pref_strs[] = { + "medium", "high", "INVALID", "low", 0 +}; + +enum rtadv_event { + RTADV_START, + RTADV_STOP, + RTADV_TIMER, + RTADV_TIMER_MSEC, + RTADV_READ +}; + +static void rtadv_event(struct zebra_vrf *, enum rtadv_event, int); + +static int if_join_all_router(int, struct interface *); +static int if_leave_all_router(int, struct interface *); + +static struct zebra_vrf *rtadv_interface_get_zvrf(const struct interface *ifp) +{ + /* We use the default vrf for rtadv handling except in netns */ + if (!vrf_is_backend_netns()) + return vrf_info_lookup(VRF_DEFAULT); + + return ifp->vrf->info; +} + +static int rtadv_increment_received(struct zebra_vrf *zvrf, ifindex_t *ifindex) +{ + int ret = -1; + struct interface *iface; + struct zebra_if *zif; + + iface = if_lookup_by_index(*ifindex, zvrf->vrf->vrf_id); + if (iface && iface->info) { + zif = iface->info; + zif->ra_rcvd++; + ret = 0; + } + return ret; +} + +static int rtadv_recv_packet(struct zebra_vrf *zvrf, int sock, uint8_t *buf, + int buflen, struct sockaddr_in6 *from, + ifindex_t *ifindex, int *hoplimit) +{ + int ret; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsgptr; + struct in6_addr dst; + + char adata[1024]; + + /* Fill in message and iovec. */ + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (void *)from; + msg.msg_namelen = sizeof(struct sockaddr_in6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = (void *)adata; + msg.msg_controllen = sizeof(adata); + iov.iov_base = buf; + iov.iov_len = buflen; + + /* If recvmsg fail return minus value. */ + ret = recvmsg(sock, &msg, 0); + if (ret < 0) + return ret; + + for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; + cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + /* I want interface index which this packet comes from. */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 + && cmsgptr->cmsg_type == IPV6_PKTINFO) { + struct in6_pktinfo *ptr; + + ptr = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); + *ifindex = ptr->ipi6_ifindex; + memcpy(&dst, &ptr->ipi6_addr, sizeof(ptr->ipi6_addr)); + } + + /* Incoming packet's hop limit. */ + if (cmsgptr->cmsg_level == IPPROTO_IPV6 + && cmsgptr->cmsg_type == IPV6_HOPLIMIT) { + int *hoptr = (int *)CMSG_DATA(cmsgptr); + *hoplimit = *hoptr; + } + } + + rtadv_increment_received(zvrf, ifindex); + return ret; +} + +#define RTADV_MSG_SIZE 4096 + +/* Send router advertisement packet. */ +static void rtadv_send_packet(int sock, struct interface *ifp, + enum ipv6_nd_suppress_ra_status stop) +{ + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsgptr; + struct in6_pktinfo *pkt; + struct sockaddr_in6 addr; + static void *adata = NULL; + unsigned char buf[RTADV_MSG_SIZE]; + struct nd_router_advert *rtadv; + int ret; + int len = 0; + struct zebra_if *zif; + struct rtadv_prefix *rprefix; + uint8_t all_nodes_addr[] = {0xff, 0x02, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1}; + struct listnode *node; + uint16_t pkt_RouterLifetime; + + /* + * Allocate control message bufffer. This is dynamic because + * CMSG_SPACE is not guaranteed not to call a function. Note that + * the size will be different on different architectures due to + * differing alignment rules. + */ + if (adata == NULL) { + /* XXX Free on shutdown. */ + adata = calloc(1, CMSG_SPACE(sizeof(struct in6_pktinfo))); + + if (adata == NULL) { + zlog_debug("%s: can't malloc control data", __func__); + exit(-1); + } + } + + /* Logging of packet. */ + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug("%s(%s:%u): Tx RA, socket %u", ifp->name, + ifp->vrf->name, ifp->ifindex, sock); + + /* Fill in sockaddr_in6. */ + memset(&addr, 0, sizeof(struct sockaddr_in6)); + addr.sin6_family = AF_INET6; +#ifdef SIN6_LEN + addr.sin6_len = sizeof(struct sockaddr_in6); +#endif /* SIN6_LEN */ + addr.sin6_port = htons(IPPROTO_ICMPV6); + IPV6_ADDR_COPY(&addr.sin6_addr, all_nodes_addr); + + /* Fetch interface information. */ + zif = ifp->info; + + /* Make router advertisement message. */ + rtadv = (struct nd_router_advert *)buf; + + rtadv->nd_ra_type = ND_ROUTER_ADVERT; + rtadv->nd_ra_code = 0; + rtadv->nd_ra_cksum = 0; + + rtadv->nd_ra_curhoplimit = zif->rtadv.AdvCurHopLimit; + + /* RFC4191: Default Router Preference is 0 if Router Lifetime is 0. */ + rtadv->nd_ra_flags_reserved = zif->rtadv.AdvDefaultLifetime == 0 + ? 0 + : zif->rtadv.DefaultPreference; + rtadv->nd_ra_flags_reserved <<= 3; + + if (zif->rtadv.AdvManagedFlag) + rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED; + if (zif->rtadv.AdvOtherConfigFlag) + rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; + if (zif->rtadv.AdvHomeAgentFlag) + rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_HOME_AGENT; + /* Note that according to Neighbor Discovery (RFC 4861 [18]), + * AdvDefaultLifetime is by default based on the value of + * MaxRtrAdvInterval. AdvDefaultLifetime is used in the Router Lifetime + * field of Router Advertisements. Given that this field is expressed + * in seconds, a small MaxRtrAdvInterval value can result in a zero + * value for this field. To prevent this, routers SHOULD keep + * AdvDefaultLifetime in at least one second, even if the use of + * MaxRtrAdvInterval would result in a smaller value. -- RFC6275, 7.5 */ + pkt_RouterLifetime = + zif->rtadv.AdvDefaultLifetime != -1 + ? zif->rtadv.AdvDefaultLifetime + : MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval); + + /* send RA lifetime of 0 before stopping. rfc4861/6.2.5 */ + rtadv->nd_ra_router_lifetime = + (stop == RA_SUPPRESS) ? htons(0) : htons(pkt_RouterLifetime); + rtadv->nd_ra_reachable = htonl(zif->rtadv.AdvReachableTime); + rtadv->nd_ra_retransmit = htonl(zif->rtadv.AdvRetransTimer); + + len = sizeof(struct nd_router_advert); + + /* If both the Home Agent Preference and Home Agent Lifetime are set to + * their default values specified above, this option SHOULD NOT be + * included in the Router Advertisement messages sent by this home + * agent. -- RFC6275, 7.4 */ + if (zif->rtadv.AdvHomeAgentFlag + && (zif->rtadv.HomeAgentPreference + || zif->rtadv.HomeAgentLifetime != -1)) { + struct nd_opt_homeagent_info *ndopt_hai = + (struct nd_opt_homeagent_info *)(buf + len); + ndopt_hai->nd_opt_hai_type = ND_OPT_HA_INFORMATION; + ndopt_hai->nd_opt_hai_len = 1; + ndopt_hai->nd_opt_hai_reserved = 0; + ndopt_hai->nd_opt_hai_preference = + htons(zif->rtadv.HomeAgentPreference); + /* 16-bit unsigned integer. The lifetime associated with the + * home + * agent in units of seconds. The default value is the same as + * the + * Router Lifetime, as specified in the main body of the Router + * Advertisement. The maximum value corresponds to 18.2 hours. + * A + * value of 0 MUST NOT be used. -- RFC6275, 7.5 */ + ndopt_hai->nd_opt_hai_lifetime = + htons(zif->rtadv.HomeAgentLifetime != -1 + ? zif->rtadv.HomeAgentLifetime + : MAX(1, pkt_RouterLifetime) /* 0 is OK + for RL, + but not + for HAL*/ + ); + len += sizeof(struct nd_opt_homeagent_info); + } + + if (zif->rtadv.AdvIntervalOption) { + struct nd_opt_adv_interval *ndopt_adv = + (struct nd_opt_adv_interval *)(buf + len); + ndopt_adv->nd_opt_ai_type = ND_OPT_ADV_INTERVAL; + ndopt_adv->nd_opt_ai_len = 1; + ndopt_adv->nd_opt_ai_reserved = 0; + ndopt_adv->nd_opt_ai_interval = + htonl(zif->rtadv.MaxRtrAdvInterval); + len += sizeof(struct nd_opt_adv_interval); + } + + /* Fill in prefix. */ + frr_each (rtadv_prefixes, zif->rtadv.prefixes, rprefix) { + struct nd_opt_prefix_info *pinfo; + + pinfo = (struct nd_opt_prefix_info *)(buf + len); + + pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; + pinfo->nd_opt_pi_len = 4; + pinfo->nd_opt_pi_prefix_len = rprefix->prefix.prefixlen; + + pinfo->nd_opt_pi_flags_reserved = 0; + if (rprefix->AdvOnLinkFlag) + pinfo->nd_opt_pi_flags_reserved |= + ND_OPT_PI_FLAG_ONLINK; + if (rprefix->AdvAutonomousFlag) + pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; + if (rprefix->AdvRouterAddressFlag) + pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR; + + pinfo->nd_opt_pi_valid_time = htonl(rprefix->AdvValidLifetime); + pinfo->nd_opt_pi_preferred_time = + htonl(rprefix->AdvPreferredLifetime); + pinfo->nd_opt_pi_reserved2 = 0; + + IPV6_ADDR_COPY(&pinfo->nd_opt_pi_prefix, + &rprefix->prefix.prefix); + + len += sizeof(struct nd_opt_prefix_info); + } + + /* Hardware address. */ + if (ifp->hw_addr_len != 0) { + buf[len++] = ND_OPT_SOURCE_LINKADDR; + + /* Option length should be rounded up to next octet if + the link address does not end on an octet boundary. */ + buf[len++] = (ifp->hw_addr_len + 9) >> 3; + + memcpy(buf + len, ifp->hw_addr, ifp->hw_addr_len); + len += ifp->hw_addr_len; + + /* Pad option to end on an octet boundary. */ + memset(buf + len, 0, -(ifp->hw_addr_len + 2) & 0x7); + len += -(ifp->hw_addr_len + 2) & 0x7; + } + + /* MTU */ + if (zif->rtadv.AdvLinkMTU) { + struct nd_opt_mtu *opt = (struct nd_opt_mtu *)(buf + len); + opt->nd_opt_mtu_type = ND_OPT_MTU; + opt->nd_opt_mtu_len = 1; + opt->nd_opt_mtu_reserved = 0; + opt->nd_opt_mtu_mtu = htonl(zif->rtadv.AdvLinkMTU); + len += sizeof(struct nd_opt_mtu); + } + + /* + * There is no limit on the number of configurable recursive DNS + * servers or search list entries. We don't want the RA message + * to exceed the link's MTU (risking fragmentation) or even + * blow the stack buffer allocated for it. + */ + size_t max_len = MIN(ifp->mtu6 - 40, sizeof(buf)); + + /* Recursive DNS servers */ + struct rtadv_rdnss *rdnss; + + for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) { + size_t opt_len = + sizeof(struct nd_opt_rdnss) + sizeof(struct in6_addr); + + if (len + opt_len > max_len) { + zlog_warn( + "%s(%s:%u): Tx RA: RDNSS option would exceed MTU, omitting it", + ifp->name, ifp->vrf->name, ifp->ifindex); + goto no_more_opts; + } + struct nd_opt_rdnss *opt = (struct nd_opt_rdnss *)(buf + len); + + opt->nd_opt_rdnss_type = ND_OPT_RDNSS; + opt->nd_opt_rdnss_len = opt_len / 8; + 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 opt_len = sizeof(struct nd_opt_dnssl) + + ((dnssl->encoded_len + 7) & ~7); + + if (len + opt_len > max_len) { + zlog_warn( + "%s(%u): Tx RA: DNSSL option would exceed MTU, omitting it", + ifp->name, ifp->ifindex); + goto no_more_opts; + } + struct nd_opt_dnssl *opt = (struct nd_opt_dnssl *)(buf + len); + + opt->nd_opt_dnssl_type = ND_OPT_DNSSL; + opt->nd_opt_dnssl_len = opt_len / 8; + 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); + + 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'; + } + +no_more_opts: + + msg.msg_name = (void *)&addr; + msg.msg_namelen = sizeof(struct sockaddr_in6); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = (void *)adata; + msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + msg.msg_flags = 0; + iov.iov_base = buf; + iov.iov_len = len; + + cmsgptr = CMSG_FIRSTHDR(&msg); + cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + cmsgptr->cmsg_level = IPPROTO_IPV6; + cmsgptr->cmsg_type = IPV6_PKTINFO; + + pkt = (struct in6_pktinfo *)CMSG_DATA(cmsgptr); + memset(&pkt->ipi6_addr, 0, sizeof(struct in6_addr)); + pkt->ipi6_ifindex = ifp->ifindex; + + ret = sendmsg(sock, &msg, 0); + if (ret < 0) { + flog_err_sys(EC_LIB_SOCKET, + "%s(%u): Tx RA failed, socket %u error %d (%s)", + ifp->name, ifp->ifindex, sock, errno, + safe_strerror(errno)); + } else + zif->ra_sent++; +} + +static void rtadv_timer(struct event *thread) +{ + struct zebra_vrf *zvrf = EVENT_ARG(thread); + struct vrf *vrf; + struct interface *ifp; + struct zebra_if *zif; + int period; + + zvrf->rtadv.ra_timer = NULL; + if (adv_if_list_count(&zvrf->rtadv.adv_msec_if) == 0) { + period = 1000; /* 1 s */ + rtadv_event(zvrf, RTADV_TIMER, 1 /* 1 s */); + } else { + period = 10; /* 10 ms */ + rtadv_event(zvrf, RTADV_TIMER_MSEC, 10 /* 10 ms */); + } + + RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id) + FOR_ALL_INTERFACES (vrf, ifp) { + if (if_is_loopback(ifp) || !if_is_operative(ifp) || + IS_ZEBRA_IF_BRIDGE_SLAVE(ifp) || + !connected_get_linklocal(ifp) || + (vrf_is_backend_netns() && + ifp->vrf->vrf_id != zvrf->vrf->vrf_id)) + continue; + + zif = ifp->info; + + if (zif->rtadv.AdvSendAdvertisements) { + if (zif->rtadv.inFastRexmit + && zif->rtadv.UseFastRexmit) { + /* We assume we fast rexmit every sec so + * no + * additional vars */ + if (--zif->rtadv.NumFastReXmitsRemain + <= 0) + zif->rtadv.inFastRexmit = 0; + + if (IS_ZEBRA_DEBUG_SEND) + zlog_debug( + "Fast RA Rexmit on interface %s(%s:%u)", + ifp->name, + ifp->vrf->name, + ifp->ifindex); + + rtadv_send_packet(zvrf->rtadv.sock, ifp, + RA_ENABLE); + } else { + zif->rtadv.AdvIntervalTimer -= period; + if (zif->rtadv.AdvIntervalTimer <= 0) { + /* FIXME: using + MaxRtrAdvInterval each + time isn't what section + 6.2.4 of RFC4861 tells to do. + */ + zif->rtadv.AdvIntervalTimer = + zif->rtadv + .MaxRtrAdvInterval; + rtadv_send_packet( + zvrf->rtadv.sock, ifp, + RA_ENABLE); + } + } + } + } +} + +static void rtadv_process_solicit(struct interface *ifp) +{ + struct zebra_vrf *zvrf; + struct zebra_if *zif; + + zvrf = rtadv_interface_get_zvrf(ifp); + assert(zvrf); + zif = ifp->info; + + /* + * If FastRetransmit is enabled, send the RA immediately. + * If not enabled but it has been more than MIN_DELAY_BETWEEN_RAS + * (3 seconds) since the last RA was sent, send it now and reset + * the timer to start at the max (configured) again. + * If not enabled and it is less than 3 seconds since the last + * RA packet was sent, set the timer for 3 seconds so the next + * one will be sent with a minimum of 3 seconds between RAs. + * RFC4861 sec 6.2.6 + */ + if ((zif->rtadv.UseFastRexmit) + || (zif->rtadv.AdvIntervalTimer <= + (zif->rtadv.MaxRtrAdvInterval - MIN_DELAY_BETWEEN_RAS))) { + rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_ENABLE); + zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval; + } else + zif->rtadv.AdvIntervalTimer = MIN_DELAY_BETWEEN_RAS; +} + +static const char *rtadv_optionalhdr2str(uint8_t opt_type) +{ + switch (opt_type) { + case ND_OPT_SOURCE_LINKADDR: + return "Optional Source Link Address"; + case ND_OPT_TARGET_LINKADDR: + return "Optional Target Link Address"; + case ND_OPT_PREFIX_INFORMATION: + return "Optional Prefix Information"; + case ND_OPT_REDIRECTED_HEADER: + return "Optional Redirected Header"; + case ND_OPT_MTU: + return "Optional MTU"; + case ND_OPT_RTR_ADV_INTERVAL: + return "Optional Advertisement Interval"; + case ND_OPT_HOME_AGENT_INFO: + return "Optional Home Agent Information"; + } + + return "Unknown Optional Type"; +} + +/* + * This function processes optional attributes off of + * end of a RA packet received. At this point in + * time we only care about this in one situation + * which is when a interface does not have a LL + * v6 address. We still need to be able to install + * the mac address for v4 to v6 resolution + */ +static void rtadv_process_optional(uint8_t *optional, unsigned int len, + struct interface *ifp, + struct sockaddr_in6 *addr) +{ + char *mac; + + while (len > 0) { + struct nd_opt_hdr *opt_hdr = (struct nd_opt_hdr *)optional; + + switch(opt_hdr->nd_opt_type) { + case ND_OPT_SOURCE_LINKADDR: + mac = (char *)(optional+2); + if_nbr_mac_to_ipv4ll_neigh_update(ifp, mac, + &addr->sin6_addr, 1); + break; + default: + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug( + "%s:Received Packet with optional Header type %s(%u) that is being ignored", + __func__, + rtadv_optionalhdr2str( + opt_hdr->nd_opt_type), + opt_hdr->nd_opt_type); + break; + } + + len -= 8 * opt_hdr->nd_opt_len; + optional += 8 * opt_hdr->nd_opt_len; + } +} + +static void rtadv_process_advert(uint8_t *msg, unsigned int len, + struct interface *ifp, + struct sockaddr_in6 *addr) +{ + struct nd_router_advert *radvert; + char addr_str[INET6_ADDRSTRLEN]; + struct zebra_if *zif; + struct prefix p; + + zif = ifp->info; + + inet_ntop(AF_INET6, &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN); + + if (len < sizeof(struct nd_router_advert)) { + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug( + "%s(%s:%u): Rx RA with invalid length %d from %s", + ifp->name, ifp->vrf->name, ifp->ifindex, len, + addr_str); + return; + } + + if (!IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) { + rtadv_process_optional(msg + sizeof(struct nd_router_advert), + len - sizeof(struct nd_router_advert), + ifp, addr); + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug( + "%s(%s:%u): Rx RA with non-linklocal source address from %s", + ifp->name, ifp->vrf->name, ifp->ifindex, + addr_str); + return; + } + + radvert = (struct nd_router_advert *)msg; + +#define SIXHOUR2USEC (int64_t)6 * 60 * 60 * 1000000 + + if ((radvert->nd_ra_curhoplimit && zif->rtadv.AdvCurHopLimit) && + (radvert->nd_ra_curhoplimit != zif->rtadv.AdvCurHopLimit) && + (monotime_since(&zif->rtadv.lastadvcurhoplimit, NULL) > + SIXHOUR2USEC || + zif->rtadv.lastadvcurhoplimit.tv_sec == 0)) { + flog_warn( + EC_ZEBRA_RA_PARAM_MISMATCH, + "%s(%u): Rx RA - our AdvCurHopLimit (%u) doesn't agree with %s (%u)", + ifp->name, ifp->ifindex, zif->rtadv.AdvCurHopLimit, + addr_str, radvert->nd_ra_curhoplimit); + monotime(&zif->rtadv.lastadvcurhoplimit); + } + + if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) && + !zif->rtadv.AdvManagedFlag && + (monotime_since(&zif->rtadv.lastadvmanagedflag, NULL) > + SIXHOUR2USEC || + zif->rtadv.lastadvmanagedflag.tv_sec == 0)) { + flog_warn( + EC_ZEBRA_RA_PARAM_MISMATCH, + "%s(%u): Rx RA - our AdvManagedFlag (%u) doesn't agree with %s (%u)", + ifp->name, ifp->ifindex, zif->rtadv.AdvManagedFlag, + addr_str, + !!CHECK_FLAG(radvert->nd_ra_flags_reserved, + ND_RA_FLAG_MANAGED)); + monotime(&zif->rtadv.lastadvmanagedflag); + } + + if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) && + !zif->rtadv.AdvOtherConfigFlag && + (monotime_since(&zif->rtadv.lastadvotherconfigflag, NULL) > + SIXHOUR2USEC || + zif->rtadv.lastadvotherconfigflag.tv_sec == 0)) { + flog_warn( + EC_ZEBRA_RA_PARAM_MISMATCH, + "%s(%u): Rx RA - our AdvOtherConfigFlag (%u) doesn't agree with %s (%u)", + ifp->name, ifp->ifindex, zif->rtadv.AdvOtherConfigFlag, + addr_str, + !!CHECK_FLAG(radvert->nd_ra_flags_reserved, + ND_RA_FLAG_OTHER)); + monotime(&zif->rtadv.lastadvotherconfigflag); + } + + if ((radvert->nd_ra_reachable && zif->rtadv.AdvReachableTime) && + (ntohl(radvert->nd_ra_reachable) != zif->rtadv.AdvReachableTime) && + (monotime_since(&zif->rtadv.lastadvreachabletime, NULL) > + SIXHOUR2USEC || + zif->rtadv.lastadvreachabletime.tv_sec == 0)) { + flog_warn( + EC_ZEBRA_RA_PARAM_MISMATCH, + "%s(%u): Rx RA - our AdvReachableTime (%u) doesn't agree with %s (%u)", + ifp->name, ifp->ifindex, zif->rtadv.AdvReachableTime, + addr_str, ntohl(radvert->nd_ra_reachable)); + monotime(&zif->rtadv.lastadvreachabletime); + } + + if ((radvert->nd_ra_retransmit && zif->rtadv.AdvRetransTimer) && + (ntohl(radvert->nd_ra_retransmit) != + (unsigned int)zif->rtadv.AdvRetransTimer) && + (monotime_since(&zif->rtadv.lastadvretranstimer, NULL) > + SIXHOUR2USEC || + zif->rtadv.lastadvretranstimer.tv_sec == 0)) { + flog_warn( + EC_ZEBRA_RA_PARAM_MISMATCH, + "%s(%u): Rx RA - our AdvRetransTimer (%u) doesn't agree with %s (%u)", + ifp->name, ifp->ifindex, zif->rtadv.AdvRetransTimer, + addr_str, ntohl(radvert->nd_ra_retransmit)); + monotime(&zif->rtadv.lastadvretranstimer); + } + + /* Create entry for neighbor if not known. */ + p.family = AF_INET6; + IPV6_ADDR_COPY(&p.u.prefix6, &addr->sin6_addr); + p.prefixlen = IPV6_MAX_BITLEN; + + if (!nbr_connected_check(ifp, &p)) + nbr_connected_add_ipv6(ifp, &addr->sin6_addr); +} + + +static void rtadv_process_packet(uint8_t *buf, unsigned int len, + ifindex_t ifindex, int hoplimit, + struct sockaddr_in6 *from, + struct zebra_vrf *zvrf) +{ + struct icmp6_hdr *icmph; + struct interface *ifp; + struct zebra_if *zif; + char addr_str[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &from->sin6_addr, addr_str, INET6_ADDRSTRLEN); + + /* Interface search. */ + ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id); + if (ifp == NULL) { + flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE, + "RA/RS received on unknown IF %u from %s", ifindex, + addr_str); + return; + } + + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug("%s(%s:%u): Rx RA/RS len %d from %s", ifp->name, + ifp->vrf->name, ifp->ifindex, len, addr_str); + + if (if_is_loopback(ifp)) + return; + + /* Check interface configuration. */ + zif = ifp->info; + if (!zif->rtadv.AdvSendAdvertisements) + return; + + /* ICMP message length check. */ + if (len < sizeof(struct icmp6_hdr)) { + zlog_debug( + "%s(%s:%u): Rx RA with Invalid ICMPV6 packet length %d", + ifp->name, ifp->vrf->name, ifp->ifindex, len); + return; + } + + icmph = (struct icmp6_hdr *)buf; + + /* ICMP message type check. */ + if (icmph->icmp6_type != ND_ROUTER_SOLICIT + && icmph->icmp6_type != ND_ROUTER_ADVERT) { + zlog_debug("%s(%s:%u): Rx RA - Unwanted ICMPV6 message type %d", + ifp->name, ifp->vrf->name, ifp->ifindex, + icmph->icmp6_type); + return; + } + + /* Hoplimit check. */ + if (hoplimit >= 0 && hoplimit != 255) { + zlog_debug("%s(%s:%u): Rx RA - Invalid hoplimit %d", ifp->name, + ifp->vrf->name, ifp->ifindex, hoplimit); + return; + } + + /* Check ICMP message type. */ + if (icmph->icmp6_type == ND_ROUTER_SOLICIT) + rtadv_process_solicit(ifp); + else if (icmph->icmp6_type == ND_ROUTER_ADVERT) + rtadv_process_advert(buf, len, ifp, from); + + return; +} + +static void rtadv_read(struct event *thread) +{ + int sock; + int len; + uint8_t buf[RTADV_MSG_SIZE]; + struct sockaddr_in6 from; + ifindex_t ifindex = 0; + int hoplimit = -1; + struct zebra_vrf *zvrf = EVENT_ARG(thread); + + sock = EVENT_FD(thread); + zvrf->rtadv.ra_read = NULL; + + /* Register myself. */ + rtadv_event(zvrf, RTADV_READ, 0); + + len = rtadv_recv_packet(zvrf, sock, buf, sizeof(buf), &from, &ifindex, + &hoplimit); + + if (len < 0) { + flog_err_sys(EC_LIB_SOCKET, + "RA/RS recv failed, socket %u error %s", sock, + safe_strerror(errno)); + return; + } + + rtadv_process_packet(buf, (unsigned)len, ifindex, hoplimit, &from, zvrf); +} + +static int rtadv_make_socket(ns_id_t ns_id) +{ + int sock = -1; + int ret = 0; + struct icmp6_filter filter; + int error; + + frr_with_privs(&zserv_privs) { + + sock = ns_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, ns_id); + /* + * with privs might set errno too if it fails save + * to the side + */ + error = errno; + } + + if (sock < 0) { + zlog_warn("RTADV socket for ns: %u failure to create: %s(%u)", + ns_id, safe_strerror(error), error); + return -1; + } + + ret = setsockopt_ipv6_pktinfo(sock, 1); + if (ret < 0) { + zlog_warn("RTADV failure to set Packet Information"); + close(sock); + return ret; + } + ret = setsockopt_ipv6_multicast_loop(sock, 0); + if (ret < 0) { + zlog_warn("RTADV failure to set multicast Loop detection"); + close(sock); + return ret; + } + ret = setsockopt_ipv6_unicast_hops(sock, 255); + if (ret < 0) { + zlog_warn("RTADV failure to set maximum unicast hops"); + close(sock); + return ret; + } + ret = setsockopt_ipv6_multicast_hops(sock, 255); + if (ret < 0) { + zlog_warn("RTADV failure to set maximum multicast hops"); + close(sock); + return ret; + } + ret = setsockopt_ipv6_hoplimit(sock, 1); + if (ret < 0) { + zlog_warn("RTADV failure to set maximum incoming hop limit"); + close(sock); + return ret; + } + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + + ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, + sizeof(struct icmp6_filter)); + if (ret < 0) { + zlog_info("ICMP6_FILTER set fail: %s", safe_strerror(errno)); + close(sock); + return ret; + } + + return sock; +} + +static struct adv_if *adv_if_new(const char *name) +{ + struct adv_if *new; + + new = XCALLOC(MTYPE_ADV_IF, sizeof(struct adv_if)); + + strlcpy(new->name, name, sizeof(new->name)); + + return new; +} + +static void adv_if_free(struct adv_if *adv_if) +{ + XFREE(MTYPE_ADV_IF, adv_if); +} + +static bool adv_if_is_empty_internal(const struct adv_if_list_head *adv_if_head) +{ + return adv_if_list_count(adv_if_head) ? false : true; +} + +static struct adv_if *adv_if_add_internal(struct adv_if_list_head *adv_if_head, + const char *name) +{ + struct adv_if adv_if_lookup = {}; + struct adv_if *adv_if = NULL; + + strlcpy(adv_if_lookup.name, name, sizeof(adv_if_lookup.name)); + adv_if = adv_if_list_find(adv_if_head, &adv_if_lookup); + + if (adv_if != NULL) + return adv_if; + + adv_if = adv_if_new(adv_if_lookup.name); + adv_if_list_add(adv_if_head, adv_if); + + return NULL; +} + +static struct adv_if *adv_if_del_internal(struct adv_if_list_head *adv_if_head, + const char *name) +{ + struct adv_if adv_if_lookup = {}; + struct adv_if *adv_if = NULL; + + strlcpy(adv_if_lookup.name, name, sizeof(adv_if_lookup.name)); + adv_if = adv_if_list_find(adv_if_head, &adv_if_lookup); + + if (adv_if == NULL) + return NULL; + + adv_if_list_del(adv_if_head, adv_if); + + return adv_if; +} + +static void adv_if_clean_internal(struct adv_if_list_head *adv_if_head) +{ + struct adv_if *node = NULL; + + if (!adv_if_is_empty_internal(adv_if_head)) { + frr_each_safe (adv_if_list, adv_if_head, node) { + adv_if_list_del(adv_if_head, node); + adv_if_free(node); + } + } + + adv_if_list_fini(adv_if_head); +} + + +/* + * Add to list. On Success, return NULL, otherwise return already existing + * adv_if. + */ +static struct adv_if *adv_if_add(struct zebra_vrf *zvrf, const char *name) +{ + struct adv_if *adv_if = NULL; + + adv_if = adv_if_add_internal(&zvrf->rtadv.adv_if, name); + + if (adv_if != NULL) + return adv_if; + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u IF %s count: %zu", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), name, + adv_if_list_count(&zvrf->rtadv.adv_if)); + } + + return NULL; +} + +/* + * Del from list. On Success, return the adv_if, otherwise return NULL. Caller + * frees. + */ +static struct adv_if *adv_if_del(struct zebra_vrf *zvrf, const char *name) +{ + struct adv_if *adv_if = NULL; + + adv_if = adv_if_del_internal(&zvrf->rtadv.adv_if, name); + + if (adv_if == NULL) + return NULL; + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u IF %s count: %zu", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), name, + adv_if_list_count(&zvrf->rtadv.adv_if)); + } + + return adv_if; +} + +/* + * Add to list. On Success, return NULL, otherwise return already existing + * adv_if. + */ +static struct adv_if *adv_msec_if_add(struct zebra_vrf *zvrf, const char *name) +{ + struct adv_if *adv_if = NULL; + + adv_if = adv_if_add_internal(&zvrf->rtadv.adv_msec_if, name); + + if (adv_if != NULL) + return adv_if; + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u IF %s count: %zu", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), name, + adv_if_list_count(&zvrf->rtadv.adv_msec_if)); + } + + return NULL; +} + +/* + * Del from list. On Success, return the adv_if, otherwise return NULL. Caller + * frees. + */ +static struct adv_if *adv_msec_if_del(struct zebra_vrf *zvrf, const char *name) +{ + struct adv_if *adv_if = NULL; + + adv_if = adv_if_del_internal(&zvrf->rtadv.adv_msec_if, name); + + if (adv_if == NULL) + return NULL; + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u IF %s count: %zu", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), name, + adv_if_list_count(&zvrf->rtadv.adv_msec_if)); + } + + return adv_if; +} + +/* Clean adv_if list, called on vrf terminate */ +static void adv_if_clean(struct zebra_vrf *zvrf) +{ + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u count: %zu -> 0", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), + adv_if_list_count(&zvrf->rtadv.adv_if)); + } + + adv_if_clean_internal(&zvrf->rtadv.adv_if); +} + +/* Clean adv_msec_if list, called on vrf terminate */ +static void adv_msec_if_clean(struct zebra_vrf *zvrf) +{ + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s: %s:%u count: %zu -> 0", __func__, + VRF_LOGNAME(vrf), zvrf_id(zvrf), + adv_if_list_count(&zvrf->rtadv.adv_msec_if)); + } + + adv_if_clean_internal(&zvrf->rtadv.adv_msec_if); +} + +static struct rtadv_prefix *rtadv_prefix_new(void) +{ + return XCALLOC(MTYPE_RTADV_PREFIX, sizeof(struct rtadv_prefix)); +} + +static void rtadv_prefix_free(struct rtadv_prefix *rtadv_prefix) +{ + XFREE(MTYPE_RTADV_PREFIX, rtadv_prefix); +} + +static struct rtadv_prefix *rtadv_prefix_get(struct rtadv_prefixes_head *list, + struct prefix_ipv6 *p) +{ + struct rtadv_prefix *rprefix, ref; + + ref.prefix = *p; + + rprefix = rtadv_prefixes_find(list, &ref); + if (rprefix) + return rprefix; + + rprefix = rtadv_prefix_new(); + memcpy(&rprefix->prefix, p, sizeof(struct prefix_ipv6)); + rtadv_prefixes_add(list, rprefix); + + return rprefix; +} + +static void rtadv_prefix_set_defaults(struct rtadv_prefix *rp) +{ + rp->AdvAutonomousFlag = 1; + rp->AdvOnLinkFlag = 1; + rp->AdvRouterAddressFlag = 0; + rp->AdvPreferredLifetime = RTADV_PREFERRED_LIFETIME; + rp->AdvValidLifetime = RTADV_VALID_LIFETIME; +} + +static void rtadv_prefix_set(struct zebra_if *zif, struct rtadv_prefix *rp) +{ + struct rtadv_prefix *rprefix; + + rprefix = rtadv_prefix_get(zif->rtadv.prefixes, &rp->prefix); + + /* + * Set parameters based on where the prefix is created. + * If auto-created based on kernel address addition, set the + * default values. If created from a manual "ipv6 nd prefix" + * command, take the parameters from the manual command. Note + * that if the manual command exists, the default values will + * not overwrite the manual values. + */ + if (rp->AdvPrefixCreate == PREFIX_SRC_MANUAL) { + if (rprefix->AdvPrefixCreate == PREFIX_SRC_AUTO) + rprefix->AdvPrefixCreate = PREFIX_SRC_BOTH; + else + rprefix->AdvPrefixCreate = PREFIX_SRC_MANUAL; + + rprefix->AdvAutonomousFlag = rp->AdvAutonomousFlag; + rprefix->AdvOnLinkFlag = rp->AdvOnLinkFlag; + rprefix->AdvRouterAddressFlag = rp->AdvRouterAddressFlag; + rprefix->AdvPreferredLifetime = rp->AdvPreferredLifetime; + rprefix->AdvValidLifetime = rp->AdvValidLifetime; + } else if (rp->AdvPrefixCreate == PREFIX_SRC_AUTO) { + if (rprefix->AdvPrefixCreate == PREFIX_SRC_MANUAL) + rprefix->AdvPrefixCreate = PREFIX_SRC_BOTH; + else { + rprefix->AdvPrefixCreate = PREFIX_SRC_AUTO; + rtadv_prefix_set_defaults(rprefix); + } + } +} + +static int rtadv_prefix_reset(struct zebra_if *zif, struct rtadv_prefix *rp) +{ + struct rtadv_prefix *rprefix; + + rprefix = rtadv_prefixes_find(zif->rtadv.prefixes, rp); + if (rprefix != NULL) { + + /* + * When deleting an address from the list, need to take care + * it wasn't defined both automatically via kernel + * address addition as well as manually by vtysh cli. If both, + * we don't actually delete but may change the parameters + * back to default if a manually defined entry is deleted. + */ + if (rp->AdvPrefixCreate == PREFIX_SRC_MANUAL) { + if (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH) { + rprefix->AdvPrefixCreate = PREFIX_SRC_AUTO; + rtadv_prefix_set_defaults(rprefix); + return 1; + } + } else if (rp->AdvPrefixCreate == PREFIX_SRC_AUTO) { + if (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH) { + rprefix->AdvPrefixCreate = PREFIX_SRC_MANUAL; + return 1; + } + } + + rtadv_prefixes_del(zif->rtadv.prefixes, rprefix); + rtadv_prefix_free(rprefix); + return 1; + } else + return 0; +} + +/* Add IPv6 prefixes learned from the kernel to the RA prefix list */ +void rtadv_add_prefix(struct zebra_if *zif, const struct prefix_ipv6 *p) +{ + struct rtadv_prefix rp; + + rp.prefix = *p; + apply_mask_ipv6(&rp.prefix); + rp.AdvPrefixCreate = PREFIX_SRC_AUTO; + rtadv_prefix_set(zif, &rp); +} + +/* Delete IPv6 prefixes removed by the kernel from the RA prefix list */ +void rtadv_delete_prefix(struct zebra_if *zif, const struct prefix *p) +{ + struct rtadv_prefix rp; + + rp.prefix = *((struct prefix_ipv6 *)p); + apply_mask_ipv6(&rp.prefix); + rp.AdvPrefixCreate = PREFIX_SRC_AUTO; + rtadv_prefix_reset(zif, &rp); +} + +static void rtadv_start_interface_events(struct zebra_vrf *zvrf, + struct zebra_if *zif) +{ + struct adv_if *adv_if = NULL; + + if (zif->ifp->ifindex == IFINDEX_INTERNAL) { + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug( + "%s(%s) has not configured an ifindex yet, delaying until we have one", + zif->ifp->name, zvrf->vrf->name); + return; + } + + adv_if = adv_if_add(zvrf, zif->ifp->name); + if (adv_if != NULL) + return; /* Already added */ + + if_join_all_router(zvrf->rtadv.sock, zif->ifp); + + if (adv_if_list_count(&zvrf->rtadv.adv_if) == 1) + rtadv_event(zvrf, RTADV_START, 0); +} + +static void ipv6_nd_suppress_ra_set(struct interface *ifp, + enum ipv6_nd_suppress_ra_status status) +{ + struct zebra_if *zif; + struct zebra_vrf *zvrf; + struct adv_if *adv_if = NULL; + + zif = ifp->info; + + zvrf = rtadv_interface_get_zvrf(ifp); + + if (status == RA_SUPPRESS) { + /* RA is currently enabled */ + if (zif->rtadv.AdvSendAdvertisements) { + rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_SUPPRESS); + zif->rtadv.AdvSendAdvertisements = 0; + zif->rtadv.AdvIntervalTimer = 0; + + adv_if = adv_if_del(zvrf, ifp->name); + if (adv_if == NULL) + return; /* Nothing to delete */ + + adv_if_free(adv_if); + + if_leave_all_router(zvrf->rtadv.sock, ifp); + + if (adv_if_list_count(&zvrf->rtadv.adv_if) == 0) + rtadv_event(zvrf, RTADV_STOP, 0); + } + } else { + if (!zif->rtadv.AdvSendAdvertisements) { + zif->rtadv.AdvSendAdvertisements = 1; + zif->rtadv.AdvIntervalTimer = 0; + if ((zif->rtadv.MaxRtrAdvInterval >= 1000) + && zif->rtadv.UseFastRexmit) { + /* + * Enable Fast RA only when RA interval is in + * secs and Fast RA retransmit is enabled + */ + zif->rtadv.inFastRexmit = 1; + zif->rtadv.NumFastReXmitsRemain = + RTADV_NUM_FAST_REXMITS; + } + + rtadv_start_interface_events(zvrf, zif); + } + } +} + +/* + * Handle client (BGP) message to enable or disable IPv6 RA on an interface. + * Note that while the client could request RA on an interface on which the + * operator has not enabled RA, RA won't be disabled upon client request + * if the operator has explicitly enabled RA. The enable request can also + * specify a RA interval (in seconds). + */ +static void zebra_interface_radv_set(ZAPI_HANDLER_ARGS, int enable) +{ + struct stream *s; + ifindex_t ifindex; + struct interface *ifp; + struct zebra_if *zif; + uint32_t ra_interval; + + s = msg; + + /* Get interface index and RA interval. */ + STREAM_GETL(s, ifindex); + STREAM_GETL(s, ra_interval); + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s:%u: IF %u RA %s from client %s, interval %ums", + VRF_LOGNAME(vrf), zvrf_id(zvrf), ifindex, + enable ? "enable" : "disable", + zebra_route_string(client->proto), ra_interval); + } + + /* Locate interface and check VRF match. */ + ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id); + if (!ifp) { + struct vrf *vrf = zvrf->vrf; + + flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE, + "%s:%u: IF %u RA %s client %s - interface unknown", + VRF_LOGNAME(vrf), zvrf_id(zvrf), ifindex, + enable ? "enable" : "disable", + zebra_route_string(client->proto)); + return; + } + if (vrf_is_backend_netns() && ifp->vrf->vrf_id != zvrf_id(zvrf)) { + zlog_debug( + "%s:%u: IF %u RA %s client %s - VRF mismatch, IF VRF %u", + ifp->vrf->name, zvrf_id(zvrf), ifindex, + enable ? "enable" : "disable", + zebra_route_string(client->proto), ifp->vrf->vrf_id); + return; + } + + zif = ifp->info; + if (enable) { + if (!CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) + interfaces_configured_for_ra_from_bgp++; + + SET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED); + ipv6_nd_suppress_ra_set(ifp, RA_ENABLE); + if (ra_interval + && (ra_interval * 1000) < (unsigned int) zif->rtadv.MaxRtrAdvInterval + && !CHECK_FLAG(zif->rtadv.ra_configured, + VTY_RA_INTERVAL_CONFIGURED)) + zif->rtadv.MaxRtrAdvInterval = ra_interval * 1000; + } else { + if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) + interfaces_configured_for_ra_from_bgp--; + + UNSET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED); + if (!CHECK_FLAG(zif->rtadv.ra_configured, + VTY_RA_INTERVAL_CONFIGURED)) + zif->rtadv.MaxRtrAdvInterval = + RTADV_MAX_RTR_ADV_INTERVAL; + if (!CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED)) + ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS); + } +stream_failure: + return; +} + +/* + * send router lifetime value of zero in RAs on this interface since we're + * ceasing to advertise and want to let our neighbors know. + * RFC 4861 secion 6.2.5 + */ +void rtadv_stop_ra(struct interface *ifp) +{ + struct zebra_if *zif; + struct zebra_vrf *zvrf; + + zif = ifp->info; + zvrf = rtadv_interface_get_zvrf(ifp); + + if (zif->rtadv.AdvSendAdvertisements) + rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_SUPPRESS); +} + +/* + * Send router lifetime value of zero in RAs on all interfaces since we're + * ceasing to advertise globally and want to let all of our neighbors know + * RFC 4861 secion 6.2.5 + * + * Delete all ipv6 global prefixes added to the router advertisement prefix + * lists prior to ceasing. + */ +void rtadv_stop_ra_all(void) +{ + struct vrf *vrf; + struct interface *ifp; + struct zebra_if *zif; + struct rtadv_prefix *rprefix; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) + FOR_ALL_INTERFACES (vrf, ifp) { + zif = ifp->info; + + frr_each_safe (rtadv_prefixes, zif->rtadv.prefixes, + rprefix) + rtadv_prefix_reset(zif, rprefix); + + rtadv_stop_ra(ifp); + } +} + +void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS) +{ + zebra_interface_radv_set(client, hdr, msg, zvrf, 0); +} +void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS) +{ + zebra_interface_radv_set(client, hdr, msg, zvrf, 1); +} + +static void show_zvrf_rtadv_adv_if_helper(struct vty *vty, + struct adv_if_list_head *adv_if_head) +{ + struct adv_if *node = NULL; + + if (!adv_if_is_empty_internal(adv_if_head)) { + frr_each (adv_if_list, adv_if_head, node) { + vty_out(vty, " %s\n", node->name); + } + } + + vty_out(vty, "\n"); +} + +static void show_zvrf_rtadv_helper(struct vty *vty, struct zebra_vrf *zvrf) +{ + vty_out(vty, "VRF: %s\n", zvrf_name(zvrf)); + vty_out(vty, " Interfaces:\n"); + show_zvrf_rtadv_adv_if_helper(vty, &zvrf->rtadv.adv_if); + + vty_out(vty, " Interfaces(msec):\n"); + show_zvrf_rtadv_adv_if_helper(vty, &zvrf->rtadv.adv_msec_if); +} + +DEFPY(show_ipv6_nd_ra_if, show_ipv6_nd_ra_if_cmd, + "show ipv6 nd ra-interfaces [vrf<NAME$vrf_name|all$vrf_all>]", + SHOW_STR IP6_STR + "Neighbor discovery\n" + "Route Advertisement Interfaces\n" VRF_FULL_CMD_HELP_STR) +{ + struct zebra_vrf *zvrf = NULL; + + if (!vrf_is_backend_netns() && (vrf_name || vrf_all)) { + vty_out(vty, + "%% VRF subcommand only applicable for netns-based vrfs.\n"); + return CMD_WARNING; + } + + if (vrf_all) { + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + struct zebra_vrf *zvrf; + + zvrf = vrf->info; + if (!zvrf) + continue; + + show_zvrf_rtadv_helper(vty, zvrf); + } + + return CMD_SUCCESS; + } + + if (vrf_name) + zvrf = zebra_vrf_lookup_by_name(vrf_name); + else + zvrf = zebra_vrf_lookup_by_name(VRF_DEFAULT_NAME); + + if (!zvrf) { + vty_out(vty, "%% VRF '%s' specified does not exist\n", + vrf_name); + return CMD_WARNING; + } + + show_zvrf_rtadv_helper(vty, zvrf); + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_ra_fast_retrans, + ipv6_nd_ra_fast_retrans_cmd, + "ipv6 nd ra-fast-retrans", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Fast retransmit of RA packets\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.UseFastRexmit = true; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_ra_fast_retrans, + no_ipv6_nd_ra_fast_retrans_cmd, + "no ipv6 nd ra-fast-retrans", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Fast retransmit of RA packets\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.UseFastRexmit = false; + + return CMD_SUCCESS; +} + +DEFPY (ipv6_nd_ra_hop_limit, + ipv6_nd_ra_hop_limit_cmd, + "ipv6 nd ra-hop-limit (0-255)$hopcount", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Hop Limit\n" + "Advertisement Hop Limit in hops (default:64)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.AdvCurHopLimit = hopcount; + + return CMD_SUCCESS; +} + +DEFPY (no_ipv6_nd_ra_hop_limit, + no_ipv6_nd_ra_hop_limit_cmd, + "no ipv6 nd ra-hop-limit [(0-255)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Hop Limit\n" + "Advertisement Hop Limit in hops\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.AdvCurHopLimit = RTADV_DEFAULT_HOPLIMIT; + + return CMD_SUCCESS; +} + +DEFPY (ipv6_nd_ra_retrans_interval, + ipv6_nd_ra_retrans_interval_cmd, + "ipv6 nd ra-retrans-interval (0-4294967295)$interval", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Retransmit Interval\n" + "Advertisement Retransmit Interval in msec\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on loopback interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.AdvRetransTimer = interval; + + return CMD_SUCCESS; +} + +DEFPY (no_ipv6_nd_ra_retrans_interval, + no_ipv6_nd_ra_retrans_interval_cmd, + "no ipv6 nd ra-retrans-interval [(0-4294967295)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Retransmit Interval\n" + "Advertisement Retransmit Interval in msec\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot remove IPv6 Router Advertisements on loopback interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.AdvRetransTimer = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_suppress_ra, + ipv6_nd_suppress_ra_cmd, + "ipv6 nd suppress-ra", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Suppress Router Advertisement\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (!CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) + ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS); + + UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_suppress_ra, + no_ipv6_nd_suppress_ra_cmd, + "no ipv6 nd suppress-ra", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Suppress Router Advertisement\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + if (if_is_loopback(ifp)) { + vty_out(vty, + "Cannot configure IPv6 Router Advertisements on this interface\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + ipv6_nd_suppress_ra_set(ifp, RA_ENABLE); + SET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED); + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_ra_interval_msec, + ipv6_nd_ra_interval_msec_cmd, + "ipv6 nd ra-interval msec (70-1800000)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Router Advertisement interval\n" + "Router Advertisement interval in milliseconds\n" + "Router Advertisement interval in milliseconds\n") +{ + int idx_number = 4; + VTY_DECLVAR_CONTEXT(interface, ifp); + unsigned interval; + struct zebra_if *zif = ifp->info; + struct zebra_vrf *zvrf; + struct adv_if *adv_if; + + zvrf = rtadv_interface_get_zvrf(ifp); + + interval = strtoul(argv[idx_number]->arg, NULL, 10); + if ((zif->rtadv.AdvDefaultLifetime != -1 + && interval > (unsigned)zif->rtadv.AdvDefaultLifetime * 1000)) { + vty_out(vty, + "This ra-interval would conflict with configured ra-lifetime!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (zif->rtadv.MaxRtrAdvInterval % 1000) { + adv_if = adv_msec_if_del(zvrf, ifp->name); + if (adv_if != NULL) + adv_if_free(adv_if); + } + + if (interval % 1000) + (void)adv_msec_if_add(zvrf, ifp->name); + + SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); + zif->rtadv.MaxRtrAdvInterval = interval; + zif->rtadv.MinRtrAdvInterval = 0.33 * interval; + zif->rtadv.AdvIntervalTimer = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_ra_interval, + ipv6_nd_ra_interval_cmd, + "ipv6 nd ra-interval (1-1800)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Router Advertisement interval\n" + "Router Advertisement interval in seconds\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + unsigned interval; + struct zebra_if *zif = ifp->info; + struct zebra_vrf *zvrf; + struct adv_if *adv_if; + + zvrf = rtadv_interface_get_zvrf(ifp); + + interval = strtoul(argv[idx_number]->arg, NULL, 10); + if ((zif->rtadv.AdvDefaultLifetime != -1 + && interval > (unsigned)zif->rtadv.AdvDefaultLifetime)) { + vty_out(vty, + "This ra-interval would conflict with configured ra-lifetime!\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (zif->rtadv.MaxRtrAdvInterval % 1000) { + adv_if = adv_msec_if_del(zvrf, ifp->name); + if (adv_if != NULL) + adv_if_free(adv_if); + } + + /* convert to milliseconds */ + interval = interval * 1000; + + SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); + zif->rtadv.MaxRtrAdvInterval = interval; + zif->rtadv.MinRtrAdvInterval = 0.33 * interval; + zif->rtadv.AdvIntervalTimer = 0; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_ra_interval, + no_ipv6_nd_ra_interval_cmd, + "no ipv6 nd ra-interval [<(1-1800)|msec (1-1800000)>]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Router Advertisement interval\n" + "Router Advertisement interval in seconds\n" + "Specify millisecond router advertisement interval\n" + "Router Advertisement interval in milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + struct zebra_vrf *zvrf = NULL; + struct adv_if *adv_if; + + zvrf = rtadv_interface_get_zvrf(ifp); + + if (zif->rtadv.MaxRtrAdvInterval % 1000) { + adv_if = adv_msec_if_del(zvrf, ifp->name); + if (adv_if != NULL) + adv_if_free(adv_if); + } + + UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED); + + if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED)) + zif->rtadv.MaxRtrAdvInterval = 10000; + else + zif->rtadv.MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL; + + zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval; + zif->rtadv.MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_ra_lifetime, + ipv6_nd_ra_lifetime_cmd, + "ipv6 nd ra-lifetime (0-9000)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Router lifetime\n" + "Router lifetime in seconds (0 stands for a non-default gw)\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + int lifetime; + + lifetime = strtoul(argv[idx_number]->arg, NULL, 10); + + /* The value to be placed in the Router Lifetime field + * of Router Advertisements sent from the interface, + * in seconds. MUST be either zero or between + * MaxRtrAdvInterval and 9000 seconds. -- RFC4861, 6.2.1 */ + if ((lifetime != 0 && lifetime * 1000 < zif->rtadv.MaxRtrAdvInterval)) { + vty_out(vty, + "This ra-lifetime would conflict with configured ra-interval\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + zif->rtadv.AdvDefaultLifetime = lifetime; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_ra_lifetime, + no_ipv6_nd_ra_lifetime_cmd, + "no ipv6 nd ra-lifetime [(0-9000)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Router lifetime\n" + "Router lifetime in seconds (0 stands for a non-default gw)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvDefaultLifetime = -1; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_reachable_time, + ipv6_nd_reachable_time_cmd, + "ipv6 nd reachable-time (1-3600000)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Reachable time\n" + "Reachable time in milliseconds\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + zif->rtadv.AdvReachableTime = strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_reachable_time, + no_ipv6_nd_reachable_time_cmd, + "no ipv6 nd reachable-time [(1-3600000)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Reachable time\n" + "Reachable time in milliseconds\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvReachableTime = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_homeagent_preference, + ipv6_nd_homeagent_preference_cmd, + "ipv6 nd home-agent-preference (0-65535)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent preference\n" + "preference value (default is 0, least preferred)\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + zif->rtadv.HomeAgentPreference = + strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_homeagent_preference, + no_ipv6_nd_homeagent_preference_cmd, + "no ipv6 nd home-agent-preference [(0-65535)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent preference\n" + "preference value (default is 0, least preferred)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.HomeAgentPreference = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_homeagent_lifetime, + ipv6_nd_homeagent_lifetime_cmd, + "ipv6 nd home-agent-lifetime (0-65520)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent lifetime\n" + "Home Agent lifetime in seconds (0 to track ra-lifetime)\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + zif->rtadv.HomeAgentLifetime = strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_homeagent_lifetime, + no_ipv6_nd_homeagent_lifetime_cmd, + "no ipv6 nd home-agent-lifetime [(0-65520)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent lifetime\n" + "Home Agent lifetime in seconds (0 to track ra-lifetime)\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.HomeAgentLifetime = -1; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_managed_config_flag, + ipv6_nd_managed_config_flag_cmd, + "ipv6 nd managed-config-flag", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Managed address configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvManagedFlag = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_managed_config_flag, + no_ipv6_nd_managed_config_flag_cmd, + "no ipv6 nd managed-config-flag", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Managed address configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvManagedFlag = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_homeagent_config_flag, + ipv6_nd_homeagent_config_flag_cmd, + "ipv6 nd home-agent-config-flag", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvHomeAgentFlag = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_homeagent_config_flag, + no_ipv6_nd_homeagent_config_flag_cmd, + "no ipv6 nd home-agent-config-flag", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Home Agent configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvHomeAgentFlag = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_adv_interval_config_option, + ipv6_nd_adv_interval_config_option_cmd, + "ipv6 nd adv-interval-option", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Interval Option\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvIntervalOption = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_adv_interval_config_option, + no_ipv6_nd_adv_interval_config_option_cmd, + "no ipv6 nd adv-interval-option", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertisement Interval Option\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvIntervalOption = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_other_config_flag, + ipv6_nd_other_config_flag_cmd, + "ipv6 nd other-config-flag", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Other statefull configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvOtherConfigFlag = 1; + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_other_config_flag, + no_ipv6_nd_other_config_flag_cmd, + "no ipv6 nd other-config-flag", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Other statefull configuration flag\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.AdvOtherConfigFlag = 0; + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_prefix, + ipv6_nd_prefix_cmd, + "ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] [<router-address|off-link [no-autoconfig]|no-autoconfig [off-link]>]", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Prefix information\n" + "IPv6 prefix\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n" + "Preferred lifetime in seconds\n" + "Infinite preferred lifetime\n" + "Set Router Address flag\n" + "Do not use prefix for onlink determination\n" + "Do not use prefix for autoconfiguration\n" + "Do not use prefix for autoconfiguration\n" + "Do not use prefix for onlink determination\n") +{ + /* prelude */ + char *prefix = argv[3]->arg; + int lifetimes = (argc > 4) && (argv[4]->type == RANGE_TKN + || strmatch(argv[4]->text, "infinite")); + int routeropts = lifetimes ? argc > 6 : argc > 4; + + int idx_routeropts = routeropts ? (lifetimes ? 6 : 4) : 0; + + char *lifetime = NULL, *preflifetime = NULL; + int routeraddr = 0, offlink = 0, noautoconf = 0; + if (lifetimes) { + lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg + : argv[4]->text; + preflifetime = argv[5]->type == RANGE_TKN ? argv[5]->arg + : argv[5]->text; + } + if (routeropts) { + routeraddr = + strmatch(argv[idx_routeropts]->text, "router-address"); + if (!routeraddr) { + offlink = (argc > idx_routeropts + 1 + || strmatch(argv[idx_routeropts]->text, + "off-link")); + noautoconf = (argc > idx_routeropts + 1 + || strmatch(argv[idx_routeropts]->text, + "no-autoconfig")); + } + } + + /* business */ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zebra_if = ifp->info; + int ret; + struct rtadv_prefix rp; + + ret = str2prefix_ipv6(prefix, &rp.prefix); + if (!ret) { + vty_out(vty, "Malformed IPv6 prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */ + rp.AdvOnLinkFlag = !offlink; + rp.AdvAutonomousFlag = !noautoconf; + rp.AdvRouterAddressFlag = routeraddr; + rp.AdvValidLifetime = RTADV_VALID_LIFETIME; + rp.AdvPreferredLifetime = RTADV_PREFERRED_LIFETIME; + rp.AdvPrefixCreate = PREFIX_SRC_MANUAL; + + if (lifetimes) { + rp.AdvValidLifetime = strmatch(lifetime, "infinite") + ? UINT32_MAX + : strtoll(lifetime, NULL, 10); + rp.AdvPreferredLifetime = + strmatch(preflifetime, "infinite") + ? UINT32_MAX + : strtoll(preflifetime, NULL, 10); + if (rp.AdvPreferredLifetime > rp.AdvValidLifetime) { + vty_out(vty, "Invalid preferred lifetime\n"); + return CMD_WARNING_CONFIG_FAILED; + } + } + + rtadv_prefix_set(zebra_if, &rp); + + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_prefix, + no_ipv6_nd_prefix_cmd, + "no ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] [<router-address|off-link [no-autoconfig]|no-autoconfig [off-link]>]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Prefix information\n" + "IPv6 prefix\n" + "Valid lifetime in seconds\n" + "Infinite valid lifetime\n" + "Preferred lifetime in seconds\n" + "Infinite preferred lifetime\n" + "Set Router Address flag\n" + "Do not use prefix for onlink determination\n" + "Do not use prefix for autoconfiguration\n" + "Do not use prefix for autoconfiguration\n" + "Do not use prefix for onlink determination\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zebra_if = ifp->info; + int ret; + struct rtadv_prefix rp; + char *prefix = argv[4]->arg; + + ret = str2prefix_ipv6(prefix, &rp.prefix); + if (!ret) { + vty_out(vty, "Malformed IPv6 prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */ + rp.AdvPrefixCreate = PREFIX_SRC_MANUAL; + + ret = rtadv_prefix_reset(zebra_if, &rp); + if (!ret) { + vty_out(vty, "Non-existant IPv6 prefix\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_router_preference, + ipv6_nd_router_preference_cmd, + "ipv6 nd router-preference <high|medium|low>", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Default router preference\n" + "High default router preference\n" + "Medium default router preference (default)\n" + "Low default router preference\n") +{ + int idx_high_medium_low = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + int i = 0; + + while (0 != rtadv_pref_strs[i]) { + if (strncmp(argv[idx_high_medium_low]->arg, rtadv_pref_strs[i], + 1) + == 0) { + zif->rtadv.DefaultPreference = i; + return CMD_SUCCESS; + } + i++; + } + + return CMD_ERR_NO_MATCH; +} + +DEFUN (no_ipv6_nd_router_preference, + no_ipv6_nd_router_preference_cmd, + "no ipv6 nd router-preference [<high|medium|low>]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Default router preference\n" + "High default router preference\n" + "Medium default router preference (default)\n" + "Low default router preference\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + + zif->rtadv.DefaultPreference = + RTADV_PREF_MEDIUM; /* Default per RFC4191. */ + + return CMD_SUCCESS; +} + +DEFUN (ipv6_nd_mtu, + ipv6_nd_mtu_cmd, + "ipv6 nd mtu (1-65535)", + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertised MTU\n" + "MTU in bytes\n") +{ + int idx_number = 3; + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + zif->rtadv.AdvLinkMTU = strtoul(argv[idx_number]->arg, NULL, 10); + return CMD_SUCCESS; +} + +DEFUN (no_ipv6_nd_mtu, + no_ipv6_nd_mtu_cmd, + "no ipv6 nd mtu [(1-65535)]", + NO_STR + "Interface IPv6 config commands\n" + "Neighbor discovery\n" + "Advertised MTU\n" + "MTU in bytes\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct zebra_if *zif = ifp->info; + zif->rtadv.AdvLinkMTU = 0; + 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 = {}; + + 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 = {}; + + 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 = {}; + 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 = {}; + 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) +{ + struct zebra_if *zif; + struct rtadvconf *rtadv; + int interval; + + zif = (struct zebra_if *)ifp->info; + rtadv = &zif->rtadv; + + if (rtadv->AdvSendAdvertisements) { + vty_out(vty, + " ND advertised reachable time is %d milliseconds\n", + rtadv->AdvReachableTime); + vty_out(vty, + " ND advertised retransmit interval is %u milliseconds\n", + rtadv->AdvRetransTimer); + vty_out(vty, " ND advertised hop-count limit is %d hops\n", + rtadv->AdvCurHopLimit); + vty_out(vty, " ND router advertisements sent: %d rcvd: %d\n", + zif->ra_sent, zif->ra_rcvd); + interval = rtadv->MaxRtrAdvInterval; + if (interval % 1000) + vty_out(vty, + " ND router advertisements are sent every %d milliseconds\n", + interval); + else + vty_out(vty, + " ND router advertisements are sent every %d seconds\n", + interval / 1000); + if (!rtadv->UseFastRexmit) + vty_out(vty, + " ND router advertisements do not use fast retransmit\n"); + + if (rtadv->AdvDefaultLifetime != -1) + vty_out(vty, + " ND router advertisements live for %d seconds\n", + rtadv->AdvDefaultLifetime); + else + vty_out(vty, + " ND router advertisements lifetime tracks ra-interval\n"); + vty_out(vty, + " ND router advertisement default router preference is %s\n", + rtadv_pref_strs[rtadv->DefaultPreference]); + if (rtadv->AdvManagedFlag) + vty_out(vty, + " Hosts use DHCP to obtain routable addresses.\n"); + else + vty_out(vty, + " Hosts use stateless autoconfig for addresses.\n"); + if (rtadv->AdvHomeAgentFlag) { + vty_out(vty, + " ND router advertisements with Home Agent flag bit set.\n"); + if (rtadv->HomeAgentLifetime != -1) + vty_out(vty, + " Home Agent lifetime is %u seconds\n", + rtadv->HomeAgentLifetime); + else + vty_out(vty, + " Home Agent lifetime tracks ra-lifetime\n"); + vty_out(vty, " Home Agent preference is %u\n", + rtadv->HomeAgentPreference); + } + if (rtadv->AdvIntervalOption) + vty_out(vty, + " ND router advertisements with Adv. Interval option.\n"); + } + return 0; +} + + +/* Write configuration about router advertisement. */ +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; + int interval; + + zif = ifp->info; + + if (!if_is_loopback(ifp)) { + if (zif->rtadv.AdvSendAdvertisements + && CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED)) + vty_out(vty, " no ipv6 nd suppress-ra\n"); + } + + interval = zif->rtadv.MaxRtrAdvInterval; + if (CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED)) { + if (interval % 1000) + vty_out(vty, " ipv6 nd ra-interval msec %d\n", + interval); + else if (interval != RTADV_MAX_RTR_ADV_INTERVAL) + vty_out(vty, " ipv6 nd ra-interval %d\n", + interval / 1000); + } + + if (zif->rtadv.AdvIntervalOption) + vty_out(vty, " ipv6 nd adv-interval-option\n"); + + if (!zif->rtadv.UseFastRexmit) + vty_out(vty, " no ipv6 nd ra-fast-retrans\n"); + + if (zif->rtadv.AdvRetransTimer != 0) + vty_out(vty, " ipv6 nd ra-retrans-interval %u\n", + zif->rtadv.AdvRetransTimer); + + if (zif->rtadv.AdvCurHopLimit != RTADV_DEFAULT_HOPLIMIT) + vty_out(vty, " ipv6 nd ra-hop-limit %d\n", + zif->rtadv.AdvCurHopLimit); + + if (zif->rtadv.AdvDefaultLifetime != -1) + vty_out(vty, " ipv6 nd ra-lifetime %d\n", + zif->rtadv.AdvDefaultLifetime); + + if (zif->rtadv.HomeAgentPreference) + vty_out(vty, " ipv6 nd home-agent-preference %u\n", + zif->rtadv.HomeAgentPreference); + + if (zif->rtadv.HomeAgentLifetime != -1) + vty_out(vty, " ipv6 nd home-agent-lifetime %u\n", + zif->rtadv.HomeAgentLifetime); + + if (zif->rtadv.AdvHomeAgentFlag) + vty_out(vty, " ipv6 nd home-agent-config-flag\n"); + + if (zif->rtadv.AdvReachableTime) + vty_out(vty, " ipv6 nd reachable-time %d\n", + zif->rtadv.AdvReachableTime); + + if (zif->rtadv.AdvManagedFlag) + vty_out(vty, " ipv6 nd managed-config-flag\n"); + + if (zif->rtadv.AdvOtherConfigFlag) + vty_out(vty, " ipv6 nd other-config-flag\n"); + + if (zif->rtadv.DefaultPreference != RTADV_PREF_MEDIUM) + vty_out(vty, " ipv6 nd router-preference %s\n", + rtadv_pref_strs[zif->rtadv.DefaultPreference]); + + if (zif->rtadv.AdvLinkMTU) + vty_out(vty, " ipv6 nd mtu %d\n", zif->rtadv.AdvLinkMTU); + + frr_each (rtadv_prefixes, zif->rtadv.prefixes, rprefix) { + if ((rprefix->AdvPrefixCreate == PREFIX_SRC_MANUAL) + || (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH)) { + vty_out(vty, " ipv6 nd prefix %pFX", &rprefix->prefix); + if ((rprefix->AdvValidLifetime != RTADV_VALID_LIFETIME) + || (rprefix->AdvPreferredLifetime + != RTADV_PREFERRED_LIFETIME)) { + if (rprefix->AdvValidLifetime == UINT32_MAX) + vty_out(vty, " infinite"); + else + vty_out(vty, " %u", + rprefix->AdvValidLifetime); + if (rprefix->AdvPreferredLifetime == UINT32_MAX) + vty_out(vty, " infinite"); + else + vty_out(vty, " %u", + rprefix->AdvPreferredLifetime); + } + if (!rprefix->AdvOnLinkFlag) + vty_out(vty, " off-link"); + if (!rprefix->AdvAutonomousFlag) + vty_out(vty, " no-autoconfig"); + if (rprefix->AdvRouterAddressFlag) + 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; +} + + +static void rtadv_event(struct zebra_vrf *zvrf, enum rtadv_event event, int val) +{ + struct rtadv *rtadv; + + if (IS_ZEBRA_DEBUG_EVENT) { + struct vrf *vrf = zvrf->vrf; + + zlog_debug("%s(%s) with event: %d and val: %d", __func__, + VRF_LOGNAME(vrf), event, val); + } + + rtadv = &zvrf->rtadv; + + switch (event) { + case RTADV_START: + event_add_read(zrouter.master, rtadv_read, zvrf, rtadv->sock, + &rtadv->ra_read); + event_add_event(zrouter.master, rtadv_timer, zvrf, 0, + &rtadv->ra_timer); + break; + case RTADV_STOP: + EVENT_OFF(rtadv->ra_timer); + EVENT_OFF(rtadv->ra_read); + break; + case RTADV_TIMER: + event_add_timer(zrouter.master, rtadv_timer, zvrf, val, + &rtadv->ra_timer); + break; + case RTADV_TIMER_MSEC: + event_add_timer_msec(zrouter.master, rtadv_timer, zvrf, val, + &rtadv->ra_timer); + break; + case RTADV_READ: + event_add_read(zrouter.master, rtadv_read, zvrf, rtadv->sock, + &rtadv->ra_read); + break; + default: + break; + } + return; +} + +void rtadv_if_up(struct zebra_if *zif) +{ + struct zebra_vrf *zvrf = rtadv_interface_get_zvrf(zif->ifp); + + /* Enable fast tx of RA if enabled && RA interval is not in msecs */ + if (zif->rtadv.AdvSendAdvertisements && + (zif->rtadv.MaxRtrAdvInterval >= 1000) && + zif->rtadv.UseFastRexmit) { + zif->rtadv.inFastRexmit = 1; + zif->rtadv.NumFastReXmitsRemain = RTADV_NUM_FAST_REXMITS; + } + + /* + * startup the state machine, if it hasn't been already + * due to a delayed ifindex on startup ordering + */ + if (zif->rtadv.AdvSendAdvertisements) + rtadv_start_interface_events(zvrf, zif); +} + +void rtadv_if_init(struct zebra_if *zif) +{ + /* Set default router advertise values. */ + struct rtadvconf *rtadv; + + rtadv = &zif->rtadv; + + rtadv->AdvSendAdvertisements = 0; + rtadv->MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL; + rtadv->MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL; + rtadv->AdvIntervalTimer = 0; + rtadv->AdvManagedFlag = 0; + rtadv->AdvOtherConfigFlag = 0; + rtadv->AdvHomeAgentFlag = 0; + rtadv->AdvLinkMTU = 0; + rtadv->AdvReachableTime = 0; + rtadv->AdvRetransTimer = 0; + rtadv->AdvCurHopLimit = RTADV_DEFAULT_HOPLIMIT; + memset(&rtadv->lastadvcurhoplimit, 0, + sizeof(rtadv->lastadvcurhoplimit)); + memset(&rtadv->lastadvmanagedflag, 0, + sizeof(rtadv->lastadvmanagedflag)); + memset(&rtadv->lastadvotherconfigflag, 0, + sizeof(rtadv->lastadvotherconfigflag)); + memset(&rtadv->lastadvreachabletime, 0, + sizeof(rtadv->lastadvreachabletime)); + memset(&rtadv->lastadvretranstimer, 0, + sizeof(rtadv->lastadvretranstimer)); + rtadv->AdvDefaultLifetime = -1; /* derive from MaxRtrAdvInterval */ + rtadv->HomeAgentPreference = 0; + rtadv->HomeAgentLifetime = -1; /* derive from AdvDefaultLifetime */ + rtadv->AdvIntervalOption = 0; + rtadv->UseFastRexmit = true; + rtadv->DefaultPreference = RTADV_PREF_MEDIUM; + + rtadv_prefixes_init(rtadv->prefixes); + + rtadv->AdvRDNSSList = list_new(); + rtadv->AdvDNSSLList = list_new(); +} + +void rtadv_if_fini(struct zebra_if *zif) +{ + struct rtadvconf *rtadv; + struct rtadv_prefix *rp; + + rtadv = &zif->rtadv; + + while ((rp = rtadv_prefixes_pop(rtadv->prefixes))) + rtadv_prefix_free(rp); + + list_delete(&rtadv->AdvRDNSSList); + list_delete(&rtadv->AdvDNSSLList); +} + +void rtadv_vrf_init(struct zebra_vrf *zvrf) +{ + if (!vrf_is_backend_netns() && (zvrf_id(zvrf) != VRF_DEFAULT)) + return; + + zvrf->rtadv.sock = rtadv_make_socket(zvrf->zns->ns_id); +} + +void rtadv_vrf_terminate(struct zebra_vrf *zvrf) +{ + if (!vrf_is_backend_netns() && (zvrf_id(zvrf) != VRF_DEFAULT)) + return; + + rtadv_event(zvrf, RTADV_STOP, 0); + if (zvrf->rtadv.sock >= 0) { + close(zvrf->rtadv.sock); + zvrf->rtadv.sock = -1; + } + + adv_if_clean(zvrf); + adv_msec_if_clean(zvrf); +} + +void rtadv_cmd_init(void) +{ + interfaces_configured_for_ra_from_bgp = 0; + + hook_register(zebra_if_extra_info, nd_dump_vty); + hook_register(zebra_if_config_wr, rtadv_config_write); + + install_element(VIEW_NODE, &show_ipv6_nd_ra_if_cmd); + + install_element(INTERFACE_NODE, &ipv6_nd_ra_fast_retrans_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_ra_fast_retrans_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_ra_retrans_interval_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_ra_retrans_interval_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_ra_hop_limit_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_ra_hop_limit_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_suppress_ra_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_suppress_ra_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_msec_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_ra_interval_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_ra_lifetime_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_ra_lifetime_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_reachable_time_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_reachable_time_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_managed_config_flag_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_managed_config_flag_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_other_config_flag_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_other_config_flag_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_homeagent_config_flag_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_config_flag_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_homeagent_preference_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_preference_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_homeagent_lifetime_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_lifetime_cmd); + install_element(INTERFACE_NODE, + &ipv6_nd_adv_interval_config_option_cmd); + install_element(INTERFACE_NODE, + &no_ipv6_nd_adv_interval_config_option_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_prefix_cmd); + install_element(INTERFACE_NODE, &no_ipv6_nd_prefix_cmd); + install_element(INTERFACE_NODE, &ipv6_nd_router_preference_cmd); + 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) +{ + int ret; + + struct ipv6_mreq mreq; + + memset(&mreq, 0, sizeof(mreq)); + inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = ifp->ifindex; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq, + sizeof(mreq)); + if (ret < 0) + flog_err_sys(EC_LIB_SOCKET, + "%s(%u): Failed to join group, socket %u error %s", + ifp->name, ifp->ifindex, sock, + safe_strerror(errno)); + + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug( + "%s(%s:%u): Join All-Routers multicast group, socket %u", + ifp->name, ifp->vrf->name, ifp->ifindex, sock); + + return 0; +} + +static int if_leave_all_router(int sock, struct interface *ifp) +{ + int ret; + + struct ipv6_mreq mreq; + + memset(&mreq, 0, sizeof(mreq)); + inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = ifp->ifindex; + + ret = setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *)&mreq, + sizeof(mreq)); + if (ret < 0) + flog_err_sys( + EC_LIB_SOCKET, + "%s(%s:%u): Failed to leave group, socket %u error %s", + ifp->name, ifp->vrf->name, ifp->ifindex, sock, + safe_strerror(errno)); + + if (IS_ZEBRA_DEBUG_EVENT) + zlog_debug( + "%s(%s:%u): Leave All-Routers multicast group, socket %u", + ifp->name, ifp->vrf->name, ifp->ifindex, sock); + + return 0; +} + +bool rtadv_compiled_in(void) +{ + return true; +} + +#else /* !HAVE_RTADV */ +/* + * If the end user does not have RADV enabled we should + * handle this better + */ +void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS) +{ + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug( + "Received %s command, but ZEBRA is not compiled with Router Advertisements on", + zserv_command_string(hdr->command)); + + return; +} + +void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS) +{ + if (IS_ZEBRA_DEBUG_PACKET) + zlog_debug( + "Received %s command, but ZEBRA is not compiled with Router Advertisements on", + zserv_command_string(hdr->command)); + + return; +} + +bool rtadv_compiled_in(void) +{ + return false; +} + +#endif /* HAVE_RTADV */ + +uint32_t rtadv_get_interfaces_configured_from_bgp(void) +{ + return interfaces_configured_for_ra_from_bgp; +} |