commit e19a5643040ded2ba1997eb02648d9bb06e181aa Author: Andrey V. Elsukov Date: Mon Sep 22 16:41:46 2025 +0300 Fix CARP interaction with IPv6 prefix lists. - do not add IPv6 loopback route when CARP vhid is not zero. We do not need loopback route, when CARP address is not in MASTER state. - add new flag to ND6 prefix `ndpr_raf_carp' that is set when a prefix is linked with address related to CARP. - add new flag to ND6 prefix state `NDPRF_CARP_MASTER', that is set when ifaddr that is related with prefix has CARP MASTER state. - in nd6_prelist_add() do not set prefix online if it has ndpr_raf_carp flag set. We don't need add prefix route while address is not in MASTER state. - remove NET_EPOCH_ENTER from nd6_prelist_add() since nd6_prefix_onlink() now does not require it. - make nd6_prefix_onlink() global to be able call it from CARP. - rework CARP add/delroute to properly handle IPv6 prefixes. Now we set and remove NDPRF_CARP_MASTER flag when needed. And also call nd6_prefix_onlink/offlink() to add/remove IPv6 prefix routes. - if CARP has INET6 addresses, call pfxlist_onlink_check() after add/delroute. This should handle automatically make prefixes online if such is needed. diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c index 00976bacfdac..e441324e3e78 100644 --- a/sys/netinet/ip_carp.c +++ b/sys/netinet/ip_carp.c @@ -1552,11 +1552,21 @@ carp_ifa_addroute(struct ifaddr *ifa) break; #endif #ifdef INET6 - case AF_INET6: + case AF_INET6: { + struct in6_ifaddr *ia6 = ifatoia6(ifa); + struct nd_prefix *pr = ia6->ia6_ndpr; + ifa_add_loopback_route(ifa, - (struct sockaddr *)&ifatoia6(ifa)->ia_addr); - nd6_add_ifa_lle(ifatoia6(ifa)); + (struct sockaddr *)&ia6->ia_addr); + nd6_add_ifa_lle(ia6); + if (pr != NULL) { + ND6_ONLINK_LOCK(); + pr->ndpr_stateflags |= NDPRF_CARP_MASTER; + nd6_prefix_onlink(pr); + ND6_ONLINK_UNLOCK(); + } break; + } #endif } } @@ -1565,9 +1575,21 @@ static void carp_delroute(struct carp_softc *sc) { struct ifaddr *ifa; +#ifdef INET6 + bool pfx_check = false; +#endif - CARP_FOREACH_IFA(sc, ifa) + CARP_FOREACH_IFA(sc, ifa) { +#ifdef INET6 + if (ifa->ifa_addr->sa_family == AF_INET6) + pfx_check = true; +#endif carp_ifa_delroute(ifa); + } +#ifdef INET6 + if (pfx_check) + pfxlist_onlink_check(); +#endif } static void @@ -1583,11 +1605,23 @@ carp_ifa_delroute(struct ifaddr *ifa) break; #endif #ifdef INET6 - case AF_INET6: + case AF_INET6: { + struct in6_ifaddr *ia6 = ifatoia6(ifa); + struct nd_prefix *pr = ia6->ia6_ndpr; + ifa_del_loopback_route(ifa, - (struct sockaddr *)&ifatoia6(ifa)->ia_addr); - nd6_rem_ifa_lle(ifatoia6(ifa), 1); + (struct sockaddr *)&ia6->ia_addr); + nd6_rem_ifa_lle(ia6, 1); + + /* XXXAE: initialize ia6_ndpr prior to carp_attach */ + if (pr != NULL) { + ND6_ONLINK_LOCK(); + pr->ndpr_stateflags &= ~NDPRF_CARP_MASTER; + nd6_prefix_offlink(pr); + ND6_ONLINK_UNLOCK(); + } break; + } #endif } } diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c index dba1232fd58d..8937a8b1f578 100644 --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -1272,6 +1272,12 @@ in6_addifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *i * interface route, so we configure the address first. */ + /* + * XXXAE: for CARP addresses we make prefix on-link only when + * the address gets the MASTER role. Until then we don't install + * prefix and loopback routes. + */ + /* * convert mask to prefix length (prefixmask has already * been validated in in6_update_ifa(). @@ -1299,6 +1305,7 @@ in6_addifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *i pr0.ndpr_raf_onlink = 1; /* should be configurable? */ pr0.ndpr_raf_auto = ((ifra->ifra_flags & IN6_IFF_AUTOCONF) != 0); + pr0.ndpr_raf_carp = (ifra->ifra_vhid > 0); pr0.ndpr_vltime = ifra->ifra_lifetime.ia6t_vltime; pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime; @@ -1306,7 +1313,7 @@ in6_addifaddr(struct ifnet *ifp, struct in6_aliasreq *ifra, struct in6_ifaddr *i if ((pr = nd6_prefix_lookup(&pr0)) == NULL) { /* * nd6_prelist_add will install the corresponding - * interface route. + * interface route only if it is not CARP address. */ if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) { if (carp_attached) @@ -1446,7 +1453,6 @@ in6_purgeifaddr(struct in6_ifaddr *ia) } } - static void in6_unlink_ifa(struct in6_ifaddr *ia, struct ifnet *ifp) { @@ -1571,7 +1577,8 @@ in6_notify_ifa(struct ifnet *ifp, struct in6_ifaddr *ia, /* * add a loopback route to self if not exists */ - if (!(ia->ia_flags & IFA_RTSELF) && V_nd6_useloopback) { + if (!(ia->ia_flags & IFA_RTSELF) && + ifra->ifra_vhid == 0 && V_nd6_useloopback) { error = ifa_add_loopback_route((struct ifaddr *)ia, (struct sockaddr *)&ia->ia_addr); if (error == 0) diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h index 4c9faa7f3210..2473afcbf427 100644 --- a/sys/netinet6/in6_var.h +++ b/sys/netinet6/in6_var.h @@ -323,7 +323,8 @@ struct in6_prflags { struct prf_ra { u_char onlink : 1; u_char autonomous : 1; - u_char reserved : 6; + u_char carp : 1; + u_char reserved : 5; } prf_ra; u_char prf_reserved1; u_short prf_reserved2; @@ -354,8 +355,7 @@ struct in6_prefixreq { #define ipr_raf_onlink ipr_flags.prf_ra.onlink #define ipr_raf_auto ipr_flags.prf_ra.autonomous - -#define ipr_statef_onlink ipr_flags.prf_state.onlink +#define ipr_raf_carp ipr_flags.prf_ra.carp #define ipr_rrf_decrvalid ipr_flags.prf_rr.decrvalid #define ipr_rrf_decrprefd ipr_flags.prf_rr.decrprefd @@ -387,8 +387,6 @@ struct in6_rrenumreq { #define irr_raf_onlink irr_flags.prf_ra.onlink #define irr_raf_auto irr_flags.prf_ra.autonomous -#define irr_statef_onlink irr_flags.prf_state.onlink - #define irr_rrf irr_flags.prf_rr #define irr_rrf_decrvalid irr_flags.prf_rr.decrvalid #define irr_rrf_decrprefd irr_flags.prf_rr.decrprefd diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h index eefefb20dd6c..2f25fe17725f 100644 --- a/sys/netinet6/nd6.h +++ b/sys/netinet6/nd6.h @@ -169,6 +169,7 @@ struct in6_ndifreq { /* Prefix status */ #define NDPRF_ONLINK 0x1 #define NDPRF_DETACHED 0x2 +#define NDPRF_CARP_MASTER 0x4 /* protocol constants */ #define MAX_RTR_SOLICITATION_DELAY 1 /* 1sec */ @@ -242,7 +243,7 @@ struct nd_prefix { #define ndpr_raf ndpr_flags #define ndpr_raf_onlink ndpr_flags.onlink #define ndpr_raf_auto ndpr_flags.autonomous -#define ndpr_raf_router ndpr_flags.router +#define ndpr_raf_carp ndpr_flags.carp struct nd_pfxrouter { LIST_ENTRY(nd_pfxrouter) pfr_entry; @@ -409,6 +410,7 @@ void nd6_prefix_unlink(struct nd_prefix *, struct nd_prhead *); void nd6_prefix_del(struct nd_prefix *); void nd6_prefix_ref(struct nd_prefix *); void nd6_prefix_rele(struct nd_prefix *); +int nd6_prefix_onlink(struct nd_prefix *); int nd6_prefix_offlink(struct nd_prefix *); void pfxlist_onlink_check(void); struct nd_prefix *nd6_prefix_lookup(struct nd_prefixctl *); diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c index 9a4f95f1bef8..f019f26c6389 100644 --- a/sys/netinet6/nd6_rtr.c +++ b/sys/netinet6/nd6_rtr.c @@ -76,7 +76,6 @@ static struct nd_defrouter *defrtrlist_update(struct nd_defrouter *); static int prelist_update(struct nd_prefixctl *, struct nd_defrouter *, struct mbuf *, int); -static int nd6_prefix_onlink(struct nd_prefix *); TAILQ_HEAD(nd6_drhead, nd_defrouter); VNET_DEFINE_STATIC(struct nd6_drhead, nd6_defrouter); @@ -1376,11 +1375,8 @@ nd6_prelist_add(struct nd_prefixctl *pr, struct nd_defrouter *dr, ND6_WUNLOCK(); /* ND_OPT_PI_FLAG_ONLINK processing */ - if (new->ndpr_raf_onlink) { - struct epoch_tracker et; - + if (new->ndpr_raf_onlink && new->ndpr_raf_carp == 0) { ND6_ONLINK_LOCK(); - NET_EPOCH_ENTER(et); if ((error = nd6_prefix_onlink(new)) != 0) { nd6log((LOG_ERR, "%s: failed to make the prefix %s/%d " "on-link on %s (errno=%d)\n", __func__, @@ -1388,7 +1384,6 @@ nd6_prelist_add(struct nd_prefixctl *pr, struct nd_defrouter *dr, pr->ndpr_plen, if_name(pr->ndpr_ifp), error)); /* proceed anyway. XXX: is it correct? */ } - NET_EPOCH_EXIT(et); ND6_ONLINK_UNLOCK(); } @@ -1798,6 +1793,9 @@ find_pfxlist_reachable_router(struct nd_prefix *pr) struct nd_pfxrouter *pfxrtr; ND6_LOCK_ASSERT(); + /* skip prefix that is related to CARP BACKUP address */ + if (pr->ndpr_raf_carp && (pr->ndpr_stateflags & NDPRF_CARP_MASTER) == 0) + return (NULL); NET_EPOCH_ENTER(et); LIST_FOREACH(pfxrtr, &pr->ndpr_advrtrs, pfr_entry) { @@ -1829,6 +1827,7 @@ pfxlist_onlink_check(void) struct nd_defrouter *dr; struct nd_pfxrouter *pfxrtr = NULL; struct rm_priotracker in6_ifa_tracker; + char ip6buf[INET6_ADDRSTRLEN]; uint64_t genid; uint32_t flags; @@ -1883,6 +1882,13 @@ pfxlist_onlink_check(void) else if ((pr->ndpr_stateflags & NDPRF_DETACHED) != 0 && find_pfxlist_reachable_router(pr) != NULL) pr->ndpr_stateflags &= ~NDPRF_DETACHED; + + nd6log((LOG_DEBUG, + "%s: %s: %s/%d flags 0x%02x, state 0x%04x\n", + __func__, if_name(pr->ndpr_ifp), + ip6_sprintf(ip6buf, &pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, *(u_char *)&pr->ndpr_flags, + pr->ndpr_stateflags)); } } else { /* there is no prefix that has a reachable router */ @@ -1905,7 +1911,6 @@ pfxlist_onlink_check(void) */ restart: LIST_FOREACH(pr, &V_nd_prefix, ndpr_entry) { - char ip6buf[INET6_ADDRSTRLEN]; int e; if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr) || @@ -2059,7 +2064,7 @@ nd6_prefix_onlink_rtrequest(struct nd_prefix *pr, struct ifaddr *ifa) return (error); } -static int +int nd6_prefix_onlink(struct nd_prefix *pr) { struct epoch_tracker et; @@ -2072,6 +2077,13 @@ nd6_prefix_onlink(struct nd_prefix *pr) ND6_ONLINK_LOCK_ASSERT(); ND6_UNLOCK_ASSERT(); + /* + * Skip prefixes that are linked with CARP BACKUP addresses. + */ + if (pr->ndpr_raf_carp && + (pr->ndpr_stateflags & NDPRF_CARP_MASTER) == 0) + return (ENETDOWN); + if ((pr->ndpr_stateflags & NDPRF_ONLINK) != 0) return (EEXIST); @@ -2190,6 +2202,14 @@ nd6_prefix_offlink(struct nd_prefix *pr) (NDPRF_ONLINK | NDPRF_DETACHED)) != 0) continue; + /* + * Skip prefixes that are linked with CARP BACKUP + * addresses. + */ + if (opr->ndpr_raf_carp != 0 && + (opr->ndpr_stateflags & NDPRF_CARP_MASTER) == 0) + continue; + if (opr->ndpr_plen == pr->ndpr_plen && in6_are_prefix_equal(&pr->ndpr_prefix.sin6_addr, &opr->ndpr_prefix.sin6_addr, pr->ndpr_plen)) {