]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
add RFC4191 support (#297)
authorDaniel Goertzen <daniel.goertzen@gmail.com>
Sat, 9 Mar 2024 01:27:57 +0000 (19:27 -0600)
committerGitHub <noreply@github.com>
Sat, 9 Mar 2024 01:27:57 +0000 (01:27 +0000)
* add RFC4191 support

- handles route information options from RAs.
- refactor `sa_fromprefix()` to expose lower level functionality
- refactor `ipv6nd_rtprefix()` to be usable outside of `struct ra` context

* changes as requested by RM

- mostly minor/cosmetic changes
- functional change: "no longer a default router" warning moved to capture changes from routeinfo options

* simplify routeinfo_find/new

src/ipv6.c
src/ipv6nd.c
src/ipv6nd.h
src/sa.c
src/sa.h

index eb8c617a0047d5c349d21185c9286b98c7a5b6d1..ce985d4ec5b154bc17a78907dc0f51784bf59001 100644 (file)
@@ -2318,7 +2318,9 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
 {
        struct rt *rt;
        struct ra *rap;
+       const struct routeinfo *rinfo;
        const struct ipv6_addr *addr;
+       struct in6_addr netmask;
 
        if (ctx->ra_routers == NULL)
                return 0;
@@ -2326,6 +2328,27 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
        TAILQ_FOREACH(rap, ctx->ra_routers, next) {
                if (rap->expired)
                        continue;
+
+               /* add rfc4191 route information routes */
+               TAILQ_FOREACH (rinfo, &rap->rinfos, next) {
+                       if(rinfo->lifetime == 0)
+                               continue;
+                       if ((rt = inet6_makeroute(rap->iface, rap)) == NULL)
+                               continue;
+
+                       in6_addr_fromprefix(&netmask, rinfo->prefix_len);
+
+                       sa_in6_init(&rt->rt_dest, &rinfo->prefix);
+                       sa_in6_init(&rt->rt_netmask, &netmask);
+                       sa_in6_init(&rt->rt_gateway, &rap->from);
+#ifdef HAVE_ROUTE_PREF
+                       rt->rt_pref = ipv6nd_rtpref(rinfo->flags);
+#endif
+
+                       rt_proto_add(routes, rt);
+               }
+
+               /* add subnet routes */
                TAILQ_FOREACH(addr, &rap->addrs, next) {
                        if (addr->prefix_vltime == 0)
                                continue;
@@ -2333,11 +2356,13 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
                        if (rt) {
                                rt->rt_dflags |= RTDF_RA;
 #ifdef HAVE_ROUTE_PREF
-                               rt->rt_pref = ipv6nd_rtpref(rap);
+                               rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
                                rt_proto_add(routes, rt);
                        }
                }
+
+               /* add default route */
                if (rap->lifetime == 0)
                        continue;
                if (ipv6_anyglobal(rap->iface) == NULL)
@@ -2347,7 +2372,7 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
                        continue;
                rt->rt_dflags |= RTDF_RA;
 #ifdef HAVE_ROUTE_PREF
-               rt->rt_pref = ipv6nd_rtpref(rap);
+               rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
                rt_proto_add(routes, rt);
        }
index 9bf7c5dff42a8298d80357b550c1dce8348d77a5..9264dce7300c7d6078b97c2ced948e09bb9ede05 100644 (file)
 #define        ND_OPT_PI_FLAG_ROUTER   0x20    /* Router flag in PI */
 #endif
 
+#ifndef ND_OPT_RI
+#define ND_OPT_RI      24
+struct nd_opt_ri {             /* Route Information option RFC4191 */
+       uint8_t  nd_opt_ri_type;
+       uint8_t  nd_opt_ri_len;
+       uint8_t  nd_opt_ri_prefixlen;
+       uint8_t  nd_opt_ri_flags_reserved;
+       uint32_t nd_opt_ri_lifetime;
+       struct in6_addr nd_opt_ri_prefix;
+};
+__CTASSERT(sizeof(struct nd_opt_ri) == 24);
+#define OPT_RI_FLAG_PREFERENCE(flags) ((flags & 0x18) >> 3)
+#endif
+
 #ifndef ND_OPT_RDNSS
 #define ND_OPT_RDNSS                   25
 struct nd_opt_rdnss {           /* RDNSS option RFC 6106 */
@@ -132,6 +146,8 @@ __CTASSERT(sizeof(struct nd_opt_dnssl) == 8);
 //
 
 static void ipv6nd_handledata(void *, unsigned short);
+static struct routeinfo *routeinfo_findalloc(struct ra *, const struct in6_addr *, uint8_t);
+static void routeinfohead_free(struct routeinfohead *);
 
 /*
  * Android ships buggy ICMP6 filter headers.
@@ -612,10 +628,10 @@ ipv6nd_startexpire(struct interface *ifp)
 }
 
 int
-ipv6nd_rtpref(struct ra *rap)
+ipv6nd_rtpref(uint8_t flags)
 {
 
-       switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
+       switch (flags & ND_RA_FLAG_RTPREF_MASK) {
        case ND_RA_FLAG_RTPREF_HIGH:
                return RTPREF_HIGH;
        case ND_RA_FLAG_RTPREF_MEDIUM:
@@ -624,7 +640,7 @@ ipv6nd_rtpref(struct ra *rap)
        case ND_RA_FLAG_RTPREF_LOW:
                return RTPREF_LOW;
        default:
-               logerrx("%s: impossible RA flag %x", __func__, rap->flags);
+               logerrx("%s: impossible RA flag %x", __func__, flags);
                return RTPREF_INVALID;
        }
        /* NOTREACHED */
@@ -649,7 +665,7 @@ ipv6nd_sortrouters(struct dhcpcd_ctx *ctx)
                                continue;
                        if (!ra1->isreachable && ra2->reachable)
                                continue;
-                       if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2))
+                       if (ipv6nd_rtpref(ra1->flags) <= ipv6nd_rtpref(ra2->flags))
                                continue;
                        /* All things being equal, prefer older routers. */
                        /* We don't need to check time, becase newer
@@ -827,6 +843,7 @@ ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra)
        if (remove_ra)
                TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next);
        ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL);
+       routeinfohead_free(&rap->rinfos);
        free(rap->data);
        free(rap);
 }
@@ -1105,6 +1122,8 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
        struct nd_opt_prefix_info pi;
        struct nd_opt_mtu mtu;
        struct nd_opt_rdnss rdnss;
+       struct nd_opt_ri ri;
+       struct routeinfo *rinfo;
        uint8_t *p;
        struct ra *rap;
        struct in6_addr pi_prefix;
@@ -1206,6 +1225,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
                rap->from = from->sin6_addr;
                strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
                TAILQ_INIT(&rap->addrs);
+               TAILQ_INIT(&rap->rinfos);
                new_rap = true;
                rap->isreachable = true;
        } else
@@ -1237,9 +1257,6 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
        rap->flags = nd_ra->nd_ra_flags_reserved;
        old_lifetime = rap->lifetime;
        rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
-       if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
-               logwarnx("%s: %s: no longer a default router (lifetime = 0)",
-                   ifp->name, rap->sfrom);
        if (nd_ra->nd_ra_curhoplimit != 0)
                rap->hoplimit = nd_ra->nd_ra_curhoplimit;
        else
@@ -1502,6 +1519,46 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
                            rdnss.nd_opt_rdnss_len > 1)
                                rap->hasdns = 1;
                        break;
+               case ND_OPT_RI:
+                       if (ndo.nd_opt_len > 3) {
+                               logmessage(loglevel, "%s: invalid route info option",
+                                   ifp->name);
+                               break;
+                       }
+                       memset(&ri, 0, sizeof(ri));
+                       memcpy(&ri, p, olen); /* may be smaller than sizeof(ri), pad with zero */
+                       if(ri.nd_opt_ri_prefixlen > 128) {
+                               logmessage(loglevel, "%s: invalid route info prefix length",
+                                   ifp->name);
+                               break;
+                       }
+
+                       /* rfc4191 3.1 - RI for ::/0 applies to default route */
+                       if(ri.nd_opt_ri_prefixlen == 0) {
+                               rap->lifetime = ntohl(ri.nd_opt_ri_lifetime);
+
+                               /* Update preference leaving other flags intact */
+                               rap->flags = ((rap->flags & (~ (unsigned int)ND_RA_FLAG_RTPREF_MASK))
+                                       | ri.nd_opt_ri_flags_reserved) & 0xff;
+
+                               break;
+                       }
+
+                       /* Update existing route info instead of rebuilding all routes so that
+                       previously announced but now absent routes can stay alive.  To kill a
+                       route early, an RI with lifetime=0 needs to be received (rfc4191 3.1)*/
+                       rinfo = routeinfo_findalloc(rap, &ri.nd_opt_ri_prefix, ri.nd_opt_ri_prefixlen);
+                       if(rinfo == NULL) {
+                               logerr(__func__);
+                               break;
+                       }
+
+                       /* Update/initialize other route info params */
+                       rinfo->flags = ri.nd_opt_ri_flags_reserved;
+                       rinfo->lifetime = ntohl(ri.nd_opt_ri_lifetime);
+                       rinfo->acquired = rap->acquired;
+
+                       break;
                default:
                        continue;
                }
