Improve IPv6 link-local address handling
authorclaudio <claudio@openbsd.org>
Mon, 16 Oct 2023 10:25:45 +0000 (10:25 +0000)
committerclaudio <claudio@openbsd.org>
Mon, 16 Oct 2023 10:25:45 +0000 (10:25 +0000)
When a session is established determine the possible interface scope of that
session. The scope is only set when the remote address is directly connected.
This interface scope is passed to the RDE that uses this information when
link-local nexthops are received. Again checking that a link-local nexthop
is actually acceptable.

OK tb@

usr.sbin/bgpd/bgpd.h
usr.sbin/bgpd/kroute.c
usr.sbin/bgpd/rde.c
usr.sbin/bgpd/rde.h
usr.sbin/bgpd/rde_peer.c
usr.sbin/bgpd/rde_rib.c
usr.sbin/bgpd/session.c
usr.sbin/bgpd/session.h

index c19e2ac..79d3c06 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: bgpd.h,v 1.477 2023/08/30 08:16:28 claudio Exp $ */
+/*     $OpenBSD: bgpd.h,v 1.478 2023/10/16 10:25:45 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -796,6 +796,7 @@ struct session_up {
        struct bgpd_addr        remote_addr;
        struct capabilities     capa;
        uint32_t                remote_bgpid;
+       unsigned int            if_scope;
        uint16_t                short_as;
 };
 
@@ -1439,6 +1440,7 @@ void               kr_ifinfo(char *);
 void            kr_net_reload(u_int, uint64_t, struct network_head *);
 int             kr_reload(void);
 int             get_mpe_config(const char *, u_int *, u_int *);
+uint8_t                 mask2prefixlen(sa_family_t, struct sockaddr *);
 
 /* log.c */
 void            log_peer_info(const struct peer_config *, const char *, ...)
index ac55f10..69c09c6 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: kroute.c,v 1.305 2023/06/01 09:47:34 claudio Exp $ */
+/*     $OpenBSD: kroute.c,v 1.306 2023/10/16 10:25:45 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -168,8 +168,6 @@ struct kroute6      *kroute6_match(struct ktable *, struct bgpd_addr *, int);
 void            kroute_detach_nexthop(struct ktable *, struct knexthop *);
 
 uint8_t                prefixlen_classful(in_addr_t);
-uint8_t                mask2prefixlen(in_addr_t);
-uint8_t                mask2prefixlen6(struct sockaddr_in6 *);
 uint64_t       ift2ifm(uint8_t);
 const char     *get_media_descr(uint64_t);
 const char     *get_linkstate(uint8_t, int);
@@ -2419,21 +2417,28 @@ prefixlen_classful(in_addr_t ina)
                return (8);
 }
 
-uint8_t
-mask2prefixlen(in_addr_t ina)
+static uint8_t
+mask2prefixlen4(struct sockaddr_in *sa_in)
 {
+       in_addr_t ina;
+
+       if (sa_in->sin_len == 0)
+               return (0);
+       ina = sa_in->sin_addr.s_addr;
        if (ina == 0)
                return (0);
        else
                return (33 - ffs(ntohl(ina)));
 }
 
-uint8_t
+static uint8_t
 mask2prefixlen6(struct sockaddr_in6 *sa_in6)
 {
        uint8_t *ap, *ep;
        u_int    l = 0;
 
+       if (sa_in6->sin6_len == 0)
+               return (0);
        /*
         * sin6_len is the size of the sockaddr so subtract the offset of
         * the possibly truncated sin6_addr struct.
@@ -2480,6 +2485,19 @@ mask2prefixlen6(struct sockaddr_in6 *sa_in6)
        return (l);
 }
 
+uint8_t
+mask2prefixlen(sa_family_t af, struct sockaddr *mask)
+{
+       switch (af) {
+       case AF_INET:
+               return mask2prefixlen4((struct sockaddr_in *)mask);
+       case AF_INET6:
+               return mask2prefixlen6((struct sockaddr_in6 *)mask);
+       default:
+               fatalx("%s: unsupported af", __func__);
+       }
+}
+
 const struct if_status_description
                if_status_descriptions[] = LINK_STATE_DESCRIPTIONS;
 const struct ifmedia_description
@@ -3079,9 +3097,7 @@ dispatch_rtmsg_addr(struct rt_msghdr *rtm, struct kroute_full *kf)
        case AF_INET:
                sa_in = (struct sockaddr_in *)rti_info[RTAX_NETMASK];
                if (sa_in != NULL) {
-                       if (sa_in->sin_len != 0)
-                               kf->prefixlen =
-                                   mask2prefixlen(sa_in->sin_addr.s_addr);
+                       kf->prefixlen = mask2prefixlen4(sa_in);
                } else if (rtm->rtm_flags & RTF_HOST)
                        kf->prefixlen = 32;
                else
@@ -3091,8 +3107,7 @@ dispatch_rtmsg_addr(struct rt_msghdr *rtm, struct kroute_full *kf)
        case AF_INET6:
                sa_in6 = (struct sockaddr_in6 *)rti_info[RTAX_NETMASK];
                if (sa_in6 != NULL) {
-                       if (sa_in6->sin6_len != 0)
-                               kf->prefixlen = mask2prefixlen6(sa_in6);
+                       kf->prefixlen = mask2prefixlen6(sa_in6);
                } else if (rtm->rtm_flags & RTF_HOST)
                        kf->prefixlen = 128;
                else
index dc6a318..659622b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde.c,v 1.610 2023/08/16 08:26:35 claudio Exp $ */
+/*     $OpenBSD: rde.c,v 1.611 2023/10/16 10:25:45 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -59,7 +59,7 @@ int            rde_attr_parse(u_char *, uint16_t, struct rde_peer *,
 int             rde_attr_add(struct filterstate *, u_char *, uint16_t);
 uint8_t                 rde_attr_missing(struct rde_aspath *, int, uint16_t);
 int             rde_get_mp_nexthop(u_char *, uint16_t, uint8_t,
-                   struct filterstate *);
+                   struct rde_peer *, struct filterstate *);
 void            rde_as4byte_fixup(struct rde_peer *, struct rde_aspath *);
 uint8_t                 rde_aspa_validity(struct rde_peer *, struct rde_aspath *,
                    uint8_t);
@@ -1797,7 +1797,8 @@ rde_update_dispatch(struct rde_peer *peer, struct imsg *imsg)
                /* unlock the previously locked nexthop, it is no longer used */
                nexthop_unref(state.nexthop);
                state.nexthop = NULL;
