From 806ebbc1f4dd848c2d19b56f5be84db16fada612 Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Wed, 30 Jan 2013 20:19:11 +0100 Subject: [PATCH] Add RA-handling support --- CMakeLists.txt | 3 +- src/dhcpv6.c | 295 +++++++++++++++++------------------------------- src/odhcp6c.c | 124 +++++++++++++++++--- src/odhcp6c.h | 29 ++++- src/ra.c | 264 +++++++++++++++++++++++++++++++++++++++++++ src/ra.h | 26 +++++ src/rtnetlink.c | 115 ------------------- src/script.c | 44 +++++--- 8 files changed, 553 insertions(+), 347 deletions(-) create mode 100644 src/ra.c create mode 100644 src/ra.h delete mode 100644 src/rtnetlink.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a268553..0db7aed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,7 @@ set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99") add_definitions(-D_GNU_SOURCE -Wall -Werror -Wextra -pedantic) -add_executable(odhcp6c src/odhcp6c.c src/dhcpv6.c - src/rtnetlink.c src/script.c) +add_executable(odhcp6c src/odhcp6c.c src/dhcpv6.c src/ra.c src/script.c) target_link_libraries(odhcp6c resolv) # Installation diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 28fad50..599870e 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -50,7 +50,7 @@ static reply_handler dhcpv6_handle_reply; static reply_handler dhcpv6_handle_advert; static reply_handler dhcpv6_handle_rebind_reply; static reply_handler dhcpv6_handle_reconfigure; -static int dhcpv6_commit_advert(uint32_t elapsed); +static int dhcpv6_commit_advert(void); @@ -75,7 +75,6 @@ static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = { // Sockets static int sock = -1; -static int urandom_fd = -1; static int ifindex = -1; static time_t t1 = 0, t2 = 0, t3 = 0; @@ -89,9 +88,6 @@ static bool accept_reconfig = false; int init_dhcpv6(const char *ifname, int request_pd) { request_prefix = request_pd; - urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY); - if (urandom_fd < 0) - return -1; sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); @@ -164,18 +160,6 @@ void dhcpv6_set_ia_na_mode(enum odhcp6c_ia_mode mode) } -void dhcpv6_remove_addrs(void) -{ - size_t ia_na_len; - uint8_t *odata, *ia_na = odhcp6c_get_state(STATE_IA_NA, &ia_na_len); - uint16_t otype, olen; - dhcpv6_for_each_option(ia_na, ia_na + ia_na_len, otype, olen, odata) { - struct dhcpv6_ia_addr *addr = (void*)&odata[-4]; - set_rtnetlink_addr(ifindex, &addr->addr, 0, 0); - } -} - - static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs) { // Build FQDN @@ -203,8 +187,9 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs) void *srv_id = odhcp6c_get_state(STATE_SERVER_ID, &srv_id_len); // Build IA_PDs - size_t ia_pd_len; - void *ia_pd = odhcp6c_get_state(STATE_IA_PD, &ia_pd_len); + size_t ia_pd_entry_len, ia_pd_len = 0; + void *ia_pd = NULL; + void *ia_pd_entries = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entry_len); struct dhcpv6_ia_hdr hdr_ia_pd = { htons(DHCPV6_OPT_IA_PD), htons(sizeof(hdr_ia_pd) - 4 + ia_pd_len), @@ -216,7 +201,21 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs) .len = htons(25), .prefix = request_prefix }; - if (ia_pd_len == 0 && request_prefix > 0 && + if (ia_pd_entry_len > 0) { + struct odhcp6c_entry *e = ia_pd_entries; + size_t entries = ia_pd_entry_len / sizeof(*e); + struct dhcpv6_ia_prefix p[entries]; + for (size_t i = 0; i < entries; ++i) { + p[i].type = htons(DHCPV6_OPT_IA_PREFIX); + p[i].len = htons(sizeof(p[i]) - 4U); + p[i].preferred = htonl(e[i].preferred); + p[i].valid = htonl(e[i].valid); + p[i].prefix = e[i].length; + p[i].addr = e[i].target; + } + ia_pd = p; + ia_pd_len = sizeof(p); + } else if (request_prefix > 0 && (type == DHCPV6_MSG_SOLICIT || type == DHCPV6_MSG_REQUEST)) { ia_pd = &pref; @@ -224,14 +223,30 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs) } // Build IA_NAs - size_t ia_na_len; - void *ia_na = odhcp6c_get_state(STATE_IA_NA, &ia_na_len); + size_t ia_na_entry_len, ia_na_len = 0; + void *ia_na = NULL; + void *ia_na_entries = odhcp6c_get_state(STATE_IA_NA, &ia_na_entry_len); struct dhcpv6_ia_hdr hdr_ia_na = { htons(DHCPV6_OPT_IA_NA), htons(sizeof(hdr_ia_na) - 4 + ia_na_len), 1, 0, 0 }; + if (ia_na_entry_len > 0) { + struct odhcp6c_entry *e = ia_na_entries; + size_t entries = ia_na_entry_len / sizeof(*e); + struct dhcpv6_ia_addr p[entries]; + for (size_t i = 0; i < entries; ++i) { + p[i].type = htons(DHCPV6_OPT_IA_ADDR); + p[i].len = htons(sizeof(p[i]) - 4U); + p[i].addr = e[i].target; + p[i].preferred = htonl(e[i].preferred); + p[i].valid = htonl(e[i].valid); + } + ia_na = p; + ia_na_len = sizeof(p); + } + // Reconfigure Accept struct { uint16_t type; @@ -306,7 +321,7 @@ static void dhcpv6_send(enum dhcpv6_msg type, uint8_t trid[3], uint32_t ecs) static int64_t dhcpv6_rand_delay(int64_t time) { int random; - read(urandom_fd, &random, sizeof(random)); + odhcp6c_random(&random, sizeof(random)); return (time * (random % 1000)) / 10000; } @@ -341,7 +356,7 @@ int dhcpv6_request(enum dhcpv6_msg type) // Generate transaction ID uint8_t trid[3]; - read(urandom_fd, trid, sizeof(trid)); + odhcp6c_random(trid, sizeof(trid)); ssize_t len = -1; int64_t rto = 0; @@ -370,7 +385,7 @@ int dhcpv6_request(enum dhcpv6_msg type) for (; len < 0 && round_start < round_end; round_start = odhcp6c_get_milli_time()) { // Check for pending signal - if (odhcp6c_signal_is_pending()) + if (odhcp6c_signal_process()) return -1; // Set timeout for receiving @@ -395,14 +410,14 @@ int dhcpv6_request(enum dhcpv6_msg type) "%ums", (unsigned)elapsed); if (retx->handler_reply) - len = retx->handler_reply(type, - opt, opt_end, elapsed / 1000); + len = retx->handler_reply( + type, opt, opt_end); } } // Allow if (retx->handler_finish) - len = retx->handler_finish(elapsed / 1000); + len = retx->handler_finish(); } while (len < 0 && elapsed / 1000 < timeout); return len; @@ -459,7 +474,7 @@ int dhcpv6_poll_reconfigure(void) static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig, - const void *opt, const void *end, uint32_t elapsed) + const void *opt, const void *end) { // TODO: should verify the reconfigure message uint16_t otype, olen; @@ -470,27 +485,14 @@ static int dhcpv6_handle_reconfigure(_unused enum dhcpv6_msg orig, odata[0] == DHCPV6_MSG_INFO_REQ)) msg = odata[0]; - t1 -= elapsed; - t2 -= elapsed; - t3 -= elapsed; - - if (t1 < 0) - t1 = 0; - - if (t2 < 0) - t2 = 0; - - if (t3 < 0) - t3 = 0; - - dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, NULL, NULL, elapsed); + dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, NULL, NULL); return msg; } // Collect all advertised servers static int dhcpv6_handle_advert(_unused enum dhcpv6_msg orig, - const void *opt, const void *end, _unused uint32_t elapsed) + const void *opt, const void *end) { uint16_t olen, otype; uint8_t *odata; @@ -537,7 +539,7 @@ static int dhcpv6_handle_advert(_unused enum dhcpv6_msg orig, } -static int dhcpv6_commit_advert(_unused uint32_t elapsed) +static int dhcpv6_commit_advert(void) { size_t cand_len; struct dhcpv6_server_cand *c = NULL, *cand = @@ -578,60 +580,54 @@ static int dhcpv6_commit_advert(_unused uint32_t elapsed) static int dhcpv6_handle_rebind_reply(enum dhcpv6_msg orig, - const void *opt, const void *end, uint32_t elapsed) + const void *opt, const void *end) { - dhcpv6_handle_advert(orig, opt, end, elapsed); - if (dhcpv6_commit_advert(elapsed) < 0) + dhcpv6_handle_advert(orig, opt, end); + if (dhcpv6_commit_advert() < 0) { + dhcpv6_handle_reply(DHCPV6_MSG_UNKNOWN, NULL, NULL); return -1; + } - return dhcpv6_handle_reply(orig, opt, end, elapsed); + return dhcpv6_handle_reply(orig, opt, end); } -static int dhcpv6_handle_reply(_unused enum dhcpv6_msg orig, - const void *opt, const void *end, uint32_t elapsed) +static int dhcpv6_handle_reply(enum dhcpv6_msg orig, + const void *opt, const void *end) { - uint16_t otype, olen; uint8_t *odata; - bool have_update = false; - - t1 = t2 = t3 = 86400; - - size_t ia_na_len, dns_len, search_len, sntp_ip_len, sntp_dns_len; - size_t sip_ip_len, sip_fqdn_len; - uint8_t *ia_na = odhcp6c_get_state(STATE_IA_NA, &ia_na_len); - uint8_t *ia_end; - odhcp6c_get_state(STATE_DNS, &dns_len); - odhcp6c_get_state(STATE_SEARCH, &search_len); - odhcp6c_get_state(STATE_SNTP_IP, &sntp_ip_len); - odhcp6c_get_state(STATE_SNTP_FQDN, &sntp_dns_len); - odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len); - odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len); - - // Decrease valid and preferred lifetime of prefixes - size_t ia_pd_len; - uint8_t *ia_pd = odhcp6c_get_state(STATE_IA_PD, &ia_pd_len); - dhcpv6_for_each_option(ia_pd, ia_pd + ia_pd_len, otype, olen, odata) { - struct dhcpv6_ia_prefix *p = (void*)&odata[-4]; - uint32_t valid = ntohl(p->valid); - if (valid != UINT32_MAX) - p->valid = (valid < elapsed) ? 0 : htonl(valid - elapsed); - - uint32_t pref = ntohl(p->preferred); - if (pref != UINT32_MAX) - p->preferred = (pref < elapsed) ? 0 : htonl(pref - elapsed); - } + uint16_t otype, olen; - // Decrease valid and preferred lifetime of addresses - dhcpv6_for_each_option(ia_na, ia_na + ia_na_len, otype, olen, odata) { - struct dhcpv6_ia_addr *p = (void*)&odata[-4]; - uint32_t valid = ntohl(p->valid); - if (valid != UINT32_MAX) - p->valid = (valid < elapsed) ? 0 : htonl(valid - elapsed); + static time_t last_update = 0; + time_t now = odhcp6c_get_milli_time() / 1000; + + uint32_t elapsed = now - last_update; + odhcp6c_expire(); + + if (orig == DHCPV6_MSG_UNKNOWN) { + t1 -= elapsed; + t2 -= elapsed; + t3 -= elapsed; + + if (t1 < 0) + t1 = 0; + + if (t2 < 0) + t2 = 0; + + if (t3 < 0) + t3 = 0; + } else { + t1 = t2 = t3 = 86400; + } - uint32_t pref = ntohl(p->preferred); - if (pref != UINT32_MAX) - p->preferred = (pref < elapsed) ? 0 : htonl(pref - elapsed); + if (opt) { + odhcp6c_clear_state(STATE_DNS); + odhcp6c_clear_state(STATE_SEARCH); + odhcp6c_clear_state(STATE_SNTP_IP); + odhcp6c_clear_state(STATE_SNTP_FQDN); + odhcp6c_clear_state(STATE_SIP_IP); + odhcp6c_clear_state(STATE_SIP_FQDN); } // Parse and find all matching IAs @@ -662,11 +658,6 @@ static int dhcpv6_handle_reply(_unused enum dhcpv6_msg orig, if (l_t2 > 0 && t2 > l_t2) t2 = l_t2; - // Always report update in case we have IA_PDs so that - // the state-script is called with updated times - if (otype == DHCPV6_OPT_IA_PD && request_prefix) - have_update = true; - time_t n = dhcpv6_parse_ia(&ia_hdr[1], odata + olen); if (n < t1) @@ -713,56 +704,18 @@ static int dhcpv6_handle_reply(_unused enum dhcpv6_msg orig, } } - if (opt) { - have_update |= odhcp6c_commit_state(STATE_DNS, dns_len); - have_update |= odhcp6c_commit_state(STATE_SEARCH, search_len); - have_update |= odhcp6c_commit_state(STATE_SNTP_IP, - sntp_ip_len); - have_update |= odhcp6c_commit_state(STATE_SNTP_FQDN, - sntp_dns_len); - have_update |= odhcp6c_commit_state(STATE_SIP_IP, sip_ip_len); - have_update |= odhcp6c_commit_state(STATE_SIP_FQDN, sip_fqdn_len); - size_t new_ia_pd_len, new_ia_na_len; - odhcp6c_get_state(STATE_IA_PD, &new_ia_pd_len); - odhcp6c_get_state(STATE_IA_NA, &new_ia_na_len); - have_update |= (new_ia_pd_len != ia_pd_len) || - (new_ia_na_len != ia_na_len); - } - - // Delete prefixes with 0 valid-time - ia_pd = odhcp6c_get_state(STATE_IA_PD, &ia_pd_len); - ia_end = ia_pd + ia_pd_len; - dhcpv6_for_each_option(ia_pd, ia_end, otype, olen, odata) { - struct dhcpv6_ia_prefix *p = (void*)&odata[-4]; - while (!p->valid) { - ia_end = ia_pd + odhcp6c_remove_state(STATE_IA_PD, - (uint8_t*)p - ia_pd, olen + 4); - have_update = true; - } - } - - - // Delete addresses with 0 valid-time - ia_na = odhcp6c_get_state(STATE_IA_NA, &ia_na_len); - ia_end = ia_na + ia_na_len; - dhcpv6_for_each_option(ia_na, ia_end, otype, olen, odata) { - struct dhcpv6_ia_addr *p = (void*)&odata[-4]; - while (!p->valid) { - ia_end = ia_na + odhcp6c_remove_state(STATE_IA_NA, - (uint8_t*)p - ia_na, olen + 4); - have_update = true; - } - } - - return have_update; + return true; } static time_t dhcpv6_parse_ia(void *opt, void *end) { uint32_t timeout = UINT32_MAX; // Minimum timeout - uint16_t otype, olen, stype, slen; - uint8_t *odata, *sdata; + uint16_t otype, olen; + uint8_t *odata; + + struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, + 0, 0, IN6ADDR_ANY_INIT, 0, 0}; // Update address IA dhcpv6_for_each_option(opt, end, otype, olen, odata) { @@ -771,71 +724,35 @@ static time_t dhcpv6_parse_ia(void *opt, void *end) if (olen + 4U < sizeof(*prefix)) continue; - olen = sizeof(*prefix); // Normalize length - uint32_t valid = ntohl(prefix->valid); - uint32_t pref = ntohl(prefix->preferred); + entry.valid = ntohl(prefix->valid); + entry.preferred = ntohl(prefix->preferred); - if (pref > valid) + if (entry.preferred > entry.valid) continue; - // Search matching IA - struct dhcpv6_ia_prefix *local = NULL; - size_t pd_len; - uint8_t *pd = odhcp6c_get_state(STATE_IA_PD, &pd_len); - dhcpv6_for_each_option(pd, pd + pd_len, - stype, slen, sdata) - if (!memcmp(sdata + 8, odata + 8, - sizeof(local->addr) + 1)) - local = (void*)&sdata[-4]; - - if (local) { // Already know that IA - local->preferred = prefix->preferred; - local->valid = prefix->valid; - } else { // New IA - odhcp6c_add_state(STATE_IA_PD, prefix, olen); - } + entry.length = prefix->prefix; + entry.target = prefix->addr; - if (timeout > valid) - timeout = valid; + odhcp6c_update_entry(STATE_IA_PD, &entry); } else if (otype == DHCPV6_OPT_IA_ADDR) { struct dhcpv6_ia_addr *addr = (void*)&odata[-4]; if (olen + 4U < sizeof(*addr)) continue; - olen = sizeof(*addr); // Normalize length - uint32_t pref = ntohl(addr->preferred); - uint32_t valid = ntohl(addr->valid); + entry.preferred = ntohl(addr->preferred); + entry.valid = ntohl(addr->valid); - if (pref > valid) + if (entry.preferred > entry.valid) continue; - // Search matching IA - struct dhcpv6_ia_addr *local = NULL; - size_t na_len; - uint8_t *na = odhcp6c_get_state(STATE_IA_NA, &na_len); - dhcpv6_for_each_option(na, na + na_len, - stype, slen, sdata) - if (!memcmp(sdata, odata, sizeof(local->addr))) - local = (void*)&sdata[-4]; - + entry.length = 128; + entry.target = addr->addr; - if (local) { // Already know that IA - local->preferred = addr->preferred; - local->valid = addr->valid; - } else { // New IA - odhcp6c_add_state(STATE_IA_NA, addr, olen); - } - - - if (timeout > valid) - timeout = valid; - - if (set_rtnetlink_addr(ifindex, &addr->addr, - pref, valid) == -EADDRNOTAVAIL) { - dhcpv6_request(DHCPV6_MSG_DECLINE); - raise(SIGUSR2); - } + odhcp6c_update_entry(STATE_IA_NA, &entry); } + + if (entry.valid > 0 && timeout > entry.valid) + timeout = entry.valid; } return timeout; diff --git a/src/odhcp6c.c b/src/odhcp6c.c index 4e50758..6266aa4 100644 --- a/src/odhcp6c.c +++ b/src/odhcp6c.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include "odhcp6c.h" +#include "ra.h" static void sighandler(int signal); @@ -38,6 +40,8 @@ static uint8_t *state_data[_STATE_MAX] = {NULL}; static size_t state_len[_STATE_MAX] = {0}; static volatile int do_signal = 0; +static int urandom_fd = -1; +static bool bound = false; int main(_unused int argc, char* const argv[]) @@ -122,12 +126,14 @@ int main(_unused int argc, char* const argv[]) if (help || !ifname) return usage(); - if (init_dhcpv6(ifname, request_pd) || init_rtnetlink() || + if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 || + init_dhcpv6(ifname, request_pd) || ra_init(ifname) || script_init(script, ifname)) { syslog(LOG_ERR, "failed to initialize: %s", strerror(errno)); return 3; } + signal(SIGIO, sighandler); signal(SIGHUP, sighandler); signal(SIGINT, sighandler); signal(SIGCHLD, sighandler); @@ -170,6 +176,7 @@ int main(_unused int argc, char* const argv[]) odhcp6c_clear_state(STATE_SIP_IP); odhcp6c_clear_state(STATE_SIP_FQDN); dhcpv6_set_ia_na_mode(ia_na_mode); + bound = false; do_signal = 0; int res = dhcpv6_request(DHCPV6_MSG_SOLICIT); @@ -188,6 +195,8 @@ int main(_unused int argc, char* const argv[]) else if (res > 0) script_call("informed"); + bound = true; + if (dhcpv6_poll_reconfigure() > 0) script_call("informed"); } @@ -200,6 +209,7 @@ int main(_unused int argc, char* const argv[]) continue; script_call("bound"); + bound = true; while (do_signal == 0 || do_signal == SIGUSR1) { // Renew Cycle @@ -254,15 +264,14 @@ int main(_unused int argc, char* const argv[]) odhcp6c_get_state(STATE_SERVER_ID, &server_id_len); // Add all prefixes to lost prefixes - odhcp6c_clear_state(STATE_IA_PD); + bound = false; script_call("unbound"); - // Remove assigned addresses - if (ia_na_len > 0) - dhcpv6_remove_addrs(); - if (server_id_len > 0 && (ia_pd_len > 0 || ia_na_len > 0)) dhcpv6_request(DHCPV6_MSG_RELEASE); + + odhcp6c_clear_state(STATE_IA_NA); + odhcp6c_clear_state(STATE_IA_PD); } script_call("stopped"); @@ -314,8 +323,18 @@ static uint8_t* odhcp6c_resize_state(enum odhcp6c_state state, ssize_t len) } -bool odhcp6c_signal_is_pending(void) +bool odhcp6c_signal_process(void) { + if (do_signal == SIGIO) { + do_signal = 0; + bool updated = ra_process(); + updated |= ra_rtnl_process(); + if (updated && bound) { + odhcp6c_expire(); + script_call("ra-updated"); + } + } + return do_signal != 0; } @@ -346,23 +365,92 @@ size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len) } -bool odhcp6c_commit_state(enum odhcp6c_state state, size_t old_len) +void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len) +{ + *len = state_len[state]; + return state_data[state]; +} + + +struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new) { - size_t new_len = state_len[state] - old_len; - uint8_t *old_data = state_data[state], *new_data = old_data + old_len; - bool upd = new_len != old_len || memcmp(old_data, new_data, new_len); + size_t len, cmplen = offsetof(struct odhcp6c_entry, target) + new->length / 8; + struct odhcp6c_entry *start = odhcp6c_get_state(state, &len); + struct odhcp6c_entry *x = NULL; - memmove(old_data, new_data, new_len); - odhcp6c_resize_state(state, -old_len); + for (struct odhcp6c_entry *c = start; !x && c < &start[len/sizeof(*c)]; ++c) + if (!memcmp(c, new, cmplen)) + return c; - return upd; + return NULL; } -void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len) +void odhcp6c_update_entry_safe(enum odhcp6c_state state, const struct odhcp6c_entry *new, uint32_t safe) { - *len = state_len[state]; - return state_data[state]; + size_t len; + struct odhcp6c_entry *x = odhcp6c_find_entry(state, new); + struct odhcp6c_entry *start = odhcp6c_get_state(state, &len); + + if (x && x->valid > new->valid && new->valid < safe) + return; + + if (new->valid > 0) { + if (x) + *x = *new; + else + odhcp6c_add_state(state, new, sizeof(*new)); + } else if (x) { + odhcp6c_remove_state(state, (x - start) * sizeof(*x), sizeof(*x)); + } +} + + +void odhcp6c_update_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new) +{ + odhcp6c_update_entry_safe(state, new, 0); +} + + +static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed) +{ + size_t len; + struct odhcp6c_entry *start = odhcp6c_get_state(state, &len); + for (struct odhcp6c_entry *c = start; c < &start[len / sizeof(*c)]; ++c) { + if (c->preferred < elapsed) + c->preferred = 0; + else if (c->preferred != UINT32_MAX) + c->preferred -= elapsed; + + if (c->valid < elapsed) + c->valid = 0; + else if (c->valid != UINT32_MAX) + c->valid -= elapsed; + + if (!c->valid) + odhcp6c_remove_state(state, (c - start) * sizeof(*c), sizeof(*c)); + } +} + + +void odhcp6c_expire(void) +{ + static time_t last_update = 0; + time_t now = odhcp6c_get_milli_time() / 1000; + + uint32_t elapsed = now - last_update; + last_update = now; + + odhcp6c_expire_list(STATE_RA_PREFIX, elapsed); + odhcp6c_expire_list(STATE_RA_ROUTE, elapsed); + odhcp6c_expire_list(STATE_IA_NA, elapsed); + odhcp6c_expire_list(STATE_IA_PD, elapsed); +} + + +void odhcp6c_random(void *buf, size_t len) +{ + read(urandom_fd, buf, len); } @@ -374,6 +462,8 @@ static void sighandler(int signal) do_signal = SIGUSR1; else if (signal == SIGUSR2) do_signal = SIGUSR2; + else if (signal == SIGIO) + do_signal = SIGIO; else do_signal = SIGTERM; } diff --git a/src/odhcp6c.h b/src/odhcp6c.h index 1a5aabc..440381a 100644 --- a/src/odhcp6c.h +++ b/src/odhcp6c.h @@ -80,7 +80,7 @@ enum dhcpv6_status { }; typedef int(reply_handler)(enum dhcpv6_msg orig, - const void *opt, const void *end, uint32_t elapsed); + const void *opt, const void *end); // retransmission strategy struct dhcpv6_retx { @@ -89,7 +89,7 @@ struct dhcpv6_retx { uint16_t max_timeo; char name[8]; reply_handler *handler_reply; - int(*handler_finish)(uint32_t elapsed); + int(*handler_finish)(void); }; @@ -162,6 +162,8 @@ enum odhcp6c_state { STATE_SNTP_FQDN, STATE_SIP_IP, STATE_SIP_FQDN, + STATE_RA_ROUTE, + STATE_RA_PREFIX, _STATE_MAX }; @@ -187,11 +189,20 @@ enum odhcp6c_ia_mode { }; +struct odhcp6c_entry { + struct in6_addr router; + uint16_t length; + int16_t priority; + struct in6_addr target; + uint32_t valid; + uint32_t preferred; +}; + + int init_dhcpv6(const char *ifname, int request_pd); void dhcpv6_set_ia_na_mode(enum odhcp6c_ia_mode mode); int dhcpv6_request(enum dhcpv6_msg type); int dhcpv6_poll_reconfigure(void); -void dhcpv6_remove_addrs(void); int init_rtnetlink(void); int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr, @@ -201,13 +212,19 @@ int script_init(const char *path, const char *ifname); ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src); void script_call(const char *status); +bool odhcp6c_signal_process(void); +uint64_t odhcp6c_get_milli_time(void); +void odhcp6c_random(void *buf, size_t len); // State manipulation -bool odhcp6c_signal_is_pending(void); -uint64_t odhcp6c_get_milli_time(void); void odhcp6c_clear_state(enum odhcp6c_state state); void odhcp6c_add_state(enum odhcp6c_state state, const void *data, size_t len); size_t odhcp6c_remove_state(enum odhcp6c_state state, size_t offset, size_t len); -bool odhcp6c_commit_state(enum odhcp6c_state state, size_t old_len); void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len); +// Entry manipulation +struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new); +void odhcp6c_update_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new); +void odhcp6c_update_entry_safe(enum odhcp6c_state state, const struct odhcp6c_entry *new, uint32_t safe); + +void odhcp6c_expire(void); diff --git a/src/ra.c b/src/ra.c new file mode 100644 index 0000000..6874601 --- /dev/null +++ b/src/ra.c @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +#include "odhcp6c.h" +#include "ra.h" + + +static int sock = -1, rtnl_sock = -1; +static unsigned if_index = 0; +static char if_name[IF_NAMESIZE] = {0}; +static volatile int rs_attempt = 1; +static struct in6_addr lladdr = IN6ADDR_ANY_INIT; + +static void ra_send_rs(int signal __attribute__((unused))); + +int ra_init(const char *ifname) +{ + sock = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC, IPPROTO_ICMPV6); + if_index = if_nametoindex(ifname); + strncpy(if_name, ifname, sizeof(if_name) - 1); + + // Filter ICMPv6 package types + struct icmp6_filter filt; + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); + setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)); + + // Bind to all-nodes + struct ipv6_mreq an = {ALL_IPV6_NODES, if_index}; + setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &an, sizeof(an)); + + // Let the kernel compute our checksums + int val = 2; + setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &val, sizeof(val)); + + // This is required by RFC 4861 + val = 255; + setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)); + + // Bind to one device + setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); + + // Add async-mode + const pid_t ourpid = getpid(); + fcntl(sock, F_SETOWN, ourpid); + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_ASYNC); + + // Get LL-addr + FILE *fp = fopen("/proc/net/if_inet6", "r"); + if (fp) { + char addrbuf[33], ifbuf[16]; + while (fscanf(fp, "%32s %*x %*x %*x %*x %15s", addrbuf, ifbuf) == 2) { + if (!strcmp(ifbuf, if_name)) { + script_unhexlify((uint8_t*)&lladdr, sizeof(lladdr), addrbuf); + break; + } + } + fclose(fp); + } + + // Open rtnetlink socket + rtnl_sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + struct sockaddr_nl rtnl_kernel = { .nl_family = AF_NETLINK }; + if (connect(rtnl_sock, (struct sockaddr*)&rtnl_kernel, sizeof(rtnl_kernel))) + return -1; + uint32_t group = RTNLGRP_IPV6_IFADDR; + setsockopt(rtnl_sock, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); + + // Add async-mode + fcntl(rtnl_sock, F_SETOWN, ourpid); + fcntl(rtnl_sock, F_SETFL, fcntl(rtnl_sock, F_GETFL) | O_ASYNC); + + // Send RS + signal(SIGALRM, ra_send_rs); + ra_send_rs(SIGALRM); + + return 0; +} + + +static void ra_send_rs(int signal __attribute__((unused))) +{ + const struct icmp6_hdr rs = {ND_ROUTER_SOLICIT, 0, 0, {{0}}}; + const struct sockaddr_in6 dest = {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, if_index}; + sendto(sock, &rs, sizeof(rs), MSG_DONTWAIT, (struct sockaddr*)&dest, sizeof(dest)); + + if (++rs_attempt < 3) + alarm(4); +} + + +static int16_t pref_to_priority(uint8_t flags) +{ + flags = (flags >> 3) & 0x03; + return (flags == 0x00) ? 1024 : (flags == 0x01) ? 512 : + (flags == 0x11) ? 2048 : -1; +} + + +static void update_proc(const char *sect, const char *opt, uint32_t value) +{ + char buf[64]; + snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/%s/%s/%s", sect, if_name, opt); + + int fd = open(buf, O_WRONLY); + write(fd, buf, snprintf(buf, sizeof(buf), "%u", value)); + close(fd); +} + + +static bool ra_deduplicate(const struct in6_addr *any, uint8_t length) +{ + struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, length, 0, *any, 0, 0}; + struct odhcp6c_entry *x = odhcp6c_find_entry(STATE_RA_PREFIX, &entry); + if (x) { + odhcp6c_random(&x->target.s6_addr32[2], 2 * sizeof(uint32_t)); + } else if (odhcp6c_find_entry(STATE_IA_NA, &entry)) { + dhcpv6_request(DHCPV6_MSG_DECLINE); + raise(SIGUSR2); + } + + return !!x; +} + + +bool ra_rtnl_process(void) +{ + bool found = false; + uint8_t buf[8192]; + while (true) { + ssize_t len = recv(rtnl_sock, buf, sizeof(buf), MSG_DONTWAIT); + if (len < 0) + break; + + for (struct nlmsghdr *nh = (struct nlmsghdr*)buf; NLMSG_OK(nh, len); + nh = NLMSG_NEXT(nh, len)) { + struct ifaddrmsg *ifa = NLMSG_DATA(nh); + struct in6_addr *addr = NULL; + if (nh->nlmsg_type != RTM_NEWADDR || NLMSG_PAYLOAD(nh, 0) < sizeof(*ifa) || + !(ifa->ifa_flags & IFA_F_DADFAILED) || + ifa->ifa_index != if_index) + continue; + + ssize_t alen = NLMSG_PAYLOAD(nh, sizeof(*ifa)); + for (struct rtattr *rta = (struct rtattr*)&ifa[1]; RTA_OK(rta, alen); + rta = RTA_NEXT(rta, alen)) + if (rta->rta_type == IFA_LOCAL && RTA_PAYLOAD(rta) >= sizeof(*addr)) + addr = RTA_DATA(rta); + + if (addr) + found |= ra_deduplicate(addr, ifa->ifa_prefixlen); + } + } + return found; +} + + +bool ra_process(void) +{ + bool found = false; + uint8_t buf[1500]; + struct nd_router_advert *adv = (struct nd_router_advert*)buf; + struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0}; + const struct in6_addr any = IN6ADDR_ANY_INIT; + odhcp6c_expire(); + + while (true) { + struct sockaddr_in6 from; + socklen_t from_len = sizeof(from); + ssize_t len = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, &from, &from_len); + if (len < 0) + break; + else if (len < (ssize_t)sizeof(*adv)) + continue; + + // Stop sending solicits + if (rs_attempt > 0) { + alarm(0); + rs_attempt = 0; + } + + found = true; + + // Parse default route + entry.router = from.sin6_addr; + entry.priority = pref_to_priority(adv->nd_ra_flags_reserved); + if (entry.priority < 0) + entry.priority = pref_to_priority(0); + entry.valid = ntohs(adv->nd_ra_router_lifetime); + entry.preferred = entry.valid; + odhcp6c_update_entry(STATE_RA_ROUTE, &entry); + + // Parse ND parameters + if (adv->nd_ra_reachable) + update_proc("neigh", "base_reachable_time_ms", ntohl(adv->nd_ra_reachable)); + + if (adv->nd_ra_retransmit) + update_proc("neigh", "retrans_time_ms", ntohl(adv->nd_ra_retransmit)); + + // Evaluate options + struct icmpv6_opt *opt; + icmpv6_for_each_option(opt, &adv[1], &buf[len]) { + if (opt->type == ND_OPT_MTU) { + update_proc("conf", "mtu", ntohl(*((uint32_t*)&opt->data[2]))); + } else if (opt->type == ND_OPT_ROUTE_INFORMATION && opt->len <= 3) { + entry.router = from.sin6_addr; + entry.target = any; + entry.priority = pref_to_priority(opt->data[1]); + entry.length = opt->data[0]; + entry.valid = ntohl(*((uint32_t*)&opt->data[2])); + memcpy(&entry.target, &opt->data[6], (opt->len - 1) * 8); + + if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target) + || IN6_IS_ADDR_LOOPBACK(&entry.target) + || IN6_IS_ADDR_MULTICAST(&entry.target)) + continue; + + if (entry.priority > 0) + odhcp6c_update_entry(STATE_RA_ROUTE, &entry); + } else if (opt->type == ND_OPT_PREFIX_INFORMATION && opt->len == 4) { + struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info*)opt; + entry.router = any; + entry.target = pinfo->nd_opt_pi_prefix; + entry.priority = 0; + entry.length = pinfo->nd_opt_pi_prefix_len; + entry.valid = ntohl(pinfo->nd_opt_pi_valid_time); + entry.preferred = ntohl(pinfo->nd_opt_pi_preferred_time); + + if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target) + || IN6_IS_ADDR_LOOPBACK(&entry.target) + || IN6_IS_ADDR_MULTICAST(&entry.target) + || entry.valid < entry.preferred) + continue; + + if (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) + odhcp6c_update_entry_safe(STATE_RA_ROUTE, &entry, 7201); + + if (!(pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) || + pinfo->nd_opt_pi_prefix_len != 64) + continue; + + entry.target.s6_addr32[2] = lladdr.s6_addr32[2]; + entry.target.s6_addr32[3] = lladdr.s6_addr32[3]; + + odhcp6c_update_entry_safe(STATE_RA_PREFIX, &entry, 7201); + } + + } + } + return found; +} diff --git a/src/ra.h b/src/ra.h new file mode 100644 index 0000000..37d9573 --- /dev/null +++ b/src/ra.h @@ -0,0 +1,26 @@ +#pragma once + +#define ALL_IPV6_NODES {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}} + +#define ALL_IPV6_ROUTERS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}} + +struct icmpv6_opt { + uint8_t type; + uint8_t len; + uint8_t data[6]; +}; + +#define ND_OPT_ROUTE_INFORMATION 24 + + +#define icmpv6_for_each_option(opt, start, end)\ + for (opt = (struct icmpv6_opt*)(start);\ + (void*)(opt + 1) <= (void*)(end) && opt->len > 0 &&\ + (void*)(opt + opt->len) <= (void*)(end); opt += opt->len) + + +int ra_init(const char *ifname); +bool ra_process(void); +bool ra_rtnl_process(void); diff --git a/src/rtnetlink.c b/src/rtnetlink.c deleted file mode 100644 index 6017aab..0000000 --- a/src/rtnetlink.c +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (C) 2012 Steven Barth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License v2 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "odhcp6c.h" - - -static int sock = -1; -static unsigned seq = 0; - - -// Init rtnetlink socket -int init_rtnetlink(void) -{ - sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); - struct sockaddr_nl rtnl_kernel = { .nl_family = AF_NETLINK }; - if (connect(sock, (struct sockaddr*)&rtnl_kernel, sizeof(rtnl_kernel))) - return -1; - - return 0; -} - - -// CRUD addresses to interface -int set_rtnetlink_addr(int ifindex, const struct in6_addr *addr, - uint32_t pref, uint32_t valid) -{ - int flags = NLM_F_REQUEST | NLM_F_ACK; - int cmd = RTM_DELADDR; - - if (valid) { - flags |= NLM_F_CREATE | NLM_F_REPLACE; - cmd = RTM_NEWADDR; - } - - struct { - struct nlmsghdr nhm; - struct ifaddrmsg ifa; - struct rtattr rta_addr; - struct in6_addr addr; - struct rtattr rta_local; - struct in6_addr local; - struct rtattr rta_info; - struct ifa_cacheinfo info; - } req = { - {sizeof(req), cmd, flags, ++seq, 0}, - {AF_INET6, 128, 0, RT_SCOPE_UNIVERSE, ifindex}, - {sizeof(req.rta_addr) + sizeof(req.addr), IFA_ADDRESS}, - *addr, - {sizeof(req.rta_local) + sizeof(req.local), IFA_LOCAL}, - *addr, - {sizeof(req.rta_info) + sizeof(req.info), IFA_CACHEINFO}, - {pref, valid, 0, 0} - }; - send(sock, &req, sizeof(req), 0); - - struct { - struct nlmsghdr nhm; - struct nlmsgerr err; - } reply; - recv(sock, &reply, sizeof(reply), 0); - - char buf[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, addr, buf, sizeof(buf)); - syslog(LOG_WARNING, "%s address %s/128 for iface %i: %s", - (valid) ? "assigning" : "removing", buf, - ifindex, strerror(-reply.err.error)); - - if (reply.err.error < 0 || valid == 0) - return reply.err.error; - - // Check for duplicate addresses - struct timespec ts = {1, 0}; - nanosleep(&ts, NULL); - - req.nhm.nlmsg_type = RTM_GETADDR; - req.nhm.nlmsg_seq = ++seq; - req.nhm.nlmsg_flags = NLM_F_REQUEST; - send(sock, &req, sizeof(req), 0); - - struct { - struct nlmsghdr nhm; - struct ifaddrmsg ifa; - uint8_t buf[1024]; - } dad_reply; - recv(sock, &dad_reply, sizeof(dad_reply), 0); - - if (dad_reply.nhm.nlmsg_type != RTM_NEWADDR || - (dad_reply.ifa.ifa_flags & IFA_F_DADFAILED)) { - syslog(LOG_WARNING, "Removing duplicate address %s", buf); - set_rtnetlink_addr(ifindex, addr, 0, 0); - return -EADDRNOTAVAIL; - } - return 0; -} diff --git a/src/script.c b/src/script.c index f0d4af2..0e01f3a 100644 --- a/src/script.c +++ b/src/script.c @@ -67,7 +67,7 @@ ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src) } -void script_hexlify(char *dst, const uint8_t *src, size_t len) { +static void script_hexlify(char *dst, const uint8_t *src, size_t len) { for (size_t i = 0; i < len; ++i) { *dst++ = hexdigits[src[i] >> 4]; *dst++ = hexdigits[src[i] & 0x0f]; @@ -129,28 +129,30 @@ static void bin_to_env(uint8_t *opts, size_t len) } -static void prefix_to_env(const char *name, const uint8_t *fqdn, size_t len) +static void entry_to_env(const char *name, const void *data, size_t len) { size_t buf_len = strlen(name); - struct dhcpv6_ia_prefix *p = NULL; - char *buf = realloc(NULL, buf_len + 2 + - (len / sizeof(*p)) * (INET6_ADDRSTRLEN + 32)); + const struct odhcp6c_entry *e = data; + const struct in6_addr any = IN6ADDR_ANY_INIT; + char *buf = realloc(NULL, buf_len + 2 + (len / sizeof(*e)) * 144); memcpy(buf, name, buf_len); buf[buf_len++] = '='; - uint16_t otype, olen; - uint8_t *odata; - dhcpv6_for_each_option(fqdn, &fqdn[len], otype, olen, odata) { - if (otype != DHCPV6_OPT_IA_PREFIX || olen + 4U < sizeof(*p)) - continue; - - p = (struct dhcpv6_ia_prefix*)&odata[-4]; - inet_ntop(AF_INET6, &p->addr, &buf[buf_len], INET6_ADDRSTRLEN); + for (size_t i = 0; i < len / sizeof(*e); ++i) { + inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN); buf_len += strlen(&buf[buf_len]); - buf_len += snprintf(&buf[buf_len], 32, "/%hhu,%u,%u ", - p->prefix, ntohl(p->preferred), - ntohl(p->valid)); + buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length); + if (!IN6_ARE_ADDR_EQUAL(&any, &e[i].router)) { + buf[buf_len++] = '@'; + inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN); + buf_len += strlen(&buf[buf_len]); + } + buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid); + if (e[i].priority) + buf_len += snprintf(&buf[buf_len], 12, ",%u", e[i].priority); + buf[buf_len++] = ' '; } + buf[buf_len - 1] = '\0'; putenv(buf); } @@ -170,8 +172,11 @@ void script_call(const char *status) struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len); uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len); - size_t prefix_len; + size_t prefix_len, address_len, ra_pref_len, ra_route_len; uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len); + uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len); + uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len); + uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len); // Don't set environment before forking, because env is leaky. if (fork() == 0) { @@ -182,7 +187,10 @@ void script_call(const char *status) fqdn_to_env("SNTP_FQDN", sntp_dns, sntp_dns_len); fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len); bin_to_env(custom, custom_len); - prefix_to_env("PREFIXES", prefix, prefix_len); + entry_to_env("PREFIXES", prefix, prefix_len); + entry_to_env("ADDRESSES", address, address_len); + entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len); + entry_to_env("RA_ROUTES", ra_route, ra_route_len); argv[2] = (char*)status; execv(argv[0], argv); -- 2.39.2