@@ -1537,6 +1594,10 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx,
                ia->prefix_pltime = 0;
        }
 
+       if (!new_rap && rap->lifetime == 0 && old_lifetime != 0)
+               logwarnx("%s: %s: no longer a default router (lifetime = 0)",
+                   ifp->name, rap->sfrom);
+
        if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp))
                logwarnx("%s: no global addresses for default route",
                    ifp->name);
@@ -1699,7 +1760,7 @@ ipv6nd_env(FILE *fp, const struct interface *ifp)
                        return -1;
                if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1)
                        return -1;
-               pref = ipv6nd_rtpref(rap);
+               pref = ipv6nd_rtpref(rap->flags);
                if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix,
                    rap->flags & ND_RA_FLAG_MANAGED    ? "M" : "",
                    rap->flags & ND_RA_FLAG_OTHER      ? "O" : "",
@@ -1804,6 +1865,7 @@ ipv6nd_expirera(void *arg)
        uint32_t elapsed;
        bool expired, valid;
        struct ipv6_addr *ia;
+       struct routeinfo *rinfo, *rinfob;
        size_t len, olen;
        uint8_t *p;
        struct nd_opt_hdr ndo;
@@ -1823,7 +1885,8 @@ ipv6nd_expirera(void *arg)
                if (rap->iface != ifp || rap->expired)
                        continue;
                valid = false;
