Date: Thu, 16 Apr 2026 19:38:00 +0000 From: Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: c8d25804f56a - main - nd6: Add support for route information (RFC 4191) Message-ID: <69e13a98.19c1c.5140b765@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by pouria: URL: https://cgit.FreeBSD.org/src/commit/?id=c8d25804f56ae7c35eaa492b6110807a2675d41a commit c8d25804f56ae7c35eaa492b6110807a2675d41a Author: Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org> AuthorDate: 2026-04-16 14:23:49 +0000 Commit: Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org> CommitDate: 2026-04-16 18:57:07 +0000 nd6: Add support for route information (RFC 4191) Implement RFC 4191 by handling received Router Adverisement (RA) packets with route information option. For default routes, use the route information's lifetime and preference to overwrite the RA's lifetime/preference. Also install and update more-specific route prefixes with the option's lifetime and expire them when their lifetime elapses. PR: 263982 Reviewed by: markj Tested by: Marek Zarychta <zarychtam@plan-b.pwste.edu.pl> Relnotes: yes Differential Revision: https://reviews.freebsd.org/D55449 --- sys/net/route/nhop_ctl.c | 1 + sys/netinet/icmp6.h | 2 + sys/netinet6/nd6.c | 8 ++ sys/netinet6/nd6.h | 14 +- sys/netinet6/nd6_rtr.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 360 insertions(+), 2 deletions(-) diff --git a/sys/net/route/nhop_ctl.c b/sys/net/route/nhop_ctl.c index 52e7b0fefcd2..596ec9e25d1a 100644 --- a/sys/net/route/nhop_ctl.c +++ b/sys/net/route/nhop_ctl.c @@ -451,6 +451,7 @@ nhop_create_from_nhop(struct rib_head *rnh, const struct nhop_object *nh_orig, nhop_free(nh); return (error); } + set_nhop_expire_from_info(nh, info); *pnh = nhop_get_nhop(nh, &error); diff --git a/sys/netinet/icmp6.h b/sys/netinet/icmp6.h index 9508f221ba10..77145e4c7503 100644 --- a/sys/netinet/icmp6.h +++ b/sys/netinet/icmp6.h @@ -356,6 +356,8 @@ struct nd_opt_route_info { /* route info */ /* prefix follows */ } __packed; +#define ND_OPT_RTI_FLAG_PRF_MASK 0x18 /* 00011000 */ + struct nd_opt_rdnss { /* RDNSS option (RFC 6106) */ u_int8_t nd_opt_rdnss_type; u_int8_t nd_opt_rdnss_len; diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c index 48e80bb75e0b..c531d11f9e62 100644 --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -513,6 +513,14 @@ nd6_options(union nd_opts *ndopts) ndopts->nd_opts_pi_end = (struct nd_opt_prefix_info *)nd_opt; break; + case ND_OPT_ROUTE_INFO: + if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0) { + ndopts->nd_opt_array[nd_opt->nd_opt_type] + = nd_opt; + } + ndopts->nd_opts_rti_end = + (struct nd_opt_route_info *)nd_opt; + break; /* What about ND_OPT_ROUTE_INFO? RFC 4191 */ case ND_OPT_RDNSS: /* RFC 6106 */ case ND_OPT_DNSSL: /* RFC 6106 */ diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h index 8c8d5a3236ca..8dc27aef5e12 100644 --- a/sys/netinet6/nd6.h +++ b/sys/netinet6/nd6.h @@ -287,7 +287,7 @@ VNET_DECLARE(int, ip6_temp_regen_advance); /* seconds */ #define V_ip6_temp_regen_advance VNET(ip6_temp_regen_advance) union nd_opts { - struct nd_opt_hdr *nd_opt_array[16]; /* max = ND_OPT_NONCE */ + struct nd_opt_hdr *nd_opt_array[24]; /* max = ND_OPT_ROUTE_INFO */ struct { struct nd_opt_hdr *zero; struct nd_opt_hdr *src_lladdr; @@ -305,10 +305,20 @@ union nd_opts { struct nd_opt_hdr *__res13; struct nd_opt_nonce *nonce; struct nd_opt_hdr *__res15; + struct nd_opt_hdr *__res16; + struct nd_opt_hdr *__res17; + struct nd_opt_hdr *__res18; + struct nd_opt_hdr *__res19; + struct nd_opt_hdr *__res20; + struct nd_opt_hdr *__res21; + struct nd_opt_hdr *__res22; + struct nd_opt_hdr *__res23; + struct nd_opt_route_info *rti_beg; struct nd_opt_hdr *search; /* multiple opts */ struct nd_opt_hdr *last; /* multiple opts */ int done; struct nd_opt_prefix_info *pi_end;/* multiple opts, end */ + struct nd_opt_route_info *rti_end;/* multiple opts, end */ } nd_opt_each; }; #define nd_opts_src_lladdr nd_opt_each.src_lladdr @@ -318,6 +328,8 @@ union nd_opts { #define nd_opts_rh nd_opt_each.rh #define nd_opts_mtu nd_opt_each.mtu #define nd_opts_nonce nd_opt_each.nonce +#define nd_opts_rti nd_opt_each.rti_beg +#define nd_opts_rti_end nd_opt_each.rti_end #define nd_opts_search nd_opt_each.search #define nd_opts_last nd_opt_each.last #define nd_opts_done nd_opt_each.done diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c index 5819370a3011..623bfd60918e 100644 --- a/sys/netinet6/nd6_rtr.c +++ b/sys/netinet6/nd6_rtr.c @@ -60,6 +60,7 @@ #include <net/route.h> #include <net/route/nhop.h> #include <net/route/route_ctl.h> +#include <net/route/route_var.h> #include <net/radix.h> #include <net/vnet.h> @@ -77,9 +78,20 @@ MALLOC_DEFINE(M_IP6NDP, "ip6ndp", "IPv6 Neighbor Discovery"); +struct nd_routectl { + struct ifnet *ndrt_ifp; /* nexthop interface */ + struct sockaddr_in6 ndrt_gateway; /* gateway address */ + struct sockaddr_in6 ndrt_prefix; /* route prefix */ + struct sockaddr_in6 ndrt_mask; /* route prefix mask */ + uint8_t ndrt_plen; /* route prefix len */ + uint8_t ndrt_flags; /* route info flags */ + uint32_t ndrt_lifetime; /* route info lifetime (sec) */ +}; + static struct nd_defrouter *defrtrlist_update(struct nd_defrouter *); static int prelist_update(struct nd_prefixctl *, struct nd_defrouter *, bool, int); +static int nd6_routelist_update(struct nd_routectl *); static int nd6_prefix_onlink(struct nd_prefix *); static int in6_get_tmp_ifid(struct in6_aliasreq *); @@ -304,6 +316,116 @@ nd6_ra_opt_pi(struct nd_opt_hdr *pt, struct ifnet *ifp, (void)prelist_update(&pr, dr, auth, mcast); } +static void +nd6_ra_opt_rti(struct nd_opt_hdr *hdr, struct ifnet *ifp, + struct in6_addr saddr6, struct nd_defrouter *dr) +{ + struct nd_opt_route_info *rti = NULL; + struct nd_routectl rt; + struct in6_addr prefix, netmask; + char ip6bufs[INET6_ADDRSTRLEN]; + int olen; + uint8_t pref; + + if (hdr->nd_opt_type != ND_OPT_ROUTE_INFO) + return; + + /* RFC 4191 section 2.3, Field validation */ + rti = (struct nd_opt_route_info *)hdr; + if (rti->nd_opt_rti_len == 0 || rti->nd_opt_rti_len > 3) { + nd6log((LOG_INFO, + "%s: invalid option len %d for route " + "information option, ignored\n", __func__, + rti->nd_opt_rti_len)); + return; + } + if (rti->nd_opt_rti_prefixlen > 128 || + (rti->nd_opt_rti_prefixlen > 64 && rti->nd_opt_rti_len != 3) || + (rti->nd_opt_rti_prefixlen > 0 && rti->nd_opt_rti_len < 2)) { + nd6log((LOG_INFO, + "%s: invalid prefix len %d with option len %d for route " + "information option, ignored\n", __func__, + rti->nd_opt_rti_prefixlen, rti->nd_opt_rti_len)); + return; + } + pref = rti->nd_opt_rti_flags & ND_OPT_RTI_FLAG_PRF_MASK; + if ((pref & ND_RA_FLAG_RTPREF_RSV) != 0) { + nd6log((LOG_INFO, + "%s: reserved preference %d for route " + "information option, ignored\n", __func__, + pref)); + return; + } + + /* + * RFC 4191 section 2.3: The bits in the prefix after + * the prefix length (if any) are reserved and MUST be + * initialized to zero by the sender and ignored by the receiver. + */ + memset(&prefix, 0, sizeof(prefix)); + /* + * Calculate the variable length of the option and copy the remaining + * length into the prefix. + * The resulting value cannot exceed the size of struct in6_addr + * since the option length is limited to 3 (24 bytes). + */ + olen = (rti->nd_opt_rti_len << 3) - sizeof(struct nd_opt_route_info); + memcpy(&prefix, (char *)rti + sizeof(struct nd_opt_route_info), olen); + in6_prefixlen2mask(&netmask, rti->nd_opt_rti_prefixlen); + IN6_MASK_ADDR(&prefix, &netmask); + if (IN6_IS_ADDR_MULTICAST(&prefix) || IN6_IS_ADDR_LINKLOCAL(&prefix)) { + nd6log((LOG_INFO, "%s: invalid prefix %s, ignored\n", + __func__, ip6_sprintf(ip6bufs, &prefix))); + return; + } + + /* + * RFC 4191 section 3.1: The Router Preference and Lifetime + * values in a ::/0 Route Information Option override the + * preference and lifetime values in the Router Advertisement header. + */ + if (rti->nd_opt_rti_prefixlen == 0 && dr != NULL) { + /* + * We may disable routes from RA messages when + * ND6_IFF_NO_RADR enabled on the receiving interface or + * (ip6.forwarding == 1 && ip6.rfc6204w3 != 1). + * Therefore, don't overwrite it. + */ + if (dr->rtlifetime == 0) + return; + /* + * XXXPO: ignore rti lifetime bigger than uint16_max until + * we update the dr structure size to uint32_t. + */ + if (ntohl(rti->nd_opt_rti_lifetime) > UINT16_MAX) + return; + dr->rtlifetime = ntohl(rti->nd_opt_rti_lifetime); + dr->expire = time_uptime + dr->rtlifetime; + dr->raflags &= ~ND_OPT_RTI_FLAG_PRF_MASK; + dr->raflags |= pref; + nd6log((LOG_INFO, + "%s: override default route with route information option\n", + __func__)); + return; + } + + memset(&rt, 0, sizeof(rt)); + rt.ndrt_ifp = ifp; + rt.ndrt_prefix.sin6_len = sizeof(struct sockaddr_in6); + rt.ndrt_prefix.sin6_family = AF_INET6; + rt.ndrt_prefix.sin6_addr = prefix; + rt.ndrt_gateway.sin6_len = sizeof(struct sockaddr_in6); + rt.ndrt_gateway.sin6_family = AF_INET6; + rt.ndrt_gateway.sin6_addr = saddr6; + rt.ndrt_mask.sin6_len = sizeof(struct sockaddr_in6); + rt.ndrt_mask.sin6_family = AF_INET6; + rt.ndrt_mask.sin6_addr = netmask; + rt.ndrt_plen = rti->nd_opt_rti_prefixlen; + rt.ndrt_flags = rti->nd_opt_rti_flags; + rt.ndrt_lifetime = ntohl(rti->nd_opt_rti_lifetime); + (void)nd6_routelist_update(&rt); +} + static void nd6_ra_opt_mtu(struct nd_opt_mtu *optmtu, struct ifnet *ifp, struct in6_addr saddr6) @@ -385,7 +507,7 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len) struct in6_ifextra *ndi; struct ip6_hdr *ip6; struct nd_router_advert *nd_ra; - struct nd_opt_hdr *pt; + struct nd_opt_hdr *pt, *rti; struct in6_addr saddr6; struct nd_defrouter dr0, *dr; union nd_opts ndopts; @@ -493,6 +615,15 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len) nd_ra->nd_ra_curhoplimit); } } + /* Route Information */ + if (ndopts.nd_opts_rti != NULL) { + for (rti = (struct nd_opt_hdr *)ndopts.nd_opts_rti; + rti <= (struct nd_opt_hdr *)ndopts.nd_opts_rti_end; + rti = (struct nd_opt_hdr *)((char *)rti + + (rti->nd_opt_len << 3))) { + nd6_ra_opt_rti(rti, ifp, saddr6, &dr0); + } + } dr = defrtrlist_update(&dr0); /* Prefix Information */ if (ndopts.nd_opts_pi != NULL) { @@ -2167,6 +2298,210 @@ restart: return (error); } +/* + * Install advertised route via default router. + */ +static int +nd6_route_rtrequest(struct nd_routectl *key) +{ + struct rib_cmd_info rc; + struct rib_head *rnh; + struct nhop_object *nh; + struct ifaddr *ifa; + struct ifnet *ifp; + struct sockaddr *dst, *gw, *mask; + int flags, error; + time_t expire; + + NET_EPOCH_ASSERT(); + + ifp = key->ndrt_ifp; + dst = (struct sockaddr *)&key->ndrt_prefix; + gw = (struct sockaddr *)&key->ndrt_gateway; + mask = (struct sockaddr *)&key->ndrt_mask; + flags = RTF_DYNAMIC | RTF_GATEWAY; + if (key->ndrt_plen == 128) { + flags |= RTF_HOST; + mask = NULL; + } + + rnh = rt_tables_get_rnh_safe(key->ndrt_ifp->if_fib, AF_INET6); + if (rnh == NULL) + return (EINVAL); + + /* Get the best ifa for the given interface and gateway. */ + if ((ifa = ifaof_ifpforaddr(gw, ifp)) == NULL) + return (ENETUNREACH); + + expire = time_second + key->ndrt_lifetime; + struct rt_metrics rmx = { + .rmx_expire = expire, + }; + struct rt_addrinfo info = { + .rti_flags = flags, + .rti_info = { + [RTAX_DST] = dst, + [RTAX_GATEWAY] = gw, + [RTAX_NETMASK] = mask, + [RTAX_AUTHOR] = gw, + }, + .rti_ifa = ifa, + .rti_ifp = ifp, + .rti_mflags = RTV_EXPIRE, + .rti_rmx = &rmx, + }; + /* XXX: route preference */ + struct route_nhop_data rnd = { + .rnd_weight = RT_DEFAULT_WEIGHT, + }; + + error = nhop_create_from_info(rnh, &info, &nh); + if (error == 0) { + nhop_set_origin(nh, NH_ORIGIN_REDIRECT); + rnd.rnd_nhop = nh; + error = rib_add_route_px(ifp->if_fib, dst, key->ndrt_plen, &rnd, + RTM_F_CREATE, &rc); + } + if (error != 0) + return (error); + RTSTAT_INC(rts_dynamic); + + /* Send notification of a route addition to userland. */ + rt_missmsg_fib(RTM_REDIRECT, &info, flags | RTF_UP, error, ifp->if_fib); + + return (error); +} + +/* + * Update lifetime of advertised route. + */ +static int +nd6_route_rtupdate(struct nd_routectl *key) +{ + struct rib_cmd_info rc; + time_t expire; + + expire = time_second + key->ndrt_lifetime; + struct rt_metrics rmx = { + .rmx_expire = expire, + }; + struct rt_addrinfo info = { + .rti_info[RTAX_DST] = (struct sockaddr *)&key->ndrt_prefix, + .rti_mflags = RTV_EXPIRE, + .rti_rmx = &rmx, + }; + if (key->ndrt_plen != 128) + info.rti_info[RTAX_NETMASK] = (struct sockaddr *)&key->ndrt_mask; + return (rib_change_route(key->ndrt_ifp->if_fib, &info, &rc)); +} + +/* + * Delete advertised route. + */ +static int +nd6_route_rtdelete(struct nd_routectl *key) +{ + struct sockaddr *dst, *gw; + struct rib_cmd_info rc; + struct nhop_object *nh; + struct ifnet *ifp; + int error; + + ifp = key->ndrt_ifp; + dst = (struct sockaddr *)&key->ndrt_prefix; + gw = (struct sockaddr *)&key->ndrt_gateway; + error = rib_del_route_px(ifp->if_fib, dst, key->ndrt_plen, + rib_match_gw, gw, 0, &rc); + if (error == 0) { + nh = nhop_select_func(rc.rc_nh_old, 0); + rt_routemsg(RTM_DELETE, rc.rc_rt, nh, ifp->if_fib); + } + + return (error); +} + +/* + * Lookup for exact match of advertised route with gateway + * as its nexthop address. + */ +static bool +nd6_route_rtlookup(struct nd_routectl *key) +{ + RIB_RLOCK_TRACKER; + struct rib_head *rnh; + struct route_nhop_data rnd; + struct sockaddr *dst, *gw; + + dst = (struct sockaddr *)&key->ndrt_prefix; + gw = (struct sockaddr *)&key->ndrt_gateway; + rnh = rt_tables_get_rnh_safe(key->ndrt_ifp->if_fib, AF_INET6); + if (rnh == NULL) + return (false); + + RIB_RLOCK(rnh); + rib_lookup_prefix_plen(rnh, dst, key->ndrt_plen, &rnd); + RIB_RUNLOCK(rnh); + if (rnd.rnd_nhop == NULL) + return (false); + + return (match_nhop_gw(rnd.rnd_nhop, gw)); +} + +static int +nd6_routelist_update(struct nd_routectl *new) +{ + char ip6buf[INET6_ADDRSTRLEN]; + int error = 0; + + NET_EPOCH_ASSERT(); + + /* + * RFC 4191 section 3.1: If the received route's + * lifetime is zero, the route is removed from the Routing + * Table if present. + */ + if (new->ndrt_lifetime == 0) { + error = nd6_route_rtdelete(new); + if (error != 0) { + nd6log((LOG_DEBUG, + "%s: failed to delete the route %s/%d on %s (errno=%d)\n", + __func__, ip6_sprintf(ip6buf, &new->ndrt_prefix.sin6_addr), + new->ndrt_plen, if_name(new->ndrt_ifp), error)); + } + return (error); + } + + if (nd6_route_rtlookup(new)) { + /* + * RFC 4191 section 3.1: the route's lifetime and + * preference is updated if the route is already present. + * XXX: preference on routing table? (ndrt_flags) + */ + error = nd6_route_rtupdate(new); + if (error != 0) { + nd6log((LOG_DEBUG, + "%s: failed to update route lifetime of " + "%s/%d from RA on %s (errno=%d)\n", + __func__, ip6_sprintf(ip6buf, &new->ndrt_prefix.sin6_addr), + new->ndrt_plen, if_name(new->ndrt_ifp), error)); + } + } else { + /* + * RFC 4191 section 3.1: If a route's lifetime is non-zero, + * the route is added to the Routing Table if not present. + */ + error = nd6_route_rtrequest(new); + if (error != 0) { + nd6log((LOG_NOTICE, + "%s: failed to add route %s/%d from RA on %s (errno=%d)\n", + __func__, ip6_sprintf(ip6buf, &new->ndrt_prefix.sin6_addr), + new->ndrt_plen, if_name(new->ndrt_ifp), error)); + } + } + + return (error); +} + /* * Get a randomized interface identifier for a temporary address * Based on RFC 8981, Section 3.3.1.home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69e13a98.19c1c.5140b765>
