If IPv6 is enabled, create the ICMP6 client and handle Router Advertisements received by creating, updating and removing routes per the data in the Router Advertisements. Timeouts aren't handled yet. --- ell/netconfig.c | 248 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 2 deletions(-) diff --git a/ell/netconfig.c b/ell/netconfig.c index 6736913..81cca82 100644 --- a/ell/netconfig.c +++ b/ell/netconfig.c @@ -27,12 +27,16 @@ #include #include #include +#include #include "private.h" #include "useful.h" #include "log.h" #include "dhcp.h" #include "dhcp-private.h" +#include "icmp6.h" +#include "icmp6-private.h" +#include "dhcp6.h" #include "netlink.h" #include "rtnl.h" #include "queue.h" @@ -63,6 +67,8 @@ struct l_netconfig { bool v4_configured; struct l_dhcp_client *dhcp_client; bool v6_configured; + struct l_icmp6_client *icmp6_client; + struct l_dhcp6_client *dhcp6_client; /* These objects, if not NULL, are owned by @addresses and @routes */ struct l_rtnl_address *v4_address; @@ -101,6 +107,11 @@ union netconfig_addr { struct in6_addr v6; }; +static bool netconfig_match_ptr(const void *a, const void *b) +{ + return a == b; +} + static void netconfig_update_cleanup(struct l_netconfig *nc) { l_queue_clear(nc->addresses.added, NULL); @@ -356,6 +367,224 @@ static void netconfig_dhcp_event_handler(struct l_dhcp_client *client, } } +static struct l_rtnl_route *netconfig_find_icmp6_route( + struct l_netconfig *nc, + const uint8_t *gateway, + const struct route_info *dst) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(nc->routes.current); entry; + entry = entry->next) { + struct l_rtnl_route *route = entry->data; + const uint8_t *route_gateway; + const uint8_t *route_dst; + uint8_t route_prefix_len = 0; + + if (l_rtnl_route_get_family(route) != AF_INET6 || + l_rtnl_route_get_protocol(route) != RTPROT_RA) + continue; + + route_gateway = l_rtnl_route_get_gateway_in_addr(route); + if ((gateway || route_gateway) && + (!gateway || !route_gateway || + memcmp(gateway, route_gateway, 16))) + continue; + + route_dst = l_rtnl_route_get_dst_in_addr(route, + &route_prefix_len); + if ((dst || route_prefix_len) && + (!dst || !route_prefix_len || + dst->prefix_len != route_prefix_len || + memcmp(dst->address, route_dst, + (dst->prefix_len + 7) / 8))) + continue; + + return route; + } + + return NULL; +} + +static struct l_rtnl_route *netconfig_add_icmp6_route(struct l_netconfig *nc, + const uint8_t *gateway, + const struct route_info *dst, + uint8_t preference) +{ + struct l_rtnl_route *rt; + char buf1[INET6_ADDRSTRLEN]; + char buf2[INET6_ADDRSTRLEN]; + + if (gateway && !dst) + rt = l_rtnl_route_new_gateway(inet_ntop(AF_INET6, gateway, + buf1, sizeof(buf1))); + else if (dst && !gateway) + rt = l_rtnl_route_new_prefix(inet_ntop(AF_INET6, dst->address, + buf2, sizeof(buf2)), + dst->prefix_len); + else + rt = l_rtnl_route_new_static(inet_ntop(AF_INET6, gateway, + buf1, sizeof(buf1)), + inet_ntop(AF_INET6, + dst->address, + buf2, sizeof(buf2)), + dst->prefix_len); + + if (L_WARN_ON(!rt)) + return NULL; + + l_rtnl_route_set_preference(rt, preference); + l_rtnl_route_set_protocol(rt, RTPROT_RA); + l_rtnl_route_set_priority(rt, nc->route_priority); + l_queue_push_tail(nc->routes.current, rt); + l_queue_push_tail(nc->routes.added, rt); + return rt; +} + +static void netconfig_set_icmp6_route_data(struct l_netconfig *nc, + struct l_rtnl_route *rt, + uint64_t start_time, + uint32_t preferred_lifetime, + uint32_t valid_lifetime, + uint32_t mtu, bool updated) +{ + uint64_t expiry = start_time + valid_lifetime * L_USEC_PER_SEC; + uint64_t old_expiry = l_rtnl_route_get_expiry(rt); + bool differs = false; + + if (mtu != l_rtnl_route_get_mtu(rt)) { + l_rtnl_route_set_mtu(rt, mtu); + differs = true; + } + + /* + * valid_lifetime of 0 from a route_info means the route is being + * removed so we wouldn't be here. valid_lifetime of 0xffffffff + * means no timeout. Check if the lifetime is changing between + * finite and infinite, or two finite values that result in expiry + * time difference of more than a second -- to avoid emitting + * updates for changes resulting only from the valid_lifetime one + * second resolution and RA transmission jitter. As FC4861 + * Section 6.2.7 puts it: "Due to link propagation delays and + * potentially poorly synchronized clocks between the routers such + * comparison SHOULD allow some time skew." The RFC talks about + * routers processing one another's RAs but the same logic applies + * here. + */ + if (valid_lifetime == 0xffffffff) + expiry = 0; + + if ((expiry || old_expiry) && + (!expiry || !old_expiry || + l_time_diff(expiry, old_expiry) > L_USEC_PER_SEC)) { + l_rtnl_route_set_lifetime(rt, valid_lifetime); + l_rtnl_route_set_expiry(rt, expiry); + differs = true; + } + + if (updated && differs && + !l_queue_find(nc->routes.added, netconfig_match_ptr, + rt)) + l_queue_push_tail(nc->routes.updated, rt); +} + +static void netconfig_remove_icmp6_route(struct l_netconfig *nc, + struct l_rtnl_route *route) +{ + l_queue_remove(nc->routes.current, route); + l_queue_push_tail(nc->routes.removed, route); +} + +static void netconfig_icmp6_event_handler(struct l_icmp6_client *client, + enum l_icmp6_client_event event, + void *event_data, + void *user_data) +{ + struct l_netconfig *nc = user_data; + const struct l_icmp6_router *r; + struct l_rtnl_route *default_route; + unsigned int i; + + if (event != L_ICMP6_CLIENT_EVENT_ROUTER_FOUND) + return; + + r = event_data; + + /* + * Note: If this is the first RA received, the l_dhcp6_client + * will have received the event before us and will be acting + * on it by now. + */ + + if (nc->v4_gateway_override) + return; + + /* Process the default gateway information */ + default_route = netconfig_find_icmp6_route(nc, r->address, NULL); + + if (!default_route && r->lifetime) { + default_route = netconfig_add_icmp6_route(nc, r->address, NULL, + r->pref); + if (unlikely(!default_route)) + return; + + /* + * r->lifetime is 16-bit only so there's no risk it gets + * confused for the special 0xffffffff value in + * netconfig_set_icmp6_route_data. + */ + netconfig_set_icmp6_route_data(nc, default_route, r->start_time, + r->lifetime, r->lifetime, + r->mtu, false); + } else if (default_route && r->lifetime) + netconfig_set_icmp6_route_data(nc, default_route, r->start_time, + r->lifetime, r->lifetime, + r->mtu, true); + else if (default_route && !r->lifetime) + netconfig_remove_icmp6_route(nc, default_route); + + /* + * Process the onlink and offlink routes, from the Router + * Advertisement's Prefix Information options and Route + * Information options respectively. + */ + for (i = 0; i < r->n_routes; i++) { + const struct route_info *info = &r->routes[i]; + const uint8_t *gateway = info->onlink ? NULL : r->address; + struct l_rtnl_route *route = + netconfig_find_icmp6_route(nc, gateway, info); + + if (!route && info->valid_lifetime) { + route = netconfig_add_icmp6_route(nc, gateway, info, + info->preference); + if (unlikely(!route)) + continue; + + netconfig_set_icmp6_route_data(nc, route, r->start_time, + info->preferred_lifetime, + info->valid_lifetime, + gateway ? r->mtu : 0, false); + } else if (route && info->valid_lifetime) + netconfig_set_icmp6_route_data(nc, route, r->start_time, + info->preferred_lifetime, + info->valid_lifetime, + gateway ? r->mtu : 0, true); + else if (route && !info->valid_lifetime) + netconfig_remove_icmp6_route(nc, route); + } + + /* + * Note: we may be emitting this before L_NETCONFIG_EVENT_CONFIGURE. + * We should probably instead save the affected routes in separate + * lists and add them to the _CONFIGURE event, suppressing any _UPDATE + * events while nc->v6_configured is false. + */ + if (!l_queue_isempty(nc->routes.added) || + !l_queue_isempty(nc->routes.updated) || + !l_queue_isempty(nc->routes.removed)) + netconfig_emit_event(nc, AF_INET6, L_NETCONFIG_EVENT_UPDATE); +} + LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex) { struct l_netconfig *nc; @@ -378,6 +607,13 @@ LIB_EXPORT struct l_netconfig *l_netconfig_new(uint32_t ifindex) netconfig_dhcp_event_handler, nc, NULL); + nc->dhcp6_client = l_dhcp6_client_new(ifindex); + + nc->icmp6_client = l_dhcp6_client_get_icmp6(nc->dhcp6_client); + l_icmp6_client_add_event_handler(nc->icmp6_client, + netconfig_icmp6_event_handler, + nc, NULL); + return nc; } @@ -398,6 +634,7 @@ LIB_EXPORT void l_netconfig_destroy(struct l_netconfig *netconfig) l_netconfig_set_domain_names_override(netconfig, AF_INET6, NULL); l_dhcp_client_destroy(netconfig->dhcp_client); + l_dhcp6_client_destroy(netconfig->dhcp6_client); l_netconfig_set_event_handler(netconfig, NULL, NULL, NULL); l_queue_destroy(netconfig->addresses.current, NULL); l_queue_destroy(netconfig->addresses.added, NULL); @@ -731,16 +968,22 @@ configure_ipv6: if (!netconfig->v6_enabled) goto done; - if (netconfig->v6_static_addr && !netconfig->do_static_work) { + if (netconfig->v6_static_addr) { /* * We're basically ready to configure the interface * but do this in an idle callback. */ - netconfig->do_static_work = l_idle_create( + if (!netconfig->do_static_work) + netconfig->do_static_work = l_idle_create( netconfig_do_static_config, netconfig, NULL); + + goto done; } + if (!l_dhcp6_client_start(netconfig->dhcp6_client)) + return false; + done: netconfig->started = true; return true; @@ -767,6 +1010,7 @@ LIB_EXPORT void l_netconfig_stop(struct l_netconfig *netconfig) netconfig->v6_address = NULL; l_dhcp_client_stop(netconfig->dhcp_client); + l_dhcp6_client_stop(netconfig->dhcp6_client); } LIB_EXPORT struct l_dhcp_client *l_netconfig_get_dhcp_client( -- 2.32.0