-               if (rap->lifetime) {
+               /* lifetime may be set to infinite by rfc4191 route information */
+               if (rap->lifetime && rap->lifetime != ND6_INFINITE_LIFETIME) {
                        elapsed = (uint32_t)eloop_timespec_diff(&now,
                            &rap->acquired, NULL);
                        if (elapsed >= rap->lifetime || rap->doexpire) {
@@ -1879,6 +1942,20 @@ ipv6nd_expirera(void *arg)
                        }
                }
 
+               /* Expire route information */
+               TAILQ_FOREACH_SAFE(rinfo, &rap->rinfos, next, rinfob) {
+                       if (rinfo->lifetime == ND6_INFINITE_LIFETIME &&
+                           !rap->doexpire)
+                               continue;
+                       elapsed = (uint32_t)eloop_timespec_diff(&now,
+                           &rinfo->acquired, NULL);
+                       if (elapsed >= rinfo->lifetime || rap->doexpire) {
+                               logwarnx("%s: expired route %s",
+                                   rap->iface->name, rinfo->sprefix);
+                               TAILQ_REMOVE(&rap->rinfos, rinfo, next);
+                       }
+               }
+
                /* Work out expiry for ND options */
                elapsed = (uint32_t)eloop_timespec_diff(&now,
                    &rap->acquired, NULL);
@@ -2135,3 +2212,43 @@ ipv6nd_startrs(struct interface *ifp)
        eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp);
        return;
 }
+
+static struct routeinfo *routeinfo_findalloc(struct ra *rap, const struct in6_addr *prefix, uint8_t prefix_len)
+{
+       struct routeinfo *ri;
+       char buf[INET6_ADDRSTRLEN];
+       const char *p;
+
+       TAILQ_FOREACH(ri, &rap->rinfos, next) {
+               if (ri->prefix_len == prefix_len &&
+                   IN6_ARE_ADDR_EQUAL(&ri->prefix, prefix))
+                       return ri;
+       }
+
+       ri = malloc(sizeof(struct routeinfo));
+       if (ri == NULL)
+               return NULL;
+
+       memcpy(&ri->prefix, prefix, sizeof(ri->prefix));
+       ri->prefix_len = prefix_len;
+       p = inet_ntop(AF_INET6, prefix, buf, sizeof(buf));
+       if (p)
+               snprintf(ri->sprefix,
+                       sizeof(ri->sprefix),
+                       "%s/%d",
+                       p, prefix_len);
+       else
+               ri->sprefix[0] = '\0';
+       TAILQ_INSERT_TAIL(&rap->rinfos, ri, next);
+       return ri;
+}
+
+static void routeinfohead_free(struct routeinfohead *head)
+{
+       struct routeinfo *ri;
+
+       while ((ri = TAILQ_FIRST(head))) {
+               TAILQ_REMOVE(head, ri, next);
+               free(ri);
+       }
+}
index b702c3bd9f1d4d80f1b9b827da935c8af0f4cc6b..837b7d0f1e3ebba10af335e97ff43f73921f69bc 100644 (file)
 #include "dhcpcd.h"
 #include "ipv6.h"
 
