Implement rule 5.5 of RFC 6724 (Default Address Selection for IPv6)
authorflorian <florian@openbsd.org>
Sun, 21 Apr 2024 17:32:10 +0000 (17:32 +0000)
committerflorian <florian@openbsd.org>
Sun, 21 Apr 2024 17:32:10 +0000 (17:32 +0000)
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
sys/netinet6/in6.c
sys/netinet6/in6.h
sys/netinet6/in6_src.c
sys/netinet6/in6_var.h

index 2ec50eb..058800b 100644 (file)
@@ -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)
index 50bceb0..e1a7a62 100644 (file)
@@ -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.
index 642a24b..f4e9948 100644 (file)
@@ -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 **);
index d6163d2..b7097b4 100644 (file)
@@ -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)
index 1323eee..bd64239 100644 (file)
@@ -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 */