From 7186e9186c3dfbe1a85bb5bfea568b87dbe7209f Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 21 Apr 2024 17:32:10 +0000 Subject: [PATCH] Implement rule 5.5 of RFC 6724 (Default Address Selection for IPv6) Rule 5.5: Prefer addresses in a prefix advertised by the next-hop. For this we have to track the (link-local) address of the advertising router per interface address and compare it with the selected route. Rule 5.5 is useful in multi-homing setups where we have more than one prefix and default router. We have to use the source address with the correct default gateway otherwise traffic is likely going to be dropped because of BCP 38. While here refactor in6_update_ifa() a bit to make the code clearer and consistently use (var & flag) instead of (var & flag) != 0. Patiently reviewed by & OK bluhm. --- sys/netinet6/icmp6.c | 4 +-- sys/netinet6/in6.c | 78 ++++++++++++++++++++++++++---------------- sys/netinet6/in6.h | 6 ++-- sys/netinet6/in6_src.c | 10 +++--- sys/netinet6/in6_var.h | 3 +- 5 files changed, 61 insertions(+), 40 deletions(-) diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c index 2ec50eb06a2..058800bc5cf 100644 --- a/sys/netinet6/icmp6.c +++ b/sys/netinet6/icmp6.c @@ -1,4 +1,4 @@ -/* $OpenBSD: icmp6.c,v 1.251 2023/12/03 20:36:24 bluhm Exp $ */ +/* $OpenBSD: icmp6.c,v 1.252 2024/04/21 17:32:10 florian Exp $ */ /* $KAME: icmp6.c,v 1.217 2001/06/20 15:03:29 jinmei Exp $ */ /* @@ -1164,7 +1164,7 @@ icmp6_reflect(struct mbuf **mp, size_t off, struct sockaddr *sa) rtfree(rt); goto bad; } - ia6 = in6_ifawithscope(rt->rt_ifa->ifa_ifp, &t, rtableid); + ia6 = in6_ifawithscope(rt->rt_ifa->ifa_ifp, &t, rtableid, rt); if (ia6 != NULL) src = &ia6->ia_addr.sin6_addr; if (src == NULL) diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c index 50bceb006cb..e1a7a62092a 100644 --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -1,4 +1,4 @@ -/* $OpenBSD: in6.c,v 1.264 2024/04/17 08:36:30 florian Exp $ */ +/* $OpenBSD: in6.c,v 1.265 2024/04/21 17:32:10 florian Exp $ */ /* $KAME: in6.c,v 1.372 2004/06/14 08:14:21 itojun Exp $ */ /* @@ -562,13 +562,19 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, return (EINVAL); /* - * The destination address for a p2p link must have a family - * of AF_UNSPEC or AF_INET6. + * The destination address for a p2p link or the address of the + * announcing router for an autoconf address must have a family of + * AF_UNSPEC or AF_INET6. */ - if ((ifp->if_flags & IFF_POINTOPOINT) != 0 && - ifra->ifra_dstaddr.sin6_family != AF_INET6 && - ifra->ifra_dstaddr.sin6_family != AF_UNSPEC) - return (EAFNOSUPPORT); + if ((ifp->if_flags & IFF_POINTOPOINT) || + (ifp->if_flags & IFF_LOOPBACK) || + (ifra->ifra_flags & IN6_IFF_AUTOCONF)) { + if (ifra->ifra_dstaddr.sin6_family != AF_INET6 && + ifra->ifra_dstaddr.sin6_family != AF_UNSPEC) + return (EAFNOSUPPORT); + + } else if (ifra->ifra_dstaddr.sin6_family != AF_UNSPEC) + return (EINVAL); /* * validate ifra_prefixmask. don't check sin6_family, netmask @@ -597,27 +603,15 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, */ plen = in6_mask2len(&ia6->ia_prefixmask.sin6_addr, NULL); } - /* - * If the destination address on a p2p interface is specified, - * and the address is a scoped one, validate/set the scope - * zone identifier. - */ + dst6 = ifra->ifra_dstaddr; - if ((ifp->if_flags & (IFF_POINTOPOINT|IFF_LOOPBACK)) != 0 && - (dst6.sin6_family == AF_INET6)) { + if (dst6.sin6_family == AF_INET6) { error = in6_check_embed_scope(&dst6, ifp->if_index); if (error) return error; - } - /* - * The destination address can be specified only for a p2p or a - * loopback interface. If specified, the corresponding prefix length - * must be 128. - */ - if (ifra->ifra_dstaddr.sin6_family == AF_INET6) { - if ((ifp->if_flags & (IFF_POINTOPOINT|IFF_LOOPBACK)) == 0) - return (EINVAL); - if (plen != 128) + + if (((ifp->if_flags & IFF_POINTOPOINT) || + (ifp->if_flags & IFF_LOOPBACK)) && plen != 128) return (EINVAL); } /* lifetime consistency check */ @@ -652,7 +646,8 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, ia6->ia_addr.sin6_family = AF_INET6; ia6->ia_addr.sin6_len = sizeof(ia6->ia_addr); ia6->ia6_updatetime = getuptime(); - if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) != 0) { + if ((ifp->if_flags & IFF_POINTOPOINT) || + (ifp->if_flags & IFF_LOOPBACK)) { /* * XXX: some functions expect that ifa_dstaddr is not * NULL for p2p interfaces. @@ -686,10 +681,10 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, /* * If a new destination address is specified, scrub the old one and - * install the new destination. Note that the interface must be - * p2p or loopback (see the check above.) + * install the new destination. */ - if ((ifp->if_flags & IFF_POINTOPOINT) && dst6.sin6_family == AF_INET6 && + if (((ifp->if_flags & IFF_POINTOPOINT) || + (ifp->if_flags & IFF_LOOPBACK)) && dst6.sin6_family == AF_INET6 && !IN6_ARE_ADDR_EQUAL(&dst6.sin6_addr, &ia6->ia_dstaddr.sin6_addr)) { struct ifaddr *ifa = &ia6->ia_ifa; @@ -706,6 +701,13 @@ in6_update_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra, ia6->ia_dstaddr = dst6; } + if ((ifra->ifra_flags & IN6_IFF_AUTOCONF) && + dst6.sin6_family == AF_INET6 && + !IN6_ARE_ADDR_EQUAL(&dst6.sin6_addr, &ia6->ia_gwaddr.sin6_addr)) { + /* Set or update announcing router */ + ia6->ia_gwaddr = dst6; + } + /* * Set lifetimes. We do not refer to ia6t_expire and ia6t_preferred * to see if the address is deprecated or invalidated, but initialize @@ -1329,13 +1331,21 @@ in6_prefixlen2mask(struct in6_addr *maskp, int len) * return the best address out of the same scope */ struct in6_ifaddr * -in6_ifawithscope(struct ifnet *oifp, struct in6_addr *dst, u_int rdomain) +in6_ifawithscope(struct ifnet *oifp, struct in6_addr *dst, u_int rdomain, + struct rtentry *rt) { int dst_scope = in6_addrscope(dst), src_scope, best_scope = 0; int blen = -1; struct ifaddr *ifa; struct ifnet *ifp; struct in6_ifaddr *ia6_best = NULL; + struct in6_addr *gw6 = NULL; + + if (rt) { + if (rt->rt_gateway != NULL && + rt->rt_gateway->sa_family == AF_INET6) + gw6 = &(satosin6(rt->rt_gateway)->sin6_addr); + } if (oifp == NULL) { printf("%s: output interface is not specified\n", __func__); @@ -1460,8 +1470,16 @@ in6_ifawithscope(struct ifnet *oifp, struct in6_addr *dst, u_int rdomain) /* * Rule 5.5: Prefer addresses in a prefix advertised * by the next-hop. - * We do not track this information. */ + if (gw6) { + struct in6_addr *in6_bestgw, *in6_newgw; + + in6_bestgw = &ia6_best->ia_gwaddr.sin6_addr; + in6_newgw = &ifatoia6(ifa)->ia_gwaddr.sin6_addr; + if (!IN6_ARE_ADDR_EQUAL(in6_bestgw, gw6) && + IN6_ARE_ADDR_EQUAL(in6_newgw, gw6)) + goto replace; + } /* * Rule 6: Prefer matching label. diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h index 642a24b1999..f4e99485d02 100644 --- a/sys/netinet6/in6.h +++ b/sys/netinet6/in6.h @@ -1,4 +1,4 @@ -/* $OpenBSD: in6.h,v 1.116 2024/02/13 12:22:09 bluhm Exp $ */ +/* $OpenBSD: in6.h,v 1.117 2024/04/21 17:32:11 florian Exp $ */ /* $KAME: in6.h,v 1.83 2001/03/29 02:55:07 jinmei Exp $ */ /* @@ -404,6 +404,7 @@ struct sockaddr_in6; struct ifaddr; struct in6_ifaddr; struct ifnet; +struct rtentry; void ipv6_input(struct ifnet *, struct mbuf *); struct mbuf * @@ -413,7 +414,8 @@ int in6_cksum(struct mbuf *, uint8_t, uint32_t, uint32_t); void in6_proto_cksum_out(struct mbuf *, struct ifnet *); int in6_localaddr(struct in6_addr *); int in6_addrscope(struct in6_addr *); -struct in6_ifaddr *in6_ifawithscope(struct ifnet *, struct in6_addr *, u_int); +struct in6_ifaddr *in6_ifawithscope(struct ifnet *, struct in6_addr *, u_int, + struct rtentry *); int in6_mask2len(struct in6_addr *, u_char *); int in6_nam2sin6(const struct mbuf *, struct sockaddr_in6 **); int in6_sa2sin6(struct sockaddr *, struct sockaddr_in6 **); diff --git a/sys/netinet6/in6_src.c b/sys/netinet6/in6_src.c index d6163d254c0..b7097b43466 100644 --- a/sys/netinet6/in6_src.c +++ b/sys/netinet6/in6_src.c @@ -1,4 +1,4 @@ -/* $OpenBSD: in6_src.c,v 1.98 2024/03/31 15:53:12 bluhm Exp $ */ +/* $OpenBSD: in6_src.c,v 1.99 2024/04/21 17:32:11 florian Exp $ */ /* $KAME: in6_src.c,v 1.36 2001/02/06 04:08:17 itojun Exp $ */ /* @@ -162,7 +162,7 @@ in6_pcbselsrc(const struct in6_addr **in6src, struct sockaddr_in6 *dstsock, if (ifp == NULL) return (ENXIO); /* XXX: better error? */ - ia6 = in6_ifawithscope(ifp, dst, rtableid); + ia6 = in6_ifawithscope(ifp, dst, rtableid, NULL); if_put(ifp); if (ia6 == NULL) @@ -192,7 +192,7 @@ in6_pcbselsrc(const struct in6_addr **in6src, struct sockaddr_in6 *dstsock, if (rt != NULL) { ifp = if_get(rt->rt_ifidx); if (ifp != NULL) { - ia6 = in6_ifawithscope(ifp, dst, rtableid); + ia6 = in6_ifawithscope(ifp, dst, rtableid, rt); if_put(ifp); } if (ia6 == NULL) /* xxx scope error ?*/ @@ -256,7 +256,7 @@ in6_selectsrc(const struct in6_addr **in6src, struct sockaddr_in6 *dstsock, if (ifp == NULL) return (ENXIO); /* XXX: better error? */ - ia6 = in6_ifawithscope(ifp, dst, rtableid); + ia6 = in6_ifawithscope(ifp, dst, rtableid, NULL); if_put(ifp); if (ia6 == NULL) @@ -280,7 +280,7 @@ in6_selectsrc(const struct in6_addr **in6src, struct sockaddr_in6 *dstsock, ifp = if_get(htons(dstsock->sin6_scope_id)); if (ifp) { - ia6 = in6_ifawithscope(ifp, dst, rtableid); + ia6 = in6_ifawithscope(ifp, dst, rtableid, NULL); if_put(ifp); if (ia6 == NULL) diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h index 1323eee209a..bd64239f2ce 100644 --- a/sys/netinet6/in6_var.h +++ b/sys/netinet6/in6_var.h @@ -1,4 +1,4 @@ -/* $OpenBSD: in6_var.h,v 1.78 2022/11/23 07:57:39 kn Exp $ */ +/* $OpenBSD: in6_var.h,v 1.79 2024/04/21 17:32:11 florian Exp $ */ /* $KAME: in6_var.h,v 1.55 2001/02/16 12:49:45 itojun Exp $ */ /* @@ -93,6 +93,7 @@ struct in6_ifaddr { #define ia_flags ia_ifa.ifa_flags struct sockaddr_in6 ia_addr; /* interface address */ + struct sockaddr_in6 ia_gwaddr; /* router we learned address from */ struct sockaddr_in6 ia_dstaddr; /* space for destination addr */ struct sockaddr_in6 ia_prefixmask; /* prefix mask */ TAILQ_ENTRY(in6_ifaddr) ia_list; /* list of IP6 addresses */ -- 2.20.1