From: Daniel Goertzen Date: Sat, 9 Mar 2024 01:27:57 +0000 (-0600) Subject: add RFC4191 support (#297) X-Git-Tag: v10.0.7~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f1cf924ad691bc1e6bf33013407fbf838fa40fbe;p=thirdparty%2Fdhcpcd.git add RFC4191 support (#297) * 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 --- diff --git a/src/ipv6.c b/src/ipv6.c index eb8c617a..ce985d4e 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -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); } diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 9bf7c5df..9264dce7 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -71,6 +71,20 @@ #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); + } +} diff --git a/src/ipv6nd.h b/src/ipv6nd.h index b702c3bd..837b7d0f 100644 --- a/src/ipv6nd.h +++ b/src/ipv6nd.h @@ -37,6 +37,20 @@ #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 *); diff --git a/src/sa.c b/src/sa.c index f1e2e16e..05009d3b 100644 --- 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 */ diff --git a/src/sa.h b/src/sa.h index a848defd..902229af 100644 --- 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 *);