sd-icmp6-nd: Parse ICMPv6 prefix information

Save each new onlink IPv6 prefix and attach an expiry timer to it.
If the prefixes overlap, take the shorter prefix and write a debug
message about the event. Once the prefix is resent in a Router
Advertisement, update the timer. Add a new event for the expiring
prefix.

Add two helper functions, one for returning a prefix length given a
Router Advertisement and the other for generic prefix matching given
an IPv6 prefix and address.
This commit is contained in:
Patrik Flykt 2015-01-20 19:36:01 +02:00
parent 8d7f2c6a47
commit d77bde34cf
2 changed files with 224 additions and 4 deletions

View file

@ -249,6 +249,201 @@ int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) {
return 0;
}
static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec,
void *userdata) {
sd_icmp6_nd *nd = userdata;
ICMP6Prefix *prefix, *p;
assert(nd);
LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) {
if (prefix->timeout_valid != s)
continue;
log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ADDRESS_FORMAT_STR"/%d",
SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
prefix->len);
LIST_REMOVE(prefixes, nd->prefixes, prefix);
icmp6_nd_notify(nd,
ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED);
prefix = icmp6_prefix_unref(prefix);
break;
}
return 0;
}
static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd,
ICMP6Prefix *prefix,
usec_t valid) {
usec_t time_now;
int r;
assert_return(prefix, -EINVAL);
r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
if (r < 0)
return r;
prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid);
r = sd_event_add_time(nd->event, &prefix->timeout_valid,
clock_boottime_or_monotonic(), time_now + valid,
USEC_PER_SEC, icmp6_ra_prefix_timeout, nd);
if (r < 0)
goto error;
r = sd_event_source_set_priority(prefix->timeout_valid,
nd->event_priority);
if (r < 0)
goto error;
r = sd_event_source_set_description(prefix->timeout_valid,
"icmp6-prefix-timeout");
error:
if (r < 0)
prefix->timeout_valid =
sd_event_source_unref(prefix->timeout_valid);
return r;
}
static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen,
const struct in6_addr *addr,
uint8_t addr_prefixlen) {
uint8_t bytes, mask, len;
assert_return(prefix, -EINVAL);
assert_return(addr, -EINVAL);
len = MIN(prefixlen, addr_prefixlen);
bytes = len / 8;
mask = 0xff << (8 - len % 8);
if (memcmp(prefix, addr, bytes) != 0 ||
(prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask))
return -EADDRNOTAVAIL;
return 0;
}
static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr,
uint8_t addr_len, ICMP6Prefix **result) {
ICMP6Prefix *prefix;
LIST_FOREACH(prefixes, prefix, head) {
if (icmp6_prefix_match(&prefix->addr, prefix->len, addr,
addr_len) >= 0) {
*result = prefix;
return 0;
}
}
return -EADDRNOTAVAIL;
}
int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
struct in6_addr *addr) {
return icmp6_prefix_match(prefix, prefixlen, addr,
sizeof(addr->s6_addr) * 8);
}
int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
uint8_t *prefixlen) {
int r;
ICMP6Prefix *prefix;
assert_return(nd, -EINVAL);
assert_return(addr, -EINVAL);
assert_return(prefixlen, -EINVAL);
r = icmp6_ra_prefix_match(nd->prefixes, addr,
sizeof(addr->s6_addr) * 8, &prefix);
if (r < 0)
return r;
*prefixlen = prefix->len;
return 0;
}
static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len,
const struct nd_opt_prefix_info *prefix_opt) {
int r;
ICMP6Prefix *prefix;
uint32_t lifetime;
char time_string[FORMAT_TIMESPAN_MAX];
assert_return(nd, -EINVAL);
assert_return(prefix_opt, -EINVAL);
if (len < prefix_opt->nd_opt_pi_len)
return -ENOMSG;
if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK))
return 0;
lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time);
r = icmp6_ra_prefix_match(nd->prefixes,
&prefix_opt->nd_opt_pi_prefix,
prefix_opt->nd_opt_pi_prefix_len, &prefix);
if (r < 0 && r != -EADDRNOTAVAIL)
return r;
/* if router advertisment prefix valid timeout is zero, the timeout
callback will be called immediately to clean up the prefix */
if (r == -EADDRNOTAVAIL) {
r = icmp6_prefix_new(&prefix);
if (r < 0)
return r;
prefix->len = prefix_opt->nd_opt_pi_prefix_len;
memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix,
sizeof(prefix->addr));
log_icmp6_nd(nd, "New prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
prefix->len, lifetime,
format_timespan(time_string, FORMAT_TIMESPAN_MAX,
lifetime * USEC_PER_SEC, 0));
LIST_PREPEND(prefixes, nd->prefixes, prefix);
} else {
if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) {
uint8_t prefixlen;
prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len);
log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d",
prefix->len,
prefix_opt->nd_opt_pi_prefix_len,
prefixlen);
prefix->len = prefixlen;
}
log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s",
SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr),
prefix->len, lifetime,
format_timespan(time_string, FORMAT_TIMESPAN_MAX,
lifetime * USEC_PER_SEC, 0));
}
r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC);
return r;
}
static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
ssize_t len) {
void *opt;
@ -270,6 +465,7 @@ static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) {
struct nd_opt_mtu *opt_mtu;
uint32_t mtu;
struct nd_opt_prefix_info *opt_prefix;
if (opt_hdr->nd_opt_len == 0)
return -ENOMSG;
@ -289,6 +485,12 @@ static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra,
break;
case ND_OPT_PREFIX_INFORMATION:
opt_prefix = opt;
icmp6_ra_prefix_update(nd, len, opt_prefix);
break;
}
len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS;

View file

@ -27,10 +27,11 @@
#include "sd-event.h"
enum {
ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE = 0,
ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT = 1,
ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER = 2,
ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED = 3,
ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE = 0,
ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT = 1,
ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER = 2,
ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED = 3,
ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED = 4,
};
typedef struct sd_icmp6_nd sd_icmp6_nd;
@ -51,9 +52,26 @@ sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd);
sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd);
int sd_icmp6_nd_new(sd_icmp6_nd **ret);
int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen,
struct in6_addr *addr);
int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu);
int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr,
uint8_t *prefixlen);
int sd_icmp6_nd_stop(sd_icmp6_nd *nd);
int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd);
#define SD_ICMP6_ADDRESS_FORMAT_STR "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x"
#define SD_ICMP6_ADDRESS_FORMAT_VAL(address) \
be16toh((address).s6_addr16[0]), \
be16toh((address).s6_addr16[1]), \
be16toh((address).s6_addr16[2]), \
be16toh((address).s6_addr16[3]), \
be16toh((address).s6_addr16[4]), \
be16toh((address).s6_addr16[5]), \
be16toh((address).s6_addr16[6]), \
be16toh((address).s6_addr16[7])
#endif