Skip to content

Commit

Permalink
add RFC4191 support (#297)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
goertzenator committed Mar 9, 2024
1 parent 457f21c commit f1cf924
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 29 deletions.
29 changes: 27 additions & 2 deletions src/ipv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -2318,26 +2318,51 @@ 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;

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;
rt = inet6_makeprefix(rap->iface, rap, addr);
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)
Expand All @@ -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);
}
Expand Down
135 changes: 126 additions & 9 deletions src/ipv6nd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand All @@ -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 */
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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" : "",
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
19 changes: 17 additions & 2 deletions src/ipv6nd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 *);
Expand Down

0 comments on commit f1cf924

Please sign in to comment.