-               if ((pos = rde_get_mp_nexthop(mpp, mplen, aid, &state)) == -1) {
+               if ((pos = rde_get_mp_nexthop(mpp, mplen, aid, peer,
+                   &state)) == -1) {
                        log_peer_warnx(&peer->conf, "bad nlri nexthop");
                        rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
                            mpa.reach, mpa.reach_len);
@@ -2482,7 +2483,7 @@ rde_attr_missing(struct rde_aspath *a, int ebgp, uint16_t nlrilen)
 
 int
 rde_get_mp_nexthop(u_char *data, uint16_t len, uint8_t aid,
-    struct filterstate *state)
+    struct rde_peer *peer, struct filterstate *state)
 {
        struct bgpd_addr        nexthop;
        uint8_t                 totlen, nhlen;
@@ -2509,12 +2510,22 @@ rde_get_mp_nexthop(u_char *data, uint16_t len, uint8_t aid,
                 * traffic.
                 */
                if (nhlen != 16 && nhlen != 32) {
-                       log_warnx("bad %s nexthop, bad size %d", aid2str(aid),
-                           nhlen);
+                       log_peer_warnx(&peer->conf, "bad %s nexthop, "
+                           "bad size %d", aid2str(aid), nhlen);
                        return (-1);
                }
                memcpy(&nexthop.v6.s6_addr, data, 16);
                nexthop.aid = AID_INET6;
+               if (IN6_IS_ADDR_LINKLOCAL(&nexthop.v6)) {
+                       if (peer->local_if_scope != 0) {
+                               nexthop.scope_id = peer->local_if_scope;
+                       } else {
+                               log_peer_warnx(&peer->conf,
+                                   "unexpected link-local nexthop: %s",
+                                   log_addr(&nexthop));
+                               return (-1);
+                       }
+               }
                break;
        case AID_VPN_IPv4:
                /*
@@ -2531,8 +2542,8 @@ rde_get_mp_nexthop(u_char *data, uint16_t len, uint8_t aid,
                 * AID_VPN_IPv4 in nexthop and kroute.
                 */
                if (nhlen != 12) {
-                       log_warnx("bad %s nexthop, bad size %d", aid2str(aid),
-                           nhlen);
+                       log_peer_warnx(&peer->conf, "bad %s nexthop, "
+                           "bad size %d", aid2str(aid), nhlen);
                        return (-1);
                }
                nexthop.aid = AID_INET;
@@ -2541,26 +2552,37 @@ rde_get_mp_nexthop(u_char *data, uint16_t len, uint8_t aid,
                break;
        case AID_VPN_IPv6:
                if (nhlen != 24) {
-                       log_warnx("bad %s nexthop, bad size %d", aid2str(aid),
-                           nhlen);
+                       log_peer_warnx(&peer->conf, "bad %s nexthop, "
+                           "bad size %d", aid2str(aid), nhlen);
                        return (-1);
                }
                memcpy(&nexthop.v6, data + sizeof(uint64_t),
                    sizeof(nexthop.v6));
                nexthop.aid = AID_INET6;
+               if (IN6_IS_ADDR_LINKLOCAL(&nexthop.v6)) {
+                       if (peer->local_if_scope != 0) {
+                               nexthop.scope_id = peer->local_if_scope;
+                       } else {
+                               log_peer_warnx(&peer->conf,
+                                   "unexpected link-local nexthop: %s",
+                                   log_addr(&nexthop));
+                               return (-1);
+                       }
+               }
                break;
        case AID_FLOWSPECv4:
        case AID_FLOWSPECv6:
                /* nexthop must be 0 and ignored for flowspec */
                if (nhlen != 0) {
-                       log_warnx("bad %s nexthop, bad size %d", aid2str(aid),
-                           nhlen);
+                       log_peer_warnx(&peer->conf, "bad %s nexthop, "
+                           "bad size %d", aid2str(aid), nhlen);
                        return (-1);
                }
                /* also ignore reserved (old SNPA) field as per RFC4760 */
                return (totlen + 1);
        default:
-               log_warnx("bad multiprotocol nexthop, bad AID");
+               log_peer_warnx(&peer->conf, "bad multiprotocol nexthop, "
+                   "bad AID");
                return (-1);
        }
 
index c700aca..ee8987c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde.h,v 1.296 2023/08/16 08:26:35 claudio Exp $ */
+/*     $OpenBSD: rde.h,v 1.297 2023/10/16 10:25:46 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Claudio Jeker <claudio@openbsd.org> and
@@ -92,6 +92,7 @@ struct rde_peer {
        time_t                           staletime[AID_MAX];
        uint32_t                         remote_bgpid; /* host byte order! */
        uint32_t                         path_id_tx;
+       unsigned int                     local_if_scope;
        enum peer_state                  state;
        enum export_type                 export_type;
        enum role                        role;
index bbf0a9e..fe67303 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde_peer.c,v 1.32 2023/04/19 13:23:33 claudio Exp $ */
+/*     $OpenBSD: rde_peer.c,v 1.33 2023/10/16 10:25:46 claudio Exp $ */
 
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
@@ -430,6 +430,7 @@ peer_up(struct rde_peer *peer, struct session_up *sup)
        peer->remote_addr = sup->remote_addr;
        peer->local_v4_addr = sup->local_v4_addr;
        peer->local_v6_addr = sup->local_v6_addr;
+       peer->local_if_scope = sup->if_scope;
        memcpy(&peer->capa, &sup->capa, sizeof(peer->capa));
 
        /* clear eor markers depending on GR flags */
index cd56b20..04f7e63 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: rde_rib.c,v 1.260 2023/04/23 11:39:10 claudio Exp $ */
+/*     $OpenBSD: rde_rib.c,v 1.261 2023/10/16 10:25:46 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Claudio Jeker <claudio@openbsd.org>
@@ -1910,7 +1910,7 @@ nexthop_compare(struct nexthop *na, struct nexthop *nb)
        case AID_INET6:
                return (memcmp(&a->v6, &b->v6, sizeof(struct in6_addr)));
        default:
-               fatalx("nexthop_cmp: unknown af");
+               fatalx("nexthop_cmp: %s is unsupported", aid2str(a->aid));
        }
        return (-1);
 }
index 30d4015..ed809c4 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: session.c,v 1.448 2023/10/09 07:11:20 claudio Exp $ */
+/*     $OpenBSD: session.c,v 1.449 2023/10/16 10:25:46 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004, 2005 Henning Brauer <henning@openbsd.org>
@@ -1202,37 +1202,64 @@ session_setup_socket(struct peer *p)
        return (0);
 }
 
-/* compare two sockaddrs by converting them into bgpd_addr */
+/*
+ * compare the bgpd_addr with the sockaddr by converting the latter into
+ * a bgpd_addr. Return true if the two are equal, including any scope
+ */
 static int
-sa_equal(struct sockaddr *a, struct sockaddr *b)
+sa_equal(struct bgpd_addr *ba, struct sockaddr *b)
 {
-       struct bgpd_addr ba, bb;
+       struct bgpd_addr bb;
 
-       sa2addr(a, &ba, NULL);
        sa2addr(b, &bb, NULL);
-
-       return (memcmp(&ba, &bb, sizeof(ba)) == 0);
+       return (memcmp(ba, &bb, sizeof(*ba)) == 0);
 }
 
 static void
-get_alternate_addr(struct sockaddr *sa, struct bgpd_addr *alt)
+get_alternate_addr(struct bgpd_addr *local, struct bgpd_addr *remote,
+    struct bgpd_addr *alt, unsigned int *scope)
 {
        struct ifaddrs  *ifap, *ifa, *match;
+       int connected = 0;
+       u_int8_t plen;
 
        if (getifaddrs(&ifap) == -1)
                fatal("getifaddrs");
 
-       for (match = ifap; match != NULL; match = match->ifa_next)
-               if (match->ifa_addr != NULL && sa_equal(sa, match->ifa_addr))
+       for (match = ifap; match != NULL; match = match->ifa_next) {
+               if (match->ifa_addr == NULL)
+                       continue;
+               if (match->ifa_addr->sa_family != AF_INET &&
+                   match->ifa_addr->sa_family != AF_INET6)
+                       continue;
+               if (sa_equal(local, match->ifa_addr)) {
+                       if (match->ifa_flags & IFF_POINTOPOINT &&
+                           match->ifa_dstaddr) {
+                               if (sa_equal(remote, match->ifa_dstaddr))
+                                       connected = 1;
+                       } else if (match->ifa_netmask) {
+                               plen = mask2prefixlen(
+                                   match->ifa_addr->sa_family,
+                                   match->ifa_netmask);
+                               if (plen != 0xff &&
+                                   prefix_compare(local, remote, plen) == 0)
+                                       connected = 1;
+                       }
                        break;
+               }
+       }
 
        if (match == NULL) {
                log_warnx("%s: local address not found", __func__);
                return;
        }
+       if (connected)
+               *scope = if_nametoindex(match->ifa_name);
+       else
+               *scope = 0;
 
-       switch (sa->sa_family) {
-       case AF_INET6:
+       switch (local->aid) {
+       case AID_INET6:
                for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
                        if (ifa->ifa_addr != NULL &&
                            ifa->ifa_addr->sa_family == AF_INET &&
@@ -1242,7 +1269,7 @@ get_alternate_addr(struct sockaddr *sa, struct bgpd_addr *alt)
                        }
                }
                break;
-       case AF_INET:
+       case AID_INET:
                for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
                        if (ifa->ifa_addr != NULL &&
                            ifa->ifa_addr->sa_family == AF_INET6 &&
@@ -1260,8 +1287,8 @@ get_alternate_addr(struct sockaddr *sa, struct bgpd_addr *alt)
                }
                break;
        default:
-               log_warnx("%s: unsupported address family %d", __func__,
-                   sa->sa_family);
+               log_warnx("%s: unsupported address family %s", __func__,
+                   aid2str(local->aid));
                break;
        }
 
@@ -1278,11 +1305,13 @@ session_tcp_established(struct peer *peer)
        if (getsockname(peer->fd, (struct sockaddr *)&ss, &len) == -1)
                log_warn("getsockname");
        sa2addr((struct sockaddr *)&ss, &peer->local, &peer->local_port);
-       get_alternate_addr((struct sockaddr *)&ss, &peer->local_alt);
        len = sizeof(ss);
        if (getpeername(peer->fd, (struct sockaddr *)&ss, &len) == -1)
                log_warn("getpeername");
        sa2addr((struct sockaddr *)&ss, &peer->remote, &peer->remote_port);
+
+       get_alternate_addr(&peer->local, &peer->remote, &peer->local_alt,
+           &peer->if_scope);
 }
 
 void
@@ -3546,6 +3575,7 @@ session_up(struct peer *p)
                sup.local_v4_addr = p->local_alt;
        }
        sup.remote_addr = p->remote;
+       sup.if_scope = p->if_scope;
 
        sup.remote_bgpid = p->remote_bgpid;
        sup.short_as = p->short_as;
index 65e96d6..9c0969d 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: session.h,v 1.162 2023/03/28 12:15:23 claudio Exp $ */
+/*     $OpenBSD: session.h,v 1.163 2023/10/16 10:25:46 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -240,6 +240,7 @@ struct peer {
        int                      lasterr;
        u_int                    errcnt;
        u_int                    IdleHoldTime;
+       unsigned int             if_scope;      /* interface scope for IPv6 */
        uint32_t                 remote_bgpid;
        enum session_state       state;
        enum session_state       prev_state;