From 0ff39e3a124125914d2142d7127eab3a485ab34a Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Mon, 27 Jul 2015 23:21:57 +0200 Subject: [PATCH] ra/dhcpv6: unify address change handling --- src/dhcpv6-ia.c | 132 ++++++++++++++++++++------------------------------------ src/dhcpv6.h | 3 ++ src/ndp.c | 56 +++++++++++++++++++++++- src/router.c | 55 +++++++++++------------ 4 files changed, 130 insertions(+), 116 deletions(-) diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index 6ea49b0..c5f8783 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -34,7 +34,6 @@ #include -static void update(struct interface *iface); static void reconf_timer(struct uloop_timeout *event); static struct uloop_timeout reconf_event = {.cb = reconf_timer}; static uint32_t serial = 0; @@ -89,8 +88,6 @@ int setup_dhcpv6_ia_interface(struct interface *iface, bool enable) list_add(&border->head, &iface->ia_assignments); } - update(iface); - // Parse static entries struct lease *lease; list_for_each_entry(lease, &leases, head) { @@ -556,106 +553,73 @@ static bool assign_na(struct interface *iface, struct dhcpv6_assignment *assign) return false; } - -static int prefixcmp(const void *va, const void *vb) +void dhcpv6_ia_preupdate(struct interface *iface) { - const struct odhcpd_ipaddr *a = va, *b = vb; - uint32_t a_pref = ((a->addr.s6_addr[0] & 0xfe) != 0xfc) ? a->preferred : 1; - uint32_t b_pref = ((b->addr.s6_addr[0] & 0xfe) != 0xfc) ? b->preferred : 1; - return (a_pref < b_pref) ? 1 : (a_pref > b_pref) ? -1 : 0; -} + if (iface->dhcpv6 != RELAYD_SERVER) + return; + struct dhcpv6_assignment *c, *border = list_last_entry( + &iface->ia_assignments, struct dhcpv6_assignment, head); + list_for_each_entry(c, &iface->ia_assignments, head) + if (c != border && !iface->managed) + apply_lease(iface, c, false); +} -static void update(struct interface *iface) +void dhcpv6_ia_postupdate(struct interface *iface, time_t now) { - struct odhcpd_ipaddr addr[8]; - memset(addr, 0, sizeof(addr)); - int len = odhcpd_get_interface_addresses(iface->ifindex, addr, 8); - - if (len < 0) + if (iface->dhcpv6 != RELAYD_SERVER) return; - qsort(addr, len, sizeof(*addr), prefixcmp); - - time_t now = odhcpd_time(); int minprefix = -1; - - for (int i = 0; i < len; ++i) { - if (addr[i].preferred > 0 && addr[i].prefix < 64 && - addr[i].prefix > minprefix) - minprefix = addr[i].prefix; - - addr[i].addr.s6_addr32[3] = 0; - - if (addr[i].preferred < UINT32_MAX - now) - addr[i].preferred += now; - - if (addr[i].valid < UINT32_MAX - now) - addr[i].valid += now; + for (size_t i = 0; i < iface->ia_addr_len; ++i) { + if (iface->ia_addr[i].preferred > now && + iface->ia_addr[i].prefix < 64 && + iface->ia_addr[i].prefix > minprefix) + minprefix = iface->ia_addr[i].prefix; } - struct dhcpv6_assignment *border = list_last_entry(&iface->ia_assignments, struct dhcpv6_assignment, head); + struct dhcpv6_assignment *border = list_last_entry( + &iface->ia_assignments, struct dhcpv6_assignment, head); if (minprefix > 32 && minprefix <= 64) border->assigned = 1U << (64 - minprefix); else border->assigned = 0; - bool change = len != (int)iface->ia_addr_len; - for (int i = 0; !change && i < len; ++i) - if (addr[i].addr.s6_addr32[0] != iface->ia_addr[i].addr.s6_addr32[0] || - addr[i].addr.s6_addr32[1] != iface->ia_addr[i].addr.s6_addr32[1] || - (addr[i].preferred > 0) != (iface->ia_addr[i].preferred > 0) || - (addr[i].valid > (uint32_t)now + 7200) != - (iface->ia_addr[i].valid > (uint32_t)now + 7200)) - change = true; - - if (change) { - struct dhcpv6_assignment *c; - list_for_each_entry(c, &iface->ia_assignments, head) - if (c != border && !iface->managed) - apply_lease(iface, c, false); - } - - memcpy(iface->ia_addr, addr, len * sizeof(*addr)); - iface->ia_addr_len = len; + struct list_head reassign = LIST_HEAD_INIT(reassign); + struct dhcpv6_assignment *c, *d; + list_for_each_entry_safe(c, d, &iface->ia_assignments, head) { + if (c->clid_len == 0 || c->valid_until < now || c->managed_size) + continue; - if (change) { // Addresses / prefixes have changed - struct list_head reassign = LIST_HEAD_INIT(reassign); - struct dhcpv6_assignment *c, *d; - list_for_each_entry_safe(c, d, &iface->ia_assignments, head) { - if (c->clid_len == 0 || c->valid_until < now || c->managed_size) - continue; + if (c->length < 128 && c->assigned >= border->assigned && c != border) + list_move(&c->head, &reassign); + else if (c != border) + apply_lease(iface, c, true); - if (c->length < 128 && c->assigned >= border->assigned && c != border) - list_move(&c->head, &reassign); - else if (c != border) - apply_lease(iface, c, true); - - if (c->accept_reconf && c->reconf_cnt == 0) { - c->reconf_cnt = 1; - c->reconf_sent = now; - send_reconf(iface, c); - - // Leave all other assignments of that client alone - struct dhcpv6_assignment *a; - list_for_each_entry(a, &iface->ia_assignments, head) - if (a != c && a->clid_len == c->clid_len && - !memcmp(a->clid_data, c->clid_data, a->clid_len)) - c->reconf_cnt = INT_MAX; - } + if (c->accept_reconf && c->reconf_cnt == 0) { + c->reconf_cnt = 1; + c->reconf_sent = now; + send_reconf(iface, c); + + // Leave all other assignments of that client alone + struct dhcpv6_assignment *a; + list_for_each_entry(a, &iface->ia_assignments, head) + if (a != c && a->clid_len == c->clid_len && + !memcmp(a->clid_data, c->clid_data, a->clid_len)) + c->reconf_cnt = INT_MAX; } + } - while (!list_empty(&reassign)) { - c = list_first_entry(&reassign, struct dhcpv6_assignment, head); - list_del(&c->head); - if (!assign_pd(iface, c)) { - c->assigned = 0; - list_add(&c->head, &iface->ia_assignments); - } + while (!list_empty(&reassign)) { + c = list_first_entry(&reassign, struct dhcpv6_assignment, head); + list_del(&c->head); + if (!assign_pd(iface, c)) { + c->assigned = 0; + list_add(&c->head, &iface->ia_assignments); } - - dhcpv6_write_statefile(); } + + dhcpv6_write_statefile(); } @@ -1009,8 +973,6 @@ ssize_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface, if (!clid_data || !clid_len || clid_len > 130) goto out; - update(iface); - struct dhcpv6_assignment *first = NULL; dhcpv6_for_each_option(start, end, otype, olen, odata) { bool is_pd = (otype == DHCPV6_OPT_IA_PD); diff --git a/src/dhcpv6.h b/src/dhcpv6.h index 09aa6f1..d9f42c8 100644 --- a/src/dhcpv6.h +++ b/src/dhcpv6.h @@ -14,6 +14,7 @@ #pragma once #include +#include "odhcpd.h" #define ALL_DHCPV6_RELAYS {{{0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02}}} @@ -181,3 +182,5 @@ ssize_t dhcpv6_handle_ia(uint8_t *buf, size_t buflen, struct interface *iface, int dhcpv6_ia_init(void); int setup_dhcpv6_ia_interface(struct interface *iface, bool enable); void dhcpv6_write_statefile(void); +void dhcpv6_ia_preupdate(struct interface *iface); +void dhcpv6_ia_postupdate(struct interface *iface, time_t now); diff --git a/src/ndp.c b/src/ndp.c index 2aa55be..7f63f96 100644 --- a/src/ndp.c +++ b/src/ndp.c @@ -29,6 +29,7 @@ #include #include #include "router.h" +#include "dhcpv6.h" #include "ndp.h" @@ -272,6 +273,58 @@ static void setup_route(struct in6_addr *addr, struct interface *iface, bool add odhcpd_setup_route(addr, 128, iface, NULL, 1024, add); } +// compare prefixes +static int prefixcmp(const void *va, const void *vb) +{ + const struct odhcpd_ipaddr *a = va, *b = vb; + uint32_t a_pref = ((a->addr.s6_addr[0] & 0xfe) != 0xfc) ? a->preferred : 1; + uint32_t b_pref = ((b->addr.s6_addr[0] & 0xfe) != 0xfc) ? b->preferred : 1; + return (a_pref < b_pref) ? 1 : (a_pref > b_pref) ? -1 : 0; +} + +// Check address update +static void check_updates(struct interface *iface) +{ + struct odhcpd_ipaddr addr[8] = {{IN6ADDR_ANY_INIT, 0, 0, 0, 0}}; + time_t now = odhcpd_time(); + ssize_t len = odhcpd_get_interface_addresses(iface->ifindex, addr, 8); + + if (len < 0) + return; + + qsort(addr, len, sizeof(*addr), prefixcmp); + + for (int i = 0; i < len; ++i) { + addr[i].addr.s6_addr32[3] = 0; + + if (addr[i].preferred < UINT32_MAX - now) + addr[i].preferred += now; + + if (addr[i].valid < UINT32_MAX - now) + addr[i].valid += now; + } + + bool change = len != (ssize_t)iface->ia_addr_len; + for (ssize_t i = 0; !change && i < len; ++i) + if (!IN6_ARE_ADDR_EQUAL(&addr[i].addr, &iface->ia_addr[i].addr) || + (addr[i].preferred > 0) != (iface->ia_addr[i].preferred > 0) || + addr[i].valid < iface->ia_addr[i].valid || + addr[i].preferred < iface->ia_addr[i].preferred) + change = true; + + if (change) + dhcpv6_ia_preupdate(iface); + + memcpy(iface->ia_addr, addr, len * sizeof(*addr)); + iface->ia_addr_len = len; + + if (change) + dhcpv6_ia_postupdate(iface, now); + + if (change) + raise(SIGUSR1); +} + // Handler for neighbor cache entries from the kernel. This is our source // to learn and unlearn hosts on interfaces. @@ -412,8 +465,7 @@ static void handle_rtnetlink(_unused void *addr, void *data, size_t len, } if (is_addr) { - if (iface->ra == RELAYD_SERVER) - raise(SIGUSR1); // Inform about a change in addresses + check_updates(iface); if (iface->dhcpv6 == RELAYD_SERVER) iface->ia_reconf = true; diff --git a/src/router.c b/src/router.c index 7e666f1..36f6ae7 100644 --- a/src/router.c +++ b/src/router.c @@ -114,7 +114,7 @@ int setup_router_interface(struct interface *iface, bool enable) forward_router_solicitation(iface); } else if (iface->ra == RELAYD_SERVER && !iface->master) { iface->timer_rs.cb = trigger_router_advert; - trigger_router_advert(&iface->timer_rs); + uloop_timeout_set(&iface->timer_rs, 1000); } if (iface->ra == RELAYD_RELAY || (iface->ra == RELAYD_SERVER && !iface->master)) @@ -209,6 +209,7 @@ static bool parse_routes(struct odhcpd_ipaddr *n, ssize_t len) // Router Advert server mode static uint64_t send_router_advert(struct interface *iface, const struct in6_addr *from) { + time_t now = odhcpd_time(); int mtu = odhcpd_get_interface_config(iface->ifname, "mtu"); int hlim = odhcpd_get_interface_config(iface->ifname, "hop_limit"); @@ -219,7 +220,7 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add struct nd_router_advert h; struct icmpv6_opt lladdr; struct nd_opt_mtu mtu; - struct nd_opt_prefix_info prefix[RELAYD_MAX_PREFIXES]; + struct nd_opt_prefix_info prefix[sizeof(iface->ia_addr) / sizeof(*iface->ia_addr)]; } adv = { .h = {{.icmp6_type = ND_ROUTER_ADVERT, .icmp6_code = 0}, 0, 0}, .lladdr = {ND_OPT_SOURCE_LINKADDR, 1, {0}}, @@ -242,21 +243,21 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add odhcpd_get_mac(iface, adv.lladdr.data); // If not currently shutting down - struct odhcpd_ipaddr addrs[RELAYD_MAX_PREFIXES]; + struct odhcpd_ipaddr *addrs = NULL; ssize_t ipcnt = 0; - uint64_t minvalid = UINT64_MAX; - uint64_t maxvalid = 0; + int64_t minvalid = INT64_MAX; + int64_t maxvalid = 0; // If not shutdown if (iface->timer_rs.cb) { - ipcnt = odhcpd_get_interface_addresses(iface->ifindex, - addrs, ARRAY_SIZE(addrs)); + addrs = iface->ia_addr; + ipcnt = iface->ia_addr_len; // Check default route - if (iface->default_router > 1) - adv.h.nd_ra_router_lifetime = htons(iface->default_router); if (parse_routes(addrs, ipcnt)) adv.h.nd_ra_router_lifetime = htons(1); + if (iface->default_router > 1) + adv.h.nd_ra_router_lifetime = htons(iface->default_router); } // Construct Prefix Information options @@ -268,15 +269,9 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add for (ssize_t i = 0; i < ipcnt; ++i) { struct odhcpd_ipaddr *addr = &addrs[i]; - if (addr->prefix > 96) + if (addr->prefix > 96 || addr->valid <= now) continue; // Address not suitable - if (addr->preferred > MaxValidTime) - addr->preferred = MaxValidTime; - - if (addr->valid > MaxValidTime) - addr->valid = MaxValidTime; - struct nd_opt_prefix_info *p = NULL; for (size_t i = 0; i < cnt; ++i) { if (addr->prefix == adv.prefix[i].nd_opt_pi_prefix_len && @@ -292,16 +287,16 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add p = &adv.prefix[cnt++]; } - if (addr->preferred > 0 && - minvalid > 1000ULL * addr->valid) - minvalid = 1000ULL * addr->valid; + if (addr->preferred > now && + minvalid > 1000LL * (addr->valid - now)) + minvalid = 1000LL * (addr->valid - now); - if (maxvalid < 1000ULL * addr->valid) - maxvalid = 1000ULL * addr->valid; + if (maxvalid < 1000LL * (addr->valid - now)) + maxvalid = 1000LL * (addr->valid - now); if (((addr->addr.s6_addr[0] & 0xfe) != 0xfc || iface->default_router) - && ntohs(adv.h.nd_ra_router_lifetime) < addr->valid) - adv.h.nd_ra_router_lifetime = htons(addr->valid); + && ntohs(adv.h.nd_ra_router_lifetime) < addr->valid - now) + adv.h.nd_ra_router_lifetime = htons(addr->valid - now); odhcpd_bmemcpy(&p->nd_opt_pi_prefix, &addr->addr, (iface->ra_advrouter) ? 128 : addr->prefix); @@ -315,11 +310,13 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; if (iface->ra_advrouter) p->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR; - p->nd_opt_pi_valid_time = htonl(addr->valid); - p->nd_opt_pi_preferred_time = htonl(addr->preferred); + p->nd_opt_pi_valid_time = htonl(addr->valid - now); + if (addr->preferred > now) + p->nd_opt_pi_preferred_time = htonl(addr->preferred - now); + - if (addr->preferred > dns_time) { - dns_time = addr->preferred; + if (addr->preferred - now > dns_time) { + dns_time = addr->preferred - now; dns_pref = addr->addr; } } @@ -395,7 +392,7 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add for (ssize_t i = 0; i < ipcnt; ++i) { struct odhcpd_ipaddr *addr = &addrs[i]; - if (addr->dprefix > 64 || addr->dprefix == 0 || + if (addr->dprefix > 64 || addr->dprefix == 0 || addr->valid <= now || (addr->dprefix == 64 && addr->prefix == 64)) { continue; // Address not suitable } else if (addr->dprefix > 32) { @@ -413,7 +410,7 @@ static uint64_t send_router_advert(struct interface *iface, const struct in6_add routes[routes_cnt].flags |= ND_RA_PREF_LOW; else if (iface->route_preference > 0) routes[routes_cnt].flags |= ND_RA_PREF_HIGH; - routes[routes_cnt].lifetime = htonl(addr->valid); + routes[routes_cnt].lifetime = htonl(addr->valid - now); routes[routes_cnt].addr[0] = addr->addr.s6_addr32[0]; routes[routes_cnt].addr[1] = addr->addr.s6_addr32[1]; routes[routes_cnt].addr[2] = 0; -- 2.11.0