+/* rfc4191 */
+struct routeinfo {
+       TAILQ_ENTRY(routeinfo) next;
+       struct in6_addr prefix;
+       uint8_t prefix_len;
+       uint32_t lifetime;
+       uint8_t flags;
+       struct timespec acquired;
+       char sprefix[INET6_ADDRSTRLEN];
+};
+
+TAILQ_HEAD(routeinfohead, routeinfo);
+
+
 struct ra {
        TAILQ_ENTRY(ra) next;
        struct interface *iface;
@@ -45,13 +59,14 @@ struct ra {
        uint8_t *data;
        size_t data_len;
        struct timespec acquired;
-       unsigned char flags;
+       uint8_t flags;
        uint32_t lifetime;
        uint32_t reachable;
        uint32_t retrans;
        uint32_t mtu;
        uint8_t hoplimit;
        struct ipv6_addrhead addrs;
+       struct routeinfohead rinfos;
        bool hasdns;
        bool expired;
        bool willexpire;
@@ -105,7 +120,7 @@ int ipv6nd_open(bool);
 int ipv6nd_openif(struct interface *);
 #endif
 void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *);
-int ipv6nd_rtpref(struct ra *);
+int ipv6nd_rtpref(uint8_t);
 void ipv6nd_printoptions(const struct dhcpcd_ctx *,
     const struct dhcp_opt *, size_t);
 void ipv6nd_startrs(struct interface *);
index f1e2e16e2e5a777c9aa73a929d51fd0b9f885725..05009d3bae7669e39ee0e4ba5f6fc4acae8fdf95 100644 (file)
--- a/src/sa.c
+++ b/src/sa.c
@@ -300,11 +300,39 @@ sa_toprefix(const struct sockaddr *sa)
        return prefix;
 }
 
+static void
+ipbytes_fromprefix(uint8_t *ap, int prefix, int max_prefix)
+{
+       int bytes, bits, i;
+
+       bytes = prefix / NBBY;
+       bits = prefix % NBBY;
+
+       for (i = 0; i < bytes; i++)
+               *ap++ = 0xff;
+       if (bits) {
+               uint8_t a;
+
+               a = 0xff;
+               a  = (uint8_t)(a << (8 - bits));
+               *ap++ = a;
+       }
+       bytes = (max_prefix - prefix) / NBBY;
+       for (i = 0; i < bytes; i++)
+               *ap++ = 0x00;
+}
+
+void
+in6_addr_fromprefix(struct in6_addr *addr, int prefix)
+{
+       ipbytes_fromprefix((uint8_t *)addr, prefix, 128);
+}
+
 int
 sa_fromprefix(struct sockaddr *sa, int prefix)
 {
        uint8_t *ap;
-       int max_prefix, bytes, bits, i;
+       int max_prefix;
 
        switch (sa->sa_family) {
 #ifdef INET
@@ -328,22 +356,8 @@ sa_fromprefix(struct sockaddr *sa, int prefix)
                return -1;
        }
 
-       bytes = prefix / NBBY;
-       bits = prefix % NBBY;
-
        ap = (uint8_t *)sa + sa_addroffset(sa);
-       for (i = 0; i < bytes; i++)
-               *ap++ = 0xff;
-       if (bits) {
-               uint8_t a;
-
-               a = 0xff;
-               a  = (uint8_t)(a << (8 - bits));
-               *ap++ = a;
-       }
-       bytes = (max_prefix - prefix) / NBBY;
-       for (i = 0; i < bytes; i++)
-               *ap++ = 0x00;
+       ipbytes_fromprefix(ap, prefix, max_prefix);
 
 #ifndef NDEBUG
        /* Ensure the calculation is correct */
index a848defd0547faa295d144af775484565fec5929..902229afbe670b02aa0d546433f8d853ca6965f1 100644 (file)
--- a/src/sa.h
+++ b/src/sa.h
@@ -67,6 +67,7 @@ bool sa_is_loopback(const struct sockaddr *);
 void *sa_toaddr(struct sockaddr *);
 int sa_toprefix(const struct sockaddr *);
 int sa_fromprefix(struct sockaddr *, int);
+void in6_addr_fromprefix(struct in6_addr *, int);
 const char *sa_addrtop(const struct sockaddr *, char *, socklen_t);
 int sa_cmp(const struct sockaddr *, const struct sockaddr *);
 void sa_in_init(struct sockaddr *, const struct in_addr *);