ndp: Simplify and breakdown nd6_ra_input()

`nd6_ra_input()` is simplied to make it easier to add
additional options.

Reviewed by: glebius
Differential Revision: https://reviews.freebsd.org/D55267
This commit is contained in:
Pouria Mousavizadeh Tehrani
2026-03-05 21:31:30 +03:30
parent 7f3b46fe54
commit 0616521adc
2 changed files with 158 additions and 150 deletions
+3 -3
View File
@@ -435,11 +435,11 @@ nd6_option(union nd_opts *ndopts)
}
olen = nd_opt->nd_opt_len << 3;
if (olen == 0) {
/*
* Message validation requires that all included
* options have a length that is greater than zero.
* RFC 4861 section 6.1.2: All included options
* must have a length that is greater than zero.
*/
if (olen == 0) {
bzero(ndopts, sizeof(*ndopts));
return NULL;
}
+151 -143
View File
@@ -79,7 +79,7 @@ MALLOC_DEFINE(M_IP6NDP, "ip6ndp", "IPv6 Neighbor Discovery");
static struct nd_defrouter *defrtrlist_update(struct nd_defrouter *);
static int prelist_update(struct nd_prefixctl *, struct nd_defrouter *,
struct mbuf *, int);
bool, int);
static int nd6_prefix_onlink(struct nd_prefix *);
static int in6_get_tmp_ifid(struct in6_aliasreq *);
@@ -355,6 +355,127 @@ nd6_ifnet_link_event(void *arg __unused, struct ifnet *ifp, int linkstate)
#endif
}
static void
nd6_ra_opt_pi(struct nd_opt_hdr *pt, struct ifnet *ifp,
struct nd_router_advert *nd_ra, struct nd_defrouter *dr,
bool auth, bool mcast)
{
struct nd_opt_prefix_info *pi = NULL;
struct nd_prefixctl pr;
char ip6bufs[INET6_ADDRSTRLEN];
if (pt->nd_opt_type != ND_OPT_PREFIX_INFORMATION)
return;
pi = (struct nd_opt_prefix_info *)pt;
if (pi->nd_opt_pi_len != 4) {
nd6log((LOG_INFO,
"%s: invalid option len %d for prefix "
"information option, ignored\n", __func__,
pi->nd_opt_pi_len));
return;
}
if (pi->nd_opt_pi_prefix_len > 128) {
nd6log((LOG_INFO,
"%s: invalid prefix len %d for prefix "
"information option, ignored\n", __func__,
pi->nd_opt_pi_prefix_len));
return;
}
if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix)
|| IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) {
nd6log((LOG_INFO,
"%s: invalid prefix %s, ignored\n",
__func__, ip6_sprintf(ip6bufs,
&pi->nd_opt_pi_prefix)));
return;
}
bzero(&pr, sizeof(pr));
pr.ndpr_prefix.sin6_family = AF_INET6;
pr.ndpr_prefix.sin6_len = sizeof(pr.ndpr_prefix);
pr.ndpr_prefix.sin6_addr = pi->nd_opt_pi_prefix;
pr.ndpr_ifp = ifp;
pr.ndpr_raf_onlink = (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK) ? 1 : 0;
pr.ndpr_raf_auto = (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_AUTO) ? 1 : 0;
pr.ndpr_plen = pi->nd_opt_pi_prefix_len;
pr.ndpr_vltime = ntohl(pi->nd_opt_pi_valid_time);
pr.ndpr_pltime = ntohl(pi->nd_opt_pi_preferred_time);
(void)prelist_update(&pr, dr, auth, mcast);
}
static void
nd6_ra_opt_mtu(struct nd_opt_mtu *optmtu, struct ifnet *ifp,
struct in6_addr saddr6)
{
struct in6_ifextra *ndi;
char ip6bufs[INET6_ADDRSTRLEN];
uint32_t mtu, maxmtu;
ndi = ifp->if_inet6;
if (optmtu->nd_opt_mtu_len != 1)
return;
mtu = (uint32_t)ntohl(optmtu->nd_opt_mtu_mtu);
/* lower bound */
if (mtu < IPV6_MMTU) {
nd6log((LOG_INFO, "%s: bogus mtu option mtu=%u sent from %s, "
"ignoring\n", __func__, mtu, ip6_sprintf(ip6bufs, &saddr6)));
return;
}
/* upper bound */
maxmtu = (ndi->nd_maxmtu && ndi->nd_maxmtu < ifp->if_mtu)
? ndi->nd_maxmtu : ifp->if_mtu;
if (mtu <= maxmtu) {
if (ndi->nd_linkmtu != mtu) {
ndi->nd_linkmtu = mtu;
rt_updatemtu(ifp);
}
} else {
nd6log((LOG_INFO, "%s: bogus mtu=%u sent from %s; "
"exceeds maxmtu %u, ignoring\n", __func__,
mtu, ip6_sprintf(ip6bufs, &saddr6), maxmtu));
}
}
static int
nd6_ra_opt_src_lladdr(struct nd_opt_hdr *opthdr, struct ifnet *ifp,
struct in6_addr saddr6)
{
char ip6bufs[INET6_ADDRSTRLEN];
char *lladdr = NULL;
int lladdrlen = 0;
if (opthdr != NULL) {
lladdr = (char *)(opthdr + 1);
lladdrlen = opthdr->nd_opt_len << 3;
}
if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
nd6log((LOG_INFO,
"%s: lladdrlen mismatch for %s (if %d, RA packet %d)\n",
__func__, ip6_sprintf(ip6bufs, &saddr6),
ifp->if_addrlen, lladdrlen - 2));
return (-1);
}
nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_ROUTER_ADVERT, 0);
/*
* Installing a link-layer address might change the state of the
* router's neighbor cache, which might also affect our on-link
* detection of adveritsed prefixes.
*/
pfxlist_onlink_check();
return (0);
}
/*
* Receive Router Advertisement Message.
*
@@ -369,11 +490,13 @@ 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 in6_addr saddr6;
struct nd_defrouter *dr;
struct nd_defrouter dr0, *dr;
union nd_opts ndopts;
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
int mcast;
uint32_t advreachable;
bool mcast, auth;
/*
* We only accept RAs only when the per-interface flag
@@ -389,6 +512,7 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
goto freeit;
ip6 = mtod(m, struct ip6_hdr *);
/* RFC 4861 section 6.1.2: hlim must be 255 */
if (__predict_false(ip6->ip6_hlim != 255)) {
ICMP6STAT_INC(icp6s_invlhlim);
nd6log((LOG_ERR,
@@ -399,6 +523,7 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
}
saddr6 = ip6->ip6_src;
/* RFC 4861 section 6.1.2: source address must be link-local */
if (!IN6_IS_ADDR_LINKLOCAL(&saddr6)) {
nd6log((LOG_ERR,
"%s: src %s is not link-local\n", __func__,
@@ -413,7 +538,6 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
return;
}
}
ip6 = mtod(m, struct ip6_hdr *);
nd_ra = (struct nd_router_advert *)((caddr_t)ip6 + off);
icmp6len -= sizeof(*nd_ra);
@@ -425,15 +549,11 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
goto freeit;
}
mcast = 0;
dr = NULL;
{
struct nd_defrouter dr0;
u_int32_t advreachable = nd_ra->nd_ra_reachable;
mcast = false;
/* remember if this is a multicasted advertisement */
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst))
mcast = 1;
mcast = true;
bzero(&dr0, sizeof(dr0));
dr0.rtaddr = saddr6;
@@ -443,17 +563,20 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
* ND6_IFF_NO_RADR enabled on the receiving interface or
* (ip6.forwarding == 1 && ip6.rfc6204w3 != 1).
*/
if (ndi->nd_flags & ND6_IFF_NO_RADR)
dr0.rtlifetime = 0;
else if (V_ip6_forwarding && !V_ip6_rfc6204w3)
if ((ndi->nd_flags & ND6_IFF_NO_RADR) ||
(V_ip6_forwarding && !V_ip6_rfc6204w3))
dr0.rtlifetime = 0;
else
dr0.rtlifetime = ntohs(nd_ra->nd_ra_router_lifetime);
dr0.expire = time_uptime + dr0.rtlifetime;
dr0.ifp = ifp;
/* unspecified or not? (RFC 2461 6.3.4) */
if (advreachable) {
advreachable = ntohl(advreachable);
/*
* RFC 4861 6.3.4: RA fields such as Cur Hop Limit,
* Reachable Time, and Retrans Timer may be unspecified.
* In such cases, the parameter should be ignored.
*/
if (nd_ra->nd_ra_reachable) {
advreachable = ntohl(nd_ra->nd_ra_reachable);
if (advreachable <= MAX_REACHABLE_TIME &&
ndi->nd_basereachable != advreachable) {
ndi->nd_basereachable = advreachable;
@@ -479,63 +602,18 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
#ifdef EXPERIMENTAL
defrtr_ipv6_only_ifp(ifp);
#endif
}
/* Prefix Information */
if (ndopts.nd_opts_pi != NULL) {
/*
* prefix
* Authenticity for NA consists authentication for
* both IP header and IP datagrams, doesn't it ?
*/
if (ndopts.nd_opts_pi) {
struct nd_opt_hdr *pt;
struct nd_opt_prefix_info *pi = NULL;
struct nd_prefixctl pr;
auth = ((m->m_flags & M_AUTHIPHDR) && (m->m_flags & M_AUTHIPDGM));
for (pt = (struct nd_opt_hdr *)ndopts.nd_opts_pi;
pt <= (struct nd_opt_hdr *)ndopts.nd_opts_pi_end;
pt = (struct nd_opt_hdr *)((caddr_t)pt +
(pt->nd_opt_len << 3))) {
if (pt->nd_opt_type != ND_OPT_PREFIX_INFORMATION)
continue;
pi = (struct nd_opt_prefix_info *)pt;
if (pi->nd_opt_pi_len != 4) {
nd6log((LOG_INFO,
"%s: invalid option len %d for prefix "
"information option, ignored\n", __func__,
pi->nd_opt_pi_len));
continue;
}
if (128 < pi->nd_opt_pi_prefix_len) {
nd6log((LOG_INFO,
"%s: invalid prefix len %d for prefix "
"information option, ignored\n", __func__,
pi->nd_opt_pi_prefix_len));
continue;
}
if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix)
|| IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) {
nd6log((LOG_INFO,
"%s: invalid prefix %s, ignored\n",
__func__, ip6_sprintf(ip6bufs,
&pi->nd_opt_pi_prefix)));
continue;
}
bzero(&pr, sizeof(pr));
pr.ndpr_prefix.sin6_family = AF_INET6;
pr.ndpr_prefix.sin6_len = sizeof(pr.ndpr_prefix);
pr.ndpr_prefix.sin6_addr = pi->nd_opt_pi_prefix;
pr.ndpr_ifp = (struct ifnet *)m->m_pkthdr.rcvif;
pr.ndpr_raf_onlink = (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK) ? 1 : 0;
pr.ndpr_raf_auto = (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_AUTO) ? 1 : 0;
pr.ndpr_plen = pi->nd_opt_pi_prefix_len;
pr.ndpr_vltime = ntohl(pi->nd_opt_pi_valid_time);
pr.ndpr_pltime = ntohl(pi->nd_opt_pi_preferred_time);
(void)prelist_update(&pr, dr, m, mcast);
nd6_ra_opt_pi(pt, ifp, nd_ra, dr, auth, mcast);
}
}
if (dr != NULL) {
@@ -543,70 +621,13 @@ nd6_ra_input(struct mbuf *m, int off, int icmp6len)
dr = NULL;
}
/*
* MTU
*/
if (ndopts.nd_opts_mtu && ndopts.nd_opts_mtu->nd_opt_mtu_len == 1) {
u_long mtu;
u_long maxmtu;
/* MTU */
if (ndopts.nd_opts_mtu != NULL)
nd6_ra_opt_mtu(ndopts.nd_opts_mtu, ifp, saddr6);
mtu = (u_long)ntohl(ndopts.nd_opts_mtu->nd_opt_mtu_mtu);
/* lower bound */
if (mtu < IPV6_MMTU) {
nd6log((LOG_INFO, "%s: bogus mtu option mtu=%lu sent "
"from %s, ignoring\n", __func__,
mtu, ip6_sprintf(ip6bufs, &ip6->ip6_src)));
goto skip;
}
/* upper bound */
maxmtu = (ndi->nd_maxmtu && ndi->nd_maxmtu < ifp->if_mtu)
? ndi->nd_maxmtu : ifp->if_mtu;
if (mtu <= maxmtu) {
if (ndi->nd_linkmtu != mtu) {
ndi->nd_linkmtu = mtu;
rt_updatemtu(ifp);
}
} else {
nd6log((LOG_INFO, "%s: bogus mtu=%lu sent from %s; "
"exceeds maxmtu %lu, ignoring\n", __func__,
mtu, ip6_sprintf(ip6bufs, &ip6->ip6_src), maxmtu));
}
}
skip:
/*
* Source link layer address
*/
{
char *lladdr = NULL;
int lladdrlen = 0;
if (ndopts.nd_opts_src_lladdr) {
lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1);
lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
}
if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) {
nd6log((LOG_INFO,
"%s: lladdrlen mismatch for %s (if %d, RA packet %d)\n",
__func__, ip6_sprintf(ip6bufs, &saddr6),
ifp->if_addrlen, lladdrlen - 2));
/* Source link layer address */
if (nd6_ra_opt_src_lladdr(ndopts.nd_opts_src_lladdr, ifp, saddr6) != 0)
goto bad;
}
nd6_cache_lladdr(ifp, &saddr6, lladdr,
lladdrlen, ND_ROUTER_ADVERT, 0);
/*
* Installing a link-layer address might change the state of the
* router's neighbor cache, which might also affect our on-link
* detection of adveritsed prefixes.
*/
pfxlist_onlink_check();
}
freeit:
m_freem(m);
@@ -1478,32 +1499,19 @@ nd6_prefix_del(struct nd_prefix *pr)
static int
prelist_update(struct nd_prefixctl *new, struct nd_defrouter *dr,
struct mbuf *m, int mcast)
bool auth, int mcast)
{
struct in6_ifaddr *ia6 = NULL, *ia6_match = NULL;
struct ifaddr *ifa;
struct ifnet *ifp = new->ndpr_ifp;
struct nd_prefix *pr;
int error = 0;
int auth;
struct in6_addrlifetime lt6_tmp;
char ip6buf[INET6_ADDRSTRLEN];
bool has_temporary = false;
NET_EPOCH_ASSERT();
auth = 0;
if (m) {
/*
* Authenticity for NA consists authentication for
* both IP header and IP datagrams, doesn't it ?
*/
#if defined(M_AUTHIPHDR) && defined(M_AUTHIPDGM)
auth = ((m->m_flags & M_AUTHIPHDR) &&
(m->m_flags & M_AUTHIPDGM));
#endif
}
if ((pr = nd6_prefix_lookup(new)) != NULL) {
/*
* nd6_prefix_lookup() ensures that pr and new have the same