/* route.c * * Copyright (c) 2019-2024 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This code adds border router support to 3rd party HomeKit Routers as part of Appleā€™s commitment to the CHIP project. * * This file contains an implementation of Thread Border Router routing. * The state of the Thread network is tracked, the state of the infrastructure * network is tracked, and policy decisions are made as to what to advertise * on both networks. */ #ifndef LINUX #include #include #include #include #include #include #else #define _GNU_SOURCE #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #if !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING #ifndef LINUX #include #endif // LINUX #endif // !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING #include #include #include #include #include #ifdef IOLOOP_MACOS #include #include #include #include #include #include #include #include #endif // IOLOOP_MACOS #include "srp.h" #include "dns-msg.h" #include "ioloop.h" #include "srp-crypto.h" #include "srp-gw.h" #include "srp-mdns-proxy.h" #include "adv-ctl-server.h" #include "srp-replication.h" # define THREAD_DATA_DIR "/var/lib/openthread" # define THREAD_ULA_FILE THREAD_DATA_DIR "/thread-mesh-ula" #if STUB_ROUTER // Stub Router is true if we're building a Thread Border router or an RA tester. #ifdef THREAD_BORDER_ROUTER #include "cti-services.h" #endif #include "srp-gw.h" #include "srp-proxy.h" #include "srp-mdns-proxy.h" #include "dnssd-proxy.h" #if SRP_FEATURE_NAT64 #include "nat64-macos.h" #endif #include "srp-proxy.h" #include "route.h" #include "nat64.h" #include "state-machine.h" #include "thread-service.h" #include "service-tracker.h" #include "omr-watcher.h" #include "omr-publisher.h" #include "route-tracker.h" #include "icmp.h" #ifdef LINUX #define CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG 1 #endif #ifdef LINUX struct in6_addr in6addr_linklocal_allnodes = {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}}; struct in6_addr in6addr_linklocal_allrouters = {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}}; #endif route_state_t *route_states; #define CONFIGURE_STATIC_INTERFACE_ADDRESSES 1 #define interface_create(route_state, name, iface) interface_create_(route_state, name, iface, __FILE__, __LINE__) interface_t *NULLABLE interface_create_(route_state_t *NONNULL route_state, const char *NONNULL name, int ifindex, const char *NONNULL file, int line); static void interface_beacon_schedule(interface_t *NONNULL interface, unsigned when); static void interface_prefix_configure(struct in6_addr prefix, interface_t *NONNULL interface); static void interface_prefix_evaluate(interface_t *interface); static void start_router_solicit(interface_t *interface); #ifndef RA_TESTER static void attempt_wpan_reconnect(void *context); static void routing_policy_evaluate_all_interfaces(route_state_t *route_state, bool assume_changed); #endif static void routing_policy_evaluate(interface_t *interface, bool assume_changed); static void post_solicit_policy_evaluate(void *context); static void schedule_next_router_probe(interface_t *interface); #ifndef RA_TESTER static void thread_network_startup(route_state_t *route_state); static void thread_network_shutdown(route_state_t *route_state); static void thread_network_shutdown_start(route_state_t *route_state); static void partition_state_reset(route_state_t *route_state); static void partition_utun0_address_changed(route_state_t *route_state, const struct in6_addr *NONNULL addr, enum interface_address_change change); static void partition_utun0_pick_listener_address(route_state_t *route_state); static void partition_got_tunnel_name(route_state_t *route_state); static void partition_remove_service_done(void *UNUSED NULLABLE context, cti_status_t status); static void partition_stop_advertising_service(route_state_t *route_state); static void partition_proxy_listener_ready(void *UNUSED NULLABLE context, uint16_t port); static void partition_maybe_advertise_service(route_state_t *route_state); static void partition_service_set_changed(void *context); static void partition_maybe_enable_services(route_state_t *route_state); static void partition_disable_service(route_state_t *route_state); void partition_discontinue_srp_service(route_state_t *route_state); static void partition_schedule_service_add_wakeup(route_state_t *route_state); static void partition_schedule_anycast_service_add_wakeup(route_state_t *route_state); #endif route_state_t * route_state_create(srp_server_t *server_state, const char *name) { route_state_t *new_route_state = calloc(1, sizeof(*new_route_state)); if (new_route_state == NULL || (new_route_state->name = strdup(name)) == NULL) { free(new_route_state); ERROR("no memory for route state."); return NULL; } #if !defined(RA_TESTER) new_route_state->thread_network_running = false; new_route_state->partition_may_offer_service = false; new_route_state->partition_settle_satisfied = true; new_route_state->current_thread_state = kCTI_NCPState_Uninitialized; #endif new_route_state->have_non_thread_interface = false; new_route_state->ula_serial = 1; new_route_state->have_xpanid_prefix = false; new_route_state->have_thread_prefix = false; new_route_state->config_enable_dhcpv6_prefixes = false; new_route_state->srp_server = server_state; // temporarily communicate the server_state object to route.c with a static assignment. new_route_state->next = route_states; route_states = new_route_state; return new_route_state; } static void interface_finalize(void *context) { interface_t *interface = context; if (interface->name != NULL) { free(interface->name); } if (interface->beacon_wakeup != NULL) { ioloop_wakeup_release(interface->beacon_wakeup); } if (interface->post_solicit_wakeup != NULL) { ioloop_wakeup_release(interface->post_solicit_wakeup); } if (interface->stale_evaluation_wakeup != NULL) { ioloop_wakeup_release(interface->stale_evaluation_wakeup); } if (interface->router_solicit_wakeup != NULL) { ioloop_wakeup_release(interface->router_solicit_wakeup); } if (interface->deconfigure_wakeup != NULL) { ioloop_wakeup_release(interface->deconfigure_wakeup); } if (interface->neighbor_solicit_wakeup != NULL) { ioloop_wakeup_release(interface->neighbor_solicit_wakeup); } if (interface->router_probe_wakeup != NULL) { ioloop_wakeup_release(interface->router_probe_wakeup); } free(interface); } interface_t * interface_create_(route_state_t *route_state, const char *name, int ifindex, const char *file, int line) { interface_t *ret; if (name == NULL) { ERROR("interface_create: missing name"); return NULL; } ret = calloc(1, sizeof(*ret)); if (ret) { RETAIN(ret, interface); ret->name = strdup(name); if (ret->name == NULL) { ERROR("interface_create: no memory for name"); RELEASE(ret, interface); return NULL; } ret->deconfigure_wakeup = ioloop_wakeup_create(); if (ret->deconfigure_wakeup == NULL) { ERROR("No memory for interface deconfigure wakeup on " PUB_S_SRP ".", ret->name); RELEASE(ret, interface); return NULL; } ret->route_state = route_state; ret->index = ifindex; ret->previously_inactive = true; ret->inactive = true; ret->previously_ineligible = true; if (!strcmp(name, "lo") || !strcmp(name, "wpan0")) { ret->ineligible = true; } else { ret->ineligible = false; } } return ret; } void interface_retain_(interface_t *NONNULL interface, const char *file, int line) { RETAIN(interface, interface); } void interface_release_(interface_t *NONNULL interface, const char *file, int line) { RELEASE(interface, interface); } #ifndef RA_TESTER #endif // RA_TESTER static void interface_prefix_deconfigure(void *context) { interface_t *interface = context; INFO("post solicit wakeup."); if (interface->preferred_lifetime != 0) { INFO("PUT PREFIX DECONFIGURE CODE HERE!!"); interface->valid_lifetime = 0; } interface->deprecate_deadline = 0; } static bool want_routing(route_state_t *route_state) { #ifdef RA_TESTER (void)route_state; return true; #else return (route_state->partition_can_provide_routing && route_state->partition_has_xpanid); #endif } static void interface_beacon_send(interface_t *interface, const struct in6_addr *destination) { uint64_t now = ioloop_timenow(); #ifndef RA_TESTER route_state_t *route_state = interface->route_state; #endif INFO(PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP, interface->deprecate_deadline > now ? " ddl>now" : "", #ifdef RA_TESTER "", "", "", #else route_state->partition_can_provide_routing ? " canpr" : " !canpr", route_state->partition_has_xpanid ? " havexp" : " !havexp", interface->suppress_ipv6_prefix ? " suppress" : " !suppress", #endif interface->our_prefix_advertised ? " advert" : " !advert", interface->sent_first_beacon ? "" : " first beacon"); if (interface->deprecate_deadline > now) { // The remaining valid and preferred lifetimes is the time left until the deadline. interface->valid_lifetime = (uint32_t)((interface->deprecate_deadline - now) / 1000); interface->preferred_lifetime = 0; if (interface->valid_lifetime < icmp_listener.unsolicited_interval / 1000) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf); INFO("prefix valid life time is less than the unsolicited interval, stop advertising it " "and prepare to deconfigure the prefix - ifname: " PUB_S_SRP "prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP ", preferred time: %" PRIu32 ", valid time: %" PRIu32 ", unsolicited interval: %" PRIu32, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf), interface->preferred_lifetime, interface->valid_lifetime, icmp_listener.unsolicited_interval / 1000); interface->our_prefix_advertised = false; ioloop_add_wake_event(interface->deconfigure_wakeup, interface, interface_prefix_deconfigure, NULL, interface->valid_lifetime * 1000); } } #ifndef RA_TESTER // If we have been beaconing, and router mode has been disabled, and we don't have // an on-link prefix to advertise, discontinue beaconing. if (want_routing(route_state) || interface->our_prefix_advertised) { #endif // Send an RA. router_advertisement_send(interface, destination); if (destination == &in6addr_linklocal_allnodes) { interface->sent_first_beacon = true; interface->last_beacon = ioloop_timenow();; } #ifndef CONTINUE_ADVERTISING_DURING_DEPRECATION // If we are deprecating, just send the initial deprecation to shorten the preferred lifetime, and then go silent. if (interface->deprecate_deadline > now && !interface->suppress_ipv6_prefix) { INFO("suppressing ipv6 prefix on " PUB_S_SRP, interface->name); interface->suppress_ipv6_prefix = true; } #endif #ifndef RA_TESTER } else { INFO("didn't send: " PUB_S_SRP PUB_S_SRP PUB_S_SRP, route_state->partition_can_provide_routing ? "canpr" : "!canpr", route_state->partition_has_xpanid ? " route_state->xpanid" : " !route_state->xpanid", interface->our_prefix_advertised ? " advert" : " !advert"); } #endif if (destination == &in6addr_linklocal_allnodes) { if (interface->num_beacons_sent < MAX_RA_RETRANSMISSION - 1) { // Schedule a beacon for between 8 and 16 seconds in the future (num_beacons_sent++; } } static void interface_beacon(void *context) { interface_t *interface = context; interface_beacon_send(interface, &in6addr_linklocal_allnodes); } static void interface_beacon_schedule(interface_t *interface, unsigned when) { uint64_t now = ioloop_timenow(); unsigned interval; // Make sure we haven't send an RA too recently. if (when < MIN_DELAY_BETWEEN_RAS && now - interface->last_beacon < MIN_DELAY_BETWEEN_RAS) { when = MIN_DELAY_BETWEEN_RAS; } // Add up to a second of jitter. when += srp_random16() % 1024; interface->next_beacon = now + when; if (interface->beacon_wakeup == NULL) { interface->beacon_wakeup = ioloop_wakeup_create(); if (interface->beacon_wakeup == NULL) { ERROR("Unable to allocate beacon wakeup for " PUB_S_SRP, interface->name); return; } } else { // We can reschedule a beacon for sooner if we get a router solicit; in this case, we // need to cancel the existing beacon wakeup, and if there is none scheduled, this will // be a no-op. ioloop_cancel_wake_event(interface->beacon_wakeup); } if (interface->next_beacon - now > UINT_MAX) { interval = UINT_MAX; } else { interval = (unsigned)(interface->next_beacon - now); } INFO("Scheduling " PUB_S_SRP "beacon on " PUB_S_SRP " for %u milliseconds in the future", interface->sent_first_beacon ? "" : "first ", interface->name, interval); ioloop_add_wake_event(interface->beacon_wakeup, interface, interface_beacon, NULL, interval); } static void router_discovery_start(interface_t *interface) { INFO("Starting router discovery on " PUB_S_SRP, interface->name); // Immediately when an interface shows up, start doing router solicits. start_router_solicit(interface); if (interface->post_solicit_wakeup == NULL) { interface->post_solicit_wakeup = ioloop_wakeup_create(); if (interface->post_solicit_wakeup == NULL) { ERROR("No memory for post-solicit RA wakeup on " PUB_S_SRP ".", interface->name); } } else { ioloop_cancel_wake_event(interface->post_solicit_wakeup); } // In 20 seconds, check the results of router discovery and update policy as needed. if (interface->post_solicit_wakeup) { ioloop_add_wake_event(interface->post_solicit_wakeup, interface, post_solicit_policy_evaluate, NULL, 20 * 1000); } interface->router_discovery_in_progress = true; interface->router_discovery_started = true; } static void flush_routers(interface_t *interface, uint64_t now) { icmp_message_t *router, **p_router; // Flush stale routers (or all routers). for (p_router = &interface->routers; *p_router != NULL; ) { router = *p_router; if (now == 0 || now - router->received_time > MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE) { *p_router = router->next; SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_addr_buf); INFO("flushing stale router - ifname: " PUB_S_SRP ", router src: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf)); icmp_message_free(router); } else { p_router = &(*p_router)->next; } } } static void router_discovery_cancel(interface_t *interface) { if (interface->router_solicit_wakeup != NULL) { ioloop_cancel_wake_event(interface->router_solicit_wakeup); } if (interface->post_solicit_wakeup != NULL) { ioloop_cancel_wake_event(interface->post_solicit_wakeup); } #if SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY if (interface->vicarious_discovery_complete != NULL) { ioloop_cancel_wake_event(interface->vicarious_discovery_complete); INFO("stopping vicarious router discovery on " PUB_S_SRP, interface->name); } interface->vicarious_router_discovery_in_progress = false; #endif // SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY } static void router_discovery_stop(interface_t *interface, uint64_t now) { if (!interface->router_discovery_started) { INFO("router discovery not yet started."); return; } if (!interface->router_discovery_complete) { INFO("stopping router discovery on " PUB_S_SRP, interface->name); } router_discovery_cancel(interface); interface->router_discovery_complete = true; interface->router_discovery_in_progress = false; // clear out need_reconfigure_prefix when router_discovery_complete is set back to true. interface->need_reconfigure_prefix = false; #ifdef FLUSH_STALE_ROUTERS flush_routers(interface, now); #else (void)now; #endif // FLUSH_STALE_ROUTERS // See if we need a new prefix on the interface. interface_prefix_evaluate(interface); } #if SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY static void adjust_router_received_time(interface_t *const interface, const uint64_t now, const int64_t time_adjusted) { icmp_message_t *router; if (interface->routers == NULL) { if (interface->our_prefix_advertised) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __ipv6_prefix); INFO("No router information available for the interface - " "ifname: " PUB_S_SRP ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __ipv6_prefix)); } else { INFO("No router information available for the interface - " "ifname: " PUB_S_SRP, interface->name); } goto exit; } for (router = interface->routers; router != NULL; router = router->next) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_addr_buf); // Only adjust the received time once. if (router->received_time_already_adjusted) { INFO("received time already adjusted - remaining time: %llu, " "router src: " PRI_SEGMENTED_IPv6_ADDR_SRP, (now - router->received_time) / MSEC_PER_SEC, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf)); continue; } require_action_quiet( (time_adjusted > 0 && (UINT64_MAX - now) > (uint64_t)time_adjusted) || (time_adjusted < 0 && now > ((uint64_t)-time_adjusted)), exit, ERROR("adjust_router_received_time: invalid adjusted values is causing overflow - " "now: %" PRIu64 ", time_adjusted: %" PRId64, now, time_adjusted)); router->received_time = now + time_adjusted; router->received_time_already_adjusted = true; // Only adjust the icmp message received time once. INFO("router received time is adjusted - router src: " PRI_SEGMENTED_IPv6_ADDR_SRP ", adjusted value: %" PRId64, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_addr_buf), time_adjusted); } exit: return; } static void make_all_routers_nearly_stale(interface_t *interface, uint64_t now) { // Make every router go stale in 19.999 seconds. This means that if we don't get a response // to our solicit in 20 seconds, then when the timeout callback is called, there will be no // routers on the interface that aren't stale, which will trigger router discovery. adjust_router_received_time(interface, now, 19999 - 600 * MSEC_PER_SEC); } static void vicarious_discovery_callback(void *context) { interface_t *interface = context; INFO("Vicarious router discovery finished on " PUB_S_SRP ".", interface->name); interface->vicarious_router_discovery_in_progress = false; // At this point, policy evaluate will show all the routes that were present before vicarious // discovery as stale, so policy_evaluate will start router discovery if we didn't get any // RAs containing on-link prefixes. routing_policy_evaluate(interface, false); } #endif // SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY #ifndef RA_TESTER static void routing_policy_evaluate_all_interfaces(route_state_t *route_state, bool assume_changed) { interface_t *interface; for (interface = route_state->interfaces; interface; interface = interface->next) { routing_policy_evaluate(interface, assume_changed); } } #endif #ifdef FLUSH_STALE_ROUTERS static void stale_router_policy_evaluate(void *context) { interface_t *interface = context; INFO("Evaluating stale routers on " PUB_S_SRP, interface->name); flush_routers(interface, ioloop_timenow()); // See if we need a new prefix on the interface. interface_prefix_evaluate(interface); routing_policy_evaluate(interface, true); } #endif // FLUSH_STALE_ROUTERS static bool prefix_usable(interface_t *interface, route_state_t *route_state, icmp_message_t *router, prefix_information_t *prefix) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_addr_buf); // It needs to be on link, autoconfiguration enabled, or have the managed flag set and we are allowing DHCPv6-only // prefixes (not by default). And the preferred lifetime needs to be >0 (maybe should be >= 1800?) if (!((prefix->flags & ND_OPT_PI_FLAG_ONLINK) && ((prefix->flags & ND_OPT_PI_FLAG_AUTO) || (route_state->config_enable_dhcpv6_prefixes && (router->flags & ND_RA_FLAG_MANAGED))) && prefix->preferred_lifetime > 300)) { INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is advertising prefix " PRI_SEGMENTED_IPv6_ADDR_SRP ": %sonlink, %sautoconf, %sdhcp, preferred = %d", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_addr_buf), bool_str(prefix->flags & ND_OPT_PI_FLAG_ONLINK), bool_str(prefix->flags & ND_OPT_PI_FLAG_AUTO), bool_str(route_state->config_enable_dhcpv6_prefixes && (router->flags & ND_RA_FLAG_MANAGED)), prefix->preferred_lifetime); return false; } int cmp = in6prefix_compare(&prefix->prefix, &route_state->xpanid_prefix, 8); if (!cmp) { INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is advertising xpanid prefix " PRI_SEGMENTED_IPv6_ADDR_SRP ": not considering it usable", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_addr_buf)); // If it's the xpanid prefix, we will also advertise the xpanid prefix return false; } // If this is a stub router, and we are advertising our own prefix, and the PIO it is advertising is greater than // the one we are advertising, then we keep advertising ours. if (interface->our_prefix_advertised && router->stub_router && cmp > 0) { INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is a stub router advertising prefix " PRI_SEGMENTED_IPv6_ADDR_SRP ", which loses the election and is not usable", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_addr_buf)); return false; } INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is " PUB_S_SRP "advertising prefix " PRI_SEGMENTED_IPv6_ADDR_SRP ", which is usable", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), router->stub_router ? "a stub router " : "", SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_addr_buf)); return true; } static void routing_policy_evaluate(interface_t *interface, bool assume_changed) { icmp_message_t *router; bool new_prefix = false; // new prefix means that srp-mdns-proxy received a new prefix from the wire, which it // did not know before. bool on_link_prefix_present = false; bool something_changed = assume_changed; uint64_t now = ioloop_timenow(); bool stale_routers_exist = false; uint64_t stale_refresh_time = 0; route_state_t *route_state = interface->route_state; // No action on interfaces that aren't eligible for routing or that isn't currently active. if (interface->ineligible || interface->inactive) { INFO("not evaluating policy on " PUB_S_SRP " because it's " PUB_S_SRP, interface->name, interface->ineligible ? "ineligible" : "inactive"); return; } // We can't tell whether any particular prefix is usable until we've gotten the xpanid. if (route_state->have_xpanid_prefix) { // Look at all the router advertisements we've seen to see if any contain a usable prefix which is not the // prefix we'd advertise. Routers advertising that prefix are all Thread BRs, and it's fine for more than // one router to advertise a prefix, so we will also advertise it for redundancy. for (router = interface->routers; router; router = router->next) { icmp_option_t *option = router->options; int i; bool usable = false; for (i = 0; i < router->num_options; i++, option++) { if (option->type == icmp_option_prefix_information) { prefix_information_t *prefix = &option->option.prefix_information; #ifndef RA_TESTER omr_publisher_check_prefix(route_state->omr_publisher, &prefix->prefix, prefix->length); #endif if (prefix_usable(interface, route_state, router, prefix)) { // We don't consider the prefix we would advertise to be infrastructure-provided if we see it // advertised by another router, because that router is also a Thread BR, and we don't want // to get into dueling prefixes with it. if (in6prefix_compare(&option->option.prefix_information.prefix, &route_state->xpanid_prefix, 8)) { uint32_t preferred_lifetime_offset = MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE / MSEC_PER_SEC; uint32_t preferred_lifetime = prefix->preferred_lifetime; // Infinite preferred lifetime. Bogus. if (preferred_lifetime == UINT32_MAX) { preferred_lifetime = 60 * 60; // One hour } // If the remaining time on this prefix is less than the stale time gap, use an offset that's the // valid lifetime minus sixty seconds so that we have time if the prefix expires. if (preferred_lifetime < preferred_lifetime_offset + 60) { // If the preferred lifetime is less than a minute, we're not going to count this as a valid // on-link prefix. if (preferred_lifetime < 60) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf); INFO("router " PRI_SEGMENTED_IPv6_ADDR_SRP " advertising " PRI_SEGMENTED_IPv6_ADDR_SRP " has a preferred lifetime of %d, which is not enough to count as usable.", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf), preferred_lifetime); continue; } preferred_lifetime_offset = preferred_lifetime - 60; } // Lifetimes are in seconds, but henceforth we will compare with clock times, which are in ms. preferred_lifetime_offset *= MSEC_PER_SEC; // If the prefix' preferred lifetime plus the time received is in the past, the prefix doesn't // count as an on-link prefix that's present. if (router->received_time + preferred_lifetime_offset < now) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf); INFO("router " PRI_SEGMENTED_IPv6_ADDR_SRP " advertising " PRI_SEGMENTED_IPv6_ADDR_SRP " was received %d seconds ago with a preferred lifetime of %d.", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf), (int)((now - router->received_time) / 1000), preferred_lifetime); continue; } // This prefix is in principle usable. It may not actually be usable if it is stale, but we mark it usable so it // will continue to be probed. usable = true; // router->reachable will be true immediately after receiving a router advertisement until we do a // probe and don't get a response. It will become true again if, during a later probe, we get a // response. if (!router->reachable) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf); INFO("router %p " PRI_SEGMENTED_IPv6_ADDR_SRP " advertising %d %p " PRI_SEGMENTED_IPv6_ADDR_SRP " was last known to be reachable %d seconds ago.", router, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), i, option, SEGMENTED_IPv6_ADDR_PARAM_SRP(option->option.prefix_information.prefix.s6_addr, pref_buf), (int)((now - router->latest_na) / 1000)); continue; } // Otherwise, if this router's on-link prefix will expire later than any other we've seen if (stale_refresh_time < router->received_time + preferred_lifetime_offset) { stale_refresh_time = router->received_time + preferred_lifetime_offset; } // If this is a new icmp_message received now and contains PIO. if (router->new_router) { new_prefix = true; router->new_router = false; // clear the bit since srp-mdns-proxy already processed it. } // This router has a usable prefix. usable = true; // Right now all we need is to see if there is an on-link prefix. on_link_prefix_present = true; SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_add_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, __pio_prefix_buf); INFO("router has usable PIO - ifname: " PUB_S_SRP ", router src: " PRI_SEGMENTED_IPv6_ADDR_SRP ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_add_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, __pio_prefix_buf)); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); INFO("Router " PRI_SEGMENTED_IPv6_ADDR_SRP " is advertising the xpanid prefix: not counting as usable ", SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf)); } } else { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, __router_src_add_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, __pio_prefix_buf); INFO("router has unusable PIO - ifname: " PUB_S_SRP ", router src: " PRI_SEGMENTED_IPv6_ADDR_SRP ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, __router_src_add_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, __pio_prefix_buf)); } } } // Remember whether or not this router has a usable prefix. router->usable = usable; } } INFO("policy on " PUB_S_SRP ": " PUB_S_SRP "stale " /* stale_routers_exist ? */ PUB_S_SRP "started " /* interface->router_discovery_started ? */ PUB_S_SRP "disco " /* interface->router_discovery_complete ? */ PUB_S_SRP "present " /* on_link_prefix_present ? */ PUB_S_SRP "advert " /* interface->our_prefix_advertised ? */ PUB_S_SRP "conf " /* interface->on_link_prefix_configured ? */ PUB_S_SRP "new_prefix " /* new_prefix ? */ "preferred = %" PRIu32 " valid = %" PRIu32 " deadline = %" PRIu64, interface->name, stale_routers_exist ? "" : "!", interface->router_discovery_started ? "" : "!", interface->router_discovery_complete ? "" : "!", on_link_prefix_present ? "" : "!", interface->our_prefix_advertised ? "" : "!", interface->on_link_prefix_configured ? "" : "!", new_prefix ? "" : "!", interface->preferred_lifetime, interface->valid_lifetime, interface->deprecate_deadline); // If there are stale routers, start doing router discovery again to see if we can get them to respond. // Note that doing router discover just because we haven't seen an RA is actually not allowed in RFC 4861, // so this shouldn't be enabled. // Also, if we have not yet done router discovery, do it now. if ((!interface->router_discovery_started || !interface->router_discovery_complete #if SRP_FEATURE_STALE_ROUTER_DISCOVERY || stale_routers_exist #endif //SRP_FEATURE_STALE_ROUTER_DISCOVERY ) && !on_link_prefix_present) { if (!interface->router_discovery_in_progress) { // Start router discovery. INFO("starting router discovery"); router_discovery_start(interface); } else { INFO("router discovery in progress"); } } // If we are advertising a prefix and there's another on-link prefix, deprecate the one we are // advertising. else if (interface->our_prefix_advertised && on_link_prefix_present) { // If we have been advertising a preferred prefix, deprecate it. SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf); if (interface->preferred_lifetime == BR_PREFIX_LIFETIME) { INFO("routing_policy_evaluate: deprecating interface prefix in 30 minutes - prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf)); interface->deprecate_deadline = now + BR_PREFIX_LIFETIME * 1000; something_changed = true; interface->preferred_lifetime = 0; } else { INFO("prefix deprecating in progress - prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf)); } } // If there is no on-link prefix and we aren't advertising, or have deprecated, start advertising // again (or for the first time). else if (!on_link_prefix_present && interface->router_discovery_complete && route_state->have_xpanid_prefix && (!interface->our_prefix_advertised || interface->deprecate_deadline != 0 || interface->preferred_lifetime == 0)) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf); INFO("advertising prefix again - ifname: " PUB_S_SRP ", prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf)); // If we were deprecating, stop. ioloop_cancel_wake_event(interface->deconfigure_wakeup); interface->deprecate_deadline = 0; // Start advertising immediately, 30 minutes. interface->preferred_lifetime = interface->valid_lifetime = BR_PREFIX_LIFETIME; // If the on-link prefix isn't configured on the interface, do that. if (!interface->on_link_prefix_configured) { #ifndef RA_TESTER if (!interface->is_thread) { #endif interface_prefix_configure(interface->ipv6_prefix, interface); #ifndef RA_TESTER } else { INFO("Not setting up " PUB_S_SRP " because it is the thread interface", interface->name); } #endif } else { // Configuring the on-link prefix takes a while, so we want to re-evaluate after it's finished. interface->our_prefix_advertised = true; something_changed = true; } } // If there is no on-link prefix present, and srp-mdns-proxy itself is advertising the prefix, and it has configured // an on-link prefix, and the interface is not thread interface, and it just got an interface address removal event, // it is possible that the IPv6 routing has been flushed due to loss of address in configd, so here we explicitly // reconfigure the IPv6 prefix and the routing. else if (interface->need_reconfigure_prefix && !on_link_prefix_present && interface->our_prefix_advertised && interface->on_link_prefix_configured && !interface->is_thread) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf); INFO("reconfigure ipv6 prefix due to possible network changes -" " prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, __prefix_buf)); interface_prefix_configure(interface->ipv6_prefix, interface); interface->need_reconfigure_prefix = false; } // If the on-link prefix goes away, stop suppressing the one we've been advertising (if it's still valid). if (!on_link_prefix_present && interface->suppress_ipv6_prefix) { INFO("un-suppressing ipv6 prefix."); interface->suppress_ipv6_prefix = false; } // If we've been looking to see if there's an on-link prefix, and we got one from the new router advertisement, // stop looking for new one. if (new_prefix) { router_discovery_stop(interface, now); } // If anything changed, do an immediate beacon; otherwise wait until the next one. // Also when something changed, set the number of transmissions back to zero so that // we send a few initial beacons quickly for reliability. if (something_changed) { INFO("change on " PUB_S_SRP ": " PUB_S_SRP "started " PUB_S_SRP "disco " PUB_S_SRP "present " PUB_S_SRP "advert " PUB_S_SRP "conf preferred = %" PRIu32 " valid = %" PRIu32 " deadline = %" PRIu64, interface->name, interface->router_discovery_started ? "" : "!", interface->router_discovery_complete ? "" : "!", on_link_prefix_present ? "" : "!", interface->our_prefix_advertised ? "" : "!", interface->on_link_prefix_configured ? "" : "!", interface->preferred_lifetime, interface->valid_lifetime, interface->deprecate_deadline); interface->num_beacons_sent = 0; interface_beacon_schedule(interface, 0); } // It's possible for us to start configuring the interface because there's no on-link prefix, and then see // an advertisement for an on-link prefix before interface configuration completes. When this happens, we // need to delete the address we just configured, because we're not going to be advertising it. We always // get a policy re-evaluation event when interface configuration completes, so this will happen immediately. // At this point we have not yet sent a router advertisement with the prefix, so even though it has a preferred // lifetime of about 1800 seconds here, we can safely set it to zero without leaving stale information // in any host's routing table. if (!interface->our_prefix_advertised && interface->on_link_prefix_configured) { INFO("on-link prefix appeared during interface configuration. removing"); interface->preferred_lifetime = 0; interface_prefix_deconfigure(interface); } #ifdef FLUSH_STALE_ROUTERS // If we have an on-link prefix, schedule a policy re-evaluation at the stale router interval. if (on_link_prefix_present) { if (stale_refresh_time < now) { ERROR("Stale refresh time is in the past: %" PRIu64 "!", stale_refresh_time); } else { // The math used to compute refresh timeout guarantees that refresh_timeout will be <10 minutes. int refresh_timeout = (int)(stale_refresh_time - now); if (interface->stale_evaluation_wakeup == NULL) { interface->stale_evaluation_wakeup = ioloop_wakeup_create(); if (interface->stale_evaluation_wakeup == NULL) { ERROR("No memory for stale router evaluation wakeup on " PUB_S_SRP ".", interface->name); } } else { ioloop_cancel_wake_event(interface->stale_evaluation_wakeup); } ioloop_add_wake_event(interface->stale_evaluation_wakeup, interface, stale_router_policy_evaluate, NULL, refresh_timeout); } } #endif // FLUSH_STALE_ROUTERS // Once router discovery is complete, start doing aliveness checks on whatever we discovered (if anything). if (interface->last_router_probe == 0 && interface->router_discovery_started && interface->router_discovery_complete) { schedule_next_router_probe(interface); } #ifndef RA_TESTER if (route_state->route_tracker != NULL) { route_tracker_route_state_changed(route_state->route_tracker, interface); } #endif } #if SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY static void start_vicarious_router_discovery_if_appropriate(interface_t *const interface) { if (!interface->our_prefix_advertised && !interface->vicarious_router_discovery_in_progress && !interface->router_discovery_in_progress) { if (interface->vicarious_discovery_complete == NULL) { interface->vicarious_discovery_complete = ioloop_wakeup_create(); } else { ioloop_cancel_wake_event(interface->vicarious_discovery_complete); } if (interface->vicarious_discovery_complete != NULL) { ioloop_add_wake_event(interface->vicarious_discovery_complete, interface, vicarious_discovery_callback, NULL, 20 * 1000); interface->vicarious_router_discovery_in_progress = true; } // In order for vicarious router discovery to be useful, we need all of the routers // that were present when the first solicit was received to be stale when we give up // on vicarious discovery. If we got any router advertisements, these will not be // stale, and that means vicarious discovery succeeded. make_all_routers_nearly_stale(interface, ioloop_timenow()); INFO("Starting vicarious router discovery on " PUB_S_SRP, interface->name); } } #endif // SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY static void retransmit_unicast_beacon(void *context) { icmp_message_t *message = context; // Schedule retranmsission interface_beacon_send(message->interface, &message->source); ioloop_add_wake_event(message->wakeup, message, retransmit_unicast_beacon, NULL, MIN_DELAY_BETWEEN_RAS + srp_random16() % RA_FUZZ_TIME); // Discontinue retransmission after the third we've sent. if (message->messages_sent++ > 2) { icmp_message_t **sp = &message->interface->solicits; while (*sp != NULL) { if (*sp == message) { *sp = message->next; icmp_message_free(message); break; } else { sp = &(*sp)->next; } } } } // This gets called to check to see if any of the usable routers are still responding. It gets called whenever // we get a router solicit, to ensure that the solicit gets a quick response, and also gets called once every // minute so that we quickly notice when a router becomes unreachable. static void send_router_probes(void *context) { interface_t *interface = context; // After sending three probes, do a policy evaluation. if (interface->num_solicits_sent++ > MAX_NS_RETRANSMISSIONS - 1) { // Mark routers from which we received neighbor advertises during the probe as reachable. Routers // that did not respond are no longer reachable. for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) { SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf); INFO("router (%p) " PRI_SEGMENTED_IPv6_ADDR_SRP " was " PUB_S_SRP "reached during probing.", router, SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf), router->reached ? "" : "not "); router->reachable = router->reached; } routing_policy_evaluate(interface, false); schedule_next_router_probe(interface); return; } // Send Neighbor Solicits to any usable routers that haven't responded yet and schedule the next call to // send_router_probes... for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) { // Don't probe routers that aren't usable, and don't re-probe a router that's already responded in this probe cycle. if (!router->usable || router->reached) { continue; } neighbor_solicit_send(router->interface, &router->source); } ioloop_add_wake_event(interface->neighbor_solicit_wakeup, interface, send_router_probes, NULL, MIN_DELAY_BETWEEN_RAS + srp_random16() % RA_FUZZ_TIME); } static void check_router_aliveness(void *context) { interface_t *interface = context; if (!interface->probing) { interface->probing = true; if (interface->neighbor_solicit_wakeup == NULL) { interface->neighbor_solicit_wakeup = ioloop_wakeup_create(); } if (interface->neighbor_solicit_wakeup != NULL) { interface->num_solicits_sent = 0; // Clear the reached flag on all routers for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) { router->reached = false; } send_router_probes(interface); } } } static void schedule_next_router_probe(interface_t *interface) { if (interface->router_probe_wakeup == NULL) { interface->router_probe_wakeup = ioloop_wakeup_create(); } if (interface->router_probe_wakeup != NULL) { INFO("scheduling router probe in 60 seconds."); ioloop_add_wake_event(interface->router_probe_wakeup, interface, check_router_aliveness, NULL, 60 * 1000); interface->probing = false; interface->last_router_probe = ioloop_timenow(); } } void router_solicit(icmp_message_t *message) { interface_t *iface, *interface; bool is_retransmission = false; // Further validate the message if (message->hop_limit != 255 || message->code != 0) { ERROR("Invalid router solicitation, hop limit = %d, code = %d", message->hop_limit, message->code); goto out; } if (IN6_IS_ADDR_UNSPECIFIED(&message->source)) { icmp_option_t *option = message->options; int i; for (i = 0; i < message->num_options; i++) { if (option->type == icmp_option_source_link_layer_address) { ERROR("source link layer address in router solicitation from unspecified IP address"); goto out; } option++; } } else { // Make sure it's not from this host for (iface = message->route_state->interfaces; iface; iface = iface->next) { if (iface->have_link_layer_address && !in6addr_compare(&message->source, &iface->link_local)) { INFO("dropping router solicitation sent from this host."); goto out; } } } interface = message->interface; SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, source_buf); INFO(PUB_S_SRP " solicit on " PUB_S_SRP ": source address is " PRI_SEGMENTED_IPv6_ADDR_SRP, is_retransmission ? "retransmitted" : "initial", message->interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, source_buf)); // See if this is a retransmission... icmp_message_t **sp; sp = &interface->solicits; while (*sp != NULL) { icmp_message_t *solicit = *sp; // Same source? Not already found? if (!is_retransmission && !in6addr_compare(&message->source, &solicit->source)) { uint64_t now = ioloop_timenow(); // RFC 4861 limits RS transmissions to 3, separated by four seconds. Allowing for a bit of slop, // if it was received in the past 15 seconds, this is a retransmission. if (now - solicit->received_time > 15 * 1000) { *sp = solicit->next; icmp_message_free(solicit); } else { solicit->retransmissions_received++; is_retransmission = true; // Since this is a retransmission, that hints that there might not be any live routers // on this link, so check to see if the routers we are aware of are alive. check_router_aliveness(interface); sp = &(*sp)->next; } } else { sp = &(*sp)->next; } } // Schedule an immediate send. If this is a retransmission, just let our retransmission schedule // dictate when to send the next one. if (!is_retransmission && !interface->ineligible && !interface->inactive) { message->wakeup = ioloop_wakeup_create(); if (message->wakeup == NULL) { ERROR("no memory for solicit wakeup."); } else { // Save the message for later *sp = message; // Start the unicast RA transmission train for this RS. retransmit_unicast_beacon(message); message = NULL; } } else { INFO("not sending a router advertisement."); } #if SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY // When we receive a router solicit, it means that a host is looking for a router. We should // expect to hear replies if they are multicast. If we hear no replies, it could mean there is // no on-link prefix. In this case, we restart our own router discovery process. There is no // need to do this if we are the one advertising a prefix. start_vicarious_router_discovery_if_appropriate(interface); #endif // SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY out: if (message != NULL) { icmp_message_free(message); } } void router_advertisement(icmp_message_t *message) { interface_t *iface; icmp_message_t *router, **rp; if (message->hop_limit != 255 || message->code != 0 || !IN6_IS_ADDR_LINKLOCAL(&message->source)) { ERROR("Invalid router advertisement, hop limit = %d, code = %d", message->hop_limit, message->code); icmp_message_free(message); return; } for (iface = message->route_state->interfaces; iface != NULL; iface = iface->next) { if (iface->have_link_layer_address && !in6addr_compare(&message->source, &iface->link_local)) { INFO("dropping router advertisement sent from this host."); icmp_message_free(message); return; } } // See if we've had a previous advertisement from this router. Note that routers can send more than one // RA to advertise more data than will fit in one RA, but in practice routers tend not to do this, and how // this is supposed to work is not clearly specified. From RFC4861: // // If including all options causes the size of an advertisement to // exceed the link MTU, multiple advertisements can be sent, each // containing a subset of the options. // // If this happens, we're going to wind up using the last RA in the sequence. Ideally we'd do some work to marshal // RA trains. This is too much work to do in a current milestone. The issue is tracked in rdar://105200987 // (Restructure handling of incoming router advertisements so as to marshal the data in case we get more than one RA // from the same router with different data.) for (rp = &message->interface->routers; *rp != NULL; rp = &(*rp)->next) { router = *rp; // The new RA is from the same router as this previous RA. if (!in6addr_compare(&router->source, &message->source)) { message->next = router->next; *rp = message; icmp_message_free(router); break; } } // If we got rid of the old RA, *rp will be non-NULL. If we didn't find a match for the old RA, or if we // need to keep the old RA, then *rp will be NULL, meaning that we should keep the new RA. if (*rp == NULL) { *rp = message; } // When we receive an RA, we can assume that the router is reachable, and skip immediately probing with a // neighbor solicit. message->latest_na = message->received_time; message->reachable = true; message->reached = true; // Check for the stub router flag here so that we have it when scanning PIOs for usability. for (int i = 0; i < message->num_options; i++) { icmp_option_t *option = &message->options[i]; if (option->type == icmp_option_ra_flags_extension) { if (option->option.ra_flags_extension[0] & RA_FLAGS1_STUB_ROUTER) { message->stub_router = true; } } } // Something may have changed, so do a policy recalculation for this interface routing_policy_evaluate(message->interface, false); } void neighbor_advertisement(icmp_message_t *message) { if (message->hop_limit != 255 || message->code != 0) { ERROR("Invalid neighbor advertisement, hop limit = %d, code = %d", message->hop_limit, message->code); return; } // If this NA matches a router that has advertised a usable prefix, mark the router as alive by setting the // "latest_na" value to the current time. We don't care about NAs for routers that are not advertising a usable // prefix. for (icmp_message_t *router = message->interface->routers; router != NULL; router = router->next) { if (!in6addr_compare(&message->source, &router->source)) { // Only log for usable routers, to avoid a lot of extra noise. However, we don't actually probe routers that // aren't usable, so generally speaking this test will always be true. if (router->usable) { SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, source_buf); INFO("usable neighbor advertisement recieved on " PUB_S_SRP " from " PRI_SEGMENTED_IPv6_ADDR_SRP, message->interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, source_buf)); } router->latest_na = ioloop_timenow(); router->reached = true; router->reachable = true; } } return; } #if defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG) || \ defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG) static void link_route_done(void *context, int status, const char *error) { interface_t *interface = context; if (error != NULL) { ERROR("link_route_done on " PUB_S_SRP ": " PUB_S_SRP, interface->name, error); } else { INFO("link_route_done on " PUB_S_SRP ": %d.", interface->name, status); } ioloop_subproc_release(interface->link_route_adder_process); interface->link_route_adder_process = NULL; // Now that the on-link prefix is configured, time for a policy re-evaluation. interface->on_link_prefix_configured = true; routing_policy_evaluate(interface, true); } #endif static void interface_prefix_configure(struct in6_addr prefix, interface_t *interface) { int sock; route_state_t *route_state = interface->route_state; sock = socket(PF_INET6, SOCK_DGRAM, 0); if (sock < 0) { ERROR("interface_prefix_configure: socket(PF_INET6, SOCK_DGRAM, 0) failed " PUB_S_SRP ": " PUB_S_SRP, interface->name, strerror(errno)); return; } #ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES struct in6_addr interface_address = prefix; char addrbuf[INET6_ADDRSTRLEN + 4]; // Use our ULA prefix as the host identifier. memcpy(&interface_address.s6_addr[10], &route_state->srp_server->ula_prefix.s6_addr[0], 6); interface_address.s6_addr[8] = (interface->index >> 8) & 255; interface_address.s6_addr[9] = interface->index & 255; inet_ntop(AF_INET6, &interface_address, addrbuf, INET6_ADDRSTRLEN); #if defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG) char *args[] = { "set", interface->name, "MANUAL-V6", addrbuf, "64" }; if (interface->link_route_adder_process != NULL) { ERROR("interface_prefix_configure: " PUB_S_SRP " already configuring the route.", interface->name); return; } INFO("/sbin/ipconfig " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP, args[0], args[1], args[2], args[3], args[4]); interface->link_route_adder_process = ioloop_subproc("/usr/sbin/ipconfig", args, 5, link_route_done, interface, NULL); if (interface->link_route_adder_process == NULL) { ERROR("interface_prefix_configure: unable to set interface address for %s to %s.", interface->name, addrbuf); } #elif defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG) char *eos = addrbuf + strlen(addrbuf); if (sizeof(addrbuf) - (eos - addrbuf) < 4) { ERROR("interface_prefix_configure: this shouldn't happen: no space in addrbuf"); return; } strcpy(eos, "/64"); char *args[] = { interface->name, "add", addrbuf }; if (interface->link_route_adder_process != NULL) { ERROR("interface_prefix_configure: " PUB_S_SRP " already configuring the route.", interface->name); return; } INFO("/sbin/ifconfig %s %s %s", args[0], args[1], args[2]); interface->link_route_adder_process = ioloop_subproc("/sbin/ifconfig", args, 3, link_route_done, NULL, interface); if (interface->link_route_adder_process == NULL) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf); ERROR("interface_prefix_configure: unable to set interface address for " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP ".", interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf)); } #else struct in6_aliasreq alias_request; int ret; memset(&alias_request, 0, sizeof(alias_request)); strlcpy(alias_request.ifra_name, interface->name, IFNAMSIZ); alias_request.ifra_addr.sin6_family = AF_INET6; alias_request.ifra_addr.sin6_len = sizeof(alias_request.ifra_addr); memcpy(&alias_request.ifra_addr.sin6_addr, &interface_address, sizeof(alias_request.ifra_addr.sin6_addr)); alias_request.ifra_prefixmask.sin6_len = sizeof(alias_request.ifra_addr); alias_request.ifra_prefixmask.sin6_family = AF_INET6; memset(&alias_request.ifra_prefixmask.sin6_addr, 0xff, 8); // /64. alias_request.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; // seconds, I hope? alias_request.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; // seconds, I hope? ret = ioctl(sock, SIOCAIFADDR_IN6, &alias_request); if (ret < 0) { SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf); ERROR("interface_prefix_configure: can't configure static address " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP ": " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf), interface->name, strerror(errno)); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(interface_address.s6_addr, if_addr_buf); INFO("added address " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface_address.s6_addr, if_addr_buf), interface->name); } #endif // CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG #else (void)prefix; #endif // CONFIGURE_STATIC_INTERFACE_ADDRESSES close(sock); } #ifndef RA_TESTER static void set_thread_forwarding(void) { #ifdef LINUX const char *procfile = "/proc/sys/net/ipv6/conf/all/forwarding"; int fd = open(procfile, O_WRONLY); if (fd < 0) { ERROR("%s: %s", procfile, strerror(errno)); } else { ssize_t ret = write(fd, "1", 1); if (ret < 0) { ERROR("write: %s", strerror(errno)); } else if (ret != 1) { ERROR("invalid write: %zd", ret); } close(fd); } #else int wun = 1; int ret = sysctlbyname("net.inet6.ip6.forwarding", NULL, 0, &wun, sizeof(wun)); if (ret < 0) { ERROR(PUB_S_SRP, strerror(errno)); } else { INFO("Enabled IPv6 forwarding."); } #endif } #endif // RA_TESTER #ifdef NEED_THREAD_RTI_SETTER static void thread_rti_done(void *UNUSED context, int status, const char *error) { route_state_t *route_state = context; if (error != NULL) { ERROR("thread_rti_done: " PUB_S_SRP, error); } else { INFO("%d.", status); } ioloop_subproc_release(route_state->thread_rti_setter_process); route_state->thread_rti_setter_process = NULL; } static void set_thread_rti(route_state_t *route_state) { char *args[] = { "-w", "net.inet6.icmp6.nd6_process_rti=1" }; route_state->thread_rti_setter_process = ioloop_subproc("/usr/sbin/sysctl", args, 2, thread_rti_done, NULL, route_state); if (route_state->thread_rti_setter_process == NULL) { ERROR("Unable to set thread rti enabled."); } } #endif #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) #ifdef ADD_PREFIX_WITH_WPANCTL static void thread_prefix_done(void *context, int status, const char *error) { route_state_t *route_state = context; if (error != NULL) { ERROR("thread_prefix_done: " PUB_S_SRP, error); } else { interface_t *interface; INFO("%d.", status); for (interface = route_state->interfaces; interface; interface = interface->next) { if (!interface->inactive) { interface_beacon_schedule(interface, 0); } } } ioloop_subproc_release(route_state->thread_prefix_adder_process); route_state->thread_prefix_adder_process = NULL; } #endif #endif // THREAD_BORDER_ROUTRER && !RA_TESTER static void post_solicit_policy_evaluate(void *context) { interface_t *interface = context; INFO("Done waiting for router discovery to finish on " PUB_S_SRP, interface->name); interface->router_discovery_complete = true; interface->router_discovery_in_progress = false; #ifdef FLUSH_STALE_ROUTERS flush_routers(interface, ioloop_timenow()); #endif // FLUSH_STALE_ROUTERS // See if we need a new prefix on the interface. interface_prefix_evaluate(interface); routing_policy_evaluate(interface, true); // Always clear out need_reconfigure_prefix when router_discovery_complete is set to true. interface->need_reconfigure_prefix = false; } static void ula_record(const char *ula_printable) { size_t len = strlen(ula_printable); if (access(THREAD_DATA_DIR, F_OK) < 0) { if (mkdir(THREAD_DATA_DIR, 0700) < 0) { ERROR("ula_record: " THREAD_DATA_DIR " not present and can't be created: %s", strerror(errno)); return; } } srp_store_file_data(NULL, THREAD_ULA_FILE, (uint8_t *)ula_printable, len); } void route_ula_generate(route_state_t *route_state) { char ula_prefix_buffer[INET6_ADDRSTRLEN]; struct in6_addr ula_prefix, old_ula_prefix; bool prefix_changed; // Already have a prefix? if (route_state->srp_server->ula_prefix.s6_addr[0] == 0xfd) { old_ula_prefix = route_state->srp_server->ula_prefix; prefix_changed = true; } else { prefix_changed = false; } in6addr_zero(&ula_prefix); srp_randombytes(&ula_prefix.s6_addr[1], 5); ula_prefix.s6_addr[0] = 0xfd; inet_ntop(AF_INET6, &ula_prefix, ula_prefix_buffer, sizeof ula_prefix_buffer); ula_record(ula_prefix_buffer); if (prefix_changed) { SEGMENTED_IPv6_ADDR_GEN_SRP(old_ula_prefix.s6_addr, old_prefix_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(ula_prefix.s6_addr, new_prefix_buf); INFO("ula-generate: prefix changed from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(old_ula_prefix.s6_addr, old_prefix_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(ula_prefix.s6_addr, new_prefix_buf)); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(ula_prefix.s6_addr, new_prefix_buf); INFO("ula-generate: generated ULA prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(ula_prefix.s6_addr, new_prefix_buf)); } // Set up the thread prefix. route_state->my_thread_ula_prefix = ula_prefix; route_state->srp_server->ula_prefix = ula_prefix; route_state->have_thread_prefix = true; #if SRP_FEATURE_NAT64 if (route_state->srp_server->srp_nat64_enabled) { nat64_set_ula_prefix(&ula_prefix); } #endif } void route_ula_setup(route_state_t *route_state) { bool have_stored_ula_prefix = false; char ula_buf[INET6_ADDRSTRLEN]; uint16_t length; if (srp_load_file_data(NULL, THREAD_ULA_FILE, (uint8_t *)ula_buf, &length, sizeof(ula_buf) - 1)) { ula_buf[length] = 0; if (inet_pton(AF_INET6, ula_buf, &route_state->srp_server->ula_prefix)) { have_stored_ula_prefix = true; } else { INFO("ula prefix %.*s is not valid", length, ula_buf); } } else { INFO("Couldn't open ULA file " THREAD_ULA_FILE "."); } // If we didn't already successfully fetch a stored prefix, try to store one. if (!have_stored_ula_prefix) { route_ula_generate(route_state); } else { // Set up the thread prefix. route_state->my_thread_ula_prefix = route_state->srp_server->ula_prefix; route_state->have_thread_prefix = true; #if SRP_FEATURE_NAT64 if (route_state->srp_server->srp_nat64_enabled) { nat64_set_ula_prefix(&route_state->srp_server->ula_prefix); } #endif } } static void router_solicit_callback(void *context) { interface_t *interface = context; if (interface->is_thread) { INFO("discontinuing router solicitations on thread interface " PUB_S_SRP, interface->name); return; } if (interface->num_solicits_sent >= 3) { INFO("Done sending router solicitations on " PUB_S_SRP ".", interface->name); return; } INFO("sending router solicitation on " PUB_S_SRP , interface->name); router_solicit_send(interface); interface->num_solicits_sent++; ioloop_add_wake_event(interface->router_solicit_wakeup, interface, router_solicit_callback, NULL, RTR_SOLICITATION_INTERVAL * 1000 + srp_random16() % 1024); } static void start_router_solicit(interface_t *interface) { if (interface->router_solicit_wakeup == NULL) { interface->router_solicit_wakeup = ioloop_wakeup_create(); if (interface->router_solicit_wakeup == 0) { ERROR("No memory for router solicit wakeup on " PUB_S_SRP ".", interface->name); return; } } else { ioloop_cancel_wake_event(interface->router_solicit_wakeup); } interface->num_solicits_sent = 0; ioloop_add_wake_event(interface->router_solicit_wakeup, interface, router_solicit_callback, NULL, 128 + srp_random16() % 896); } static interface_t * find_interface(route_state_t *route_state, const char *name, int ifindex) { interface_t **p_interface, *interface = NULL; for (p_interface = &route_state->interfaces; *p_interface; p_interface = &(*p_interface)->next) { interface = *p_interface; if (!strcmp(name, interface->name)) { if (ifindex != -1 && interface->index != ifindex) { INFO("interface name " PUB_S_SRP " index changed from %d to %d", name, interface->index, ifindex); interface->index = ifindex; } break; } } // If it's a new interface, make a structure. // We could do a callback, but don't have a use case if (*p_interface == NULL) { interface = interface_create(route_state, name, ifindex); if (interface != NULL) { if (route_state->thread_interface_name != NULL && !strcmp(name, route_state->thread_interface_name)) { interface->is_thread = true; } *p_interface = interface; } } return interface; } static void interface_shutdown(interface_t *interface) { icmp_message_t *router, *next; INFO("Interface " PUB_S_SRP " went away.", interface->name); if (interface->beacon_wakeup != NULL) { ioloop_cancel_wake_event(interface->beacon_wakeup); } if (interface->post_solicit_wakeup != NULL) { ioloop_cancel_wake_event(interface->post_solicit_wakeup); } if (interface->stale_evaluation_wakeup != NULL) { ioloop_cancel_wake_event(interface->stale_evaluation_wakeup); } if (interface->router_solicit_wakeup != NULL) { ioloop_cancel_wake_event(interface->router_solicit_wakeup); } if (interface->deconfigure_wakeup != NULL) { ioloop_cancel_wake_event(interface->deconfigure_wakeup); } #if SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY if (interface->vicarious_discovery_complete != NULL) { ioloop_cancel_wake_event(interface->vicarious_discovery_complete); } interface->vicarious_router_discovery_in_progress = false; #endif // SRP_FEATURE_VICARIOUS_ROUTER_DISCOVERY for (router = interface->routers; router; router = next) { next = router->next; icmp_message_free(router); } interface->routers = NULL; interface->last_beacon = interface->next_beacon = 0; interface->deprecate_deadline = 0; interface->preferred_lifetime = interface->valid_lifetime = 0; interface->num_solicits_sent = 0; interface->inactive = true; interface->ineligible = true; interface->our_prefix_advertised = false; interface->suppress_ipv6_prefix = false; interface->have_link_layer_address = false; interface->on_link_prefix_configured = false; interface->sent_first_beacon = false; interface->num_beacons_sent = 0; interface->router_discovery_started = false; interface->router_discovery_complete = false; interface->router_discovery_in_progress = false; interface->need_reconfigure_prefix = false; } static void interface_prefix_evaluate(interface_t *interface) { route_state_t *route_state = interface->route_state; // Set up the interface prefix using the prefix number for the link. interface->ipv6_prefix = route_state->xpanid_prefix; } #ifndef RA_TESTER static bool router_is_advertising(icmp_message_t *router, const struct in6_addr *prefix, int preflen) { for (int i = 0; i < router->num_options; i++) { icmp_option_t *option = &router->options[i]; if (option->type == icmp_option_prefix_information) { prefix_information_t *pio = &option->option.prefix_information; if (pio->length != 64) { SEGMENTED_IPv6_ADDR_GEN_SRP(&pio->prefix, prefix_buf); INFO("invalid IP address prefix length: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", SEGMENTED_IPv6_ADDR_PARAM_SRP(&pio->prefix, prefix_buf), preflen); continue; } if (!in6prefix_compare(prefix, &pio->prefix, 8)) { SEGMENTED_IPv6_ADDR_GEN_SRP(&pio->prefix, prefix_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(&router->source, router_buf); INFO("router at " PRI_SEGMENTED_IPv6_ADDR_SRP " advertised prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", SEGMENTED_IPv6_ADDR_PARAM_SRP(&router->source, router_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(&pio->prefix, prefix_buf), preflen); return true; } } } return false; } static void route_remove_routers_advertising_prefix(interface_t *interface, const struct in6_addr *prefix, int preflen) { if (preflen != 64) { SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf); INFO("invalid IP address prefix length: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix, prefix_buf), preflen); return; } for (icmp_message_t **rp = &interface->routers; *rp != NULL; ) { icmp_message_t *router = *rp; if (router_is_advertising(router, prefix, preflen)) { *rp = router->next; router->next = NULL; icmp_message_free(router); } else { rp = &router->next; } } } #endif // RA_TESTER static void ifaddr_callback(srp_server_t *server_state, void *context, const char *name, const addr_t *address, const addr_t *mask, unsigned flags, enum interface_address_change change) { char addrbuf[INET6_ADDRSTRLEN]; const uint8_t *addrbytes, *maskbytes, *prefp; int preflen, i; interface_t *interface; route_state_t *route_state = context; #ifndef POSIX_BUILD interface = find_interface(route_state, name, -1); #else interface = find_interface(route_state, name, if_nametoindex(name)); #endif if (interface == NULL) { ERROR("find_interface returned NULL for " PUB_S_SRP, name); return; } const bool is_thread_interface = interface->is_thread; if (address->sa.sa_family == AF_INET) { addrbytes = (uint8_t *)&address->sin.sin_addr; maskbytes = (uint8_t *)&mask->sin.sin_addr; prefp = maskbytes + 3; preflen = 32; if (change == interface_address_added) { // Just got an IPv4 address? if (!interface->num_ipv4_addresses) { if (!(flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) { interface_prefix_evaluate(interface); } } interface->num_ipv4_addresses++; } else if (change == interface_address_deleted) { interface->num_ipv4_addresses--; // Just lost our last IPv4 address? if (!(flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) { if (!interface->num_ipv4_addresses) { interface_prefix_evaluate(interface); } } } } else if (address->sa.sa_family == AF_INET6) { if (change == interface_address_added) { interface->num_ipv6_addresses++; } else if (change == interface_address_deleted) { interface->num_ipv6_addresses--; } addrbytes = (uint8_t *)&address->sin6.sin6_addr; maskbytes = (uint8_t *)&mask->sin6.sin6_addr; prefp = maskbytes + 15; preflen = 128; #ifndef LINUX } else if (address->sa.sa_family == AF_LINK) { snprintf(addrbuf, sizeof addrbuf, "%02x:%02x:%02x:%02x:%02x:%02x", address->ether_addr.addr[0], address->ether_addr.addr[1], address->ether_addr.addr[2], address->ether_addr.addr[3], address->ether_addr.addr[4], address->ether_addr.addr[5]); prefp = (uint8_t *)&addrbuf[0]; maskbytes = prefp + 1; // Skip prefix length calculation preflen = 0; addrbytes = NULL; #endif } else { INFO("Unknown address type %d", address->sa.sa_family); return; } if (change != interface_address_unchanged) { #ifndef LINUX if (address->sa.sa_family == AF_LINK) { if (!interface->ineligible) { INFO("interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_MAC_ADDR_SRP " flags %x", name, is_thread_interface ? " (thread)" : "", change == interface_address_added ? "added" : "removed", MAC_ADDR_PARAM_SRP(address->ether_addr.addr), flags); } } else { #endif for (; prefp >= maskbytes; prefp--) { if (*prefp) { break; } preflen -= 8; } for (i = 0; i < 8; i++) { if (*prefp & (1<sa.sa_family, addrbytes, addrbuf, sizeof addrbuf); if (!interface->ineligible) { if (address->sa.sa_family == AF_INET) { IPv4_ADDR_GEN_SRP(addrbytes, addr_buf); INFO("interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_IPv4_ADDR_SRP "/%d flags %x", name, is_thread_interface ? " (thread)" : "", change == interface_address_added ? "added" : "removed", IPv4_ADDR_PARAM_SRP(addrbytes, addr_buf), preflen, flags); } else if (address->sa.sa_family == AF_INET6) { SEGMENTED_IPv6_ADDR_GEN_SRP(addrbytes, addr_buf); INFO("interface " PUB_S_SRP PUB_S_SRP " " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d flags %x", name, is_thread_interface ? " (thread)" : "", change == interface_address_added ? "added" : "removed", SEGMENTED_IPv6_ADDR_PARAM_SRP(addrbytes, addr_buf), preflen, flags); #ifndef RA_TESTER if (change == interface_address_deleted) { route_remove_routers_advertising_prefix(interface, &address->sin6.sin6_addr, preflen); if (route_state->route_tracker != NULL) { route_tracker_route_state_changed(route_state->route_tracker, interface); } } #endif } else { INFO("invalid sa_family: %d", address->sa.sa_family); } // Only notify dnssd-proxy when srp-mdns-proxy and dnssd-proxy is combined together. #if !defined(RA_TESTER) && (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY) // Notify dnssd-proxy that address is added or removed. if (!is_thread_interface) { if (change == interface_address_added) { if (!interface->inactive) { dnssd_proxy_ifaddr_callback(server_state, context, name, address, mask, flags, change); } } else { // change == interface_address_removed dnssd_proxy_ifaddr_callback(server_state, context, name, address, mask, flags, change); } } #endif // #if !defined(RA_TESTER) && (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY) // When new IP address is removed, it is possible that the existing router information, such as // PIO and RIO is no longer valid since srp-mdns-proxy is losing its IP address. In order to let it to // flush the stale router information as soon as possible, we mark all the router as stale immediately, // by setting the router received time to a value which is 601s ago (router will be stale if the router // information is received for more than 600s). And then do router discovery for 20s, so we can ensure // that all the stale router information will be updated during the discovery, or flushed away. If all // routers are flushed, then srp-mdns-proxy will advertise its own prefix and configure the new IPv6 // address. if (address->sa.sa_family == AF_INET6 && // An IPv6 address change == interface_address_deleted && // went away in6prefix_compare(&address->sin6.sin6_addr, &interface->ipv6_prefix, 8) && // not one of ours !is_thread_mesh_synthetic_or_link_local(&address->sin6.sin6_addr)) // not link-local { INFO("clearing router discovery complete flag because address deleted."); #ifdef VICARIOUS_ROUTER_DISCOVERY INFO("making all routers stale and start router discovery due to removed address"); adjust_router_received_time(interface, ioloop_timenow(), -(MAX_ROUTER_RECEIVED_TIME_GAP_BEFORE_STALE + MSEC_PER_SEC)); #endif // Explicitly set router_discovery_complete to false so we can ensure that srp-mdns-proxy will start // the router discovery immediately. interface->router_discovery_complete = false; interface->router_discovery_started = false; // Set need_reconfigure_prefix to true to let routing_policy_evaluate know that the router discovery // is caused by interface removal event, so when the router discovery finished and nothing changes, // it can reconfigure the IPv6 routing in case configured does not handle it correctly. interface->need_reconfigure_prefix = true; routing_policy_evaluate(interface, false); } } #ifndef LINUX } #endif } // Not a broadcast interface if (flags & (IFF_LOOPBACK | IFF_POINTOPOINT)) { // Not the thread interface if (!is_thread_interface) { return; } } // 169.254.* if (address->sa.sa_family == AF_INET && IN_LINKLOCAL(address->sin.sin_addr.s_addr)) { return; } if (interface->index == -1) { interface->index = address->ether_addr.index; } #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) if (is_thread_interface && address->sa.sa_family == AF_INET6) { partition_utun0_address_changed(route_state, &address->sin6.sin6_addr, change); } #endif if (address->sa.sa_family == AF_INET) { } else if (address->sa.sa_family == AF_INET6) { if (IN6_IS_ADDR_LINKLOCAL(&address->sin6.sin6_addr)) { interface->link_local = address->sin6.sin6_addr; } #ifndef LINUX } else if (address->sa.sa_family == AF_LINK) { if (address->ether_addr.len == 6) { if (change != interface_address_deleted) { memcpy(interface->link_layer, address->ether_addr.addr, 6); INFO("setting link layer address for " PUB_S_SRP " to " PRI_MAC_ADDR_SRP, interface->name, MAC_ADDR_PARAM_SRP(interface->link_layer)); interface->have_link_layer_address = true; } else { INFO("resetting link layer address for " PUB_S_SRP " (was " PRI_MAC_ADDR_SRP ")", interface->name, MAC_ADDR_PARAM_SRP(interface->link_layer)); memset(interface->link_layer, 0, 6); interface->have_link_layer_address = false; } } #endif } #if defined(POSIX_BUILD) interface_active_state_evaluate(interface, true, true); #endif } #ifndef RA_TESTER static void route_get_mesh_local_prefix_callback(void *context, const char *prefix_string, cti_status_t status) { route_state_t *route_state = context; char prefix_buf[INET6_ADDRSTRLEN]; if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { INFO("disconnected"); attempt_wpan_reconnect(route_state); goto fail; } INFO(PRI_S_SRP " %d", prefix_string != NULL ? prefix_string : "", status); if (status != kCTIStatus_NoError) { INFO("error %d", status); } if (prefix_string == NULL) { INFO("NULL prefix string"); goto fail; } const char *prefix_addr_string; char *slash = strchr(prefix_string, '/'); if (slash != NULL) { size_t len = slash - prefix_string; if (len == 0) { ERROR("bogus prefix: " PRI_S_SRP, prefix_string); goto fail; } if (len - 1 > sizeof(prefix_buf)) { ERROR("prefix too long: " PRI_S_SRP, prefix_string); goto fail; } memcpy(prefix_buf, prefix_string, len); prefix_buf[len] = 0; prefix_addr_string = prefix_buf; } else { prefix_addr_string = prefix_string; } if (!inet_pton(AF_INET6, prefix_addr_string, &route_state->thread_mesh_local_prefix)) { ERROR("prefix syntax incorrect: " PRI_S_SRP, prefix_addr_string); goto fail; } SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->thread_mesh_local_prefix.s6_addr, ml_prefix_buf); INFO(PRI_SEGMENTED_IPv6_ADDR_SRP PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->thread_mesh_local_prefix.s6_addr, ml_prefix_buf), slash ? slash : ""); route_state->have_mesh_local_prefix = true; return; fail: route_state->have_mesh_local_prefix = false; return; } #endif // RA_TESTER void route_refresh_interface_list(route_state_t *route_state) { interface_t *interface; bool UNUSED have_active = false; // We sometimes do not get "interface down" notifications when moving from one WiFi SSID to the next. To detect that // this has happened, see if we go from nonzero IPv6 addresses to zero after scanning the interface addresses for (interface = route_state->interfaces; interface != NULL; interface = interface->next) { interface->old_num_ipv6_addresses = interface->num_ipv6_addresses; } ioloop_map_interface_addresses_here(route_state->srp_server, &route_state->interface_addresses, NULL, route_state, ifaddr_callback); for (interface = route_state->interfaces; interface; interface = interface->next) { #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) if (interface->is_thread) { partition_utun0_pick_listener_address(route_state); } #endif if (!interface->ineligible && !interface->inactive) { have_active = true; } if (!interface->ineligible && !interface->inactive && interface->num_ipv6_addresses == 0 && interface->old_num_ipv6_addresses != 0) { flush_routers(interface, 0); } } #ifndef RA_TESTER // Notice if we have lost or gained infrastructure. if (have_active && !route_state->have_non_thread_interface) { INFO("we have an active interface"); route_state->have_non_thread_interface = true; route_state->partition_can_advertise_service = true; partition_maybe_advertise_anycast_service(route_state); } else if (!have_active && route_state->have_non_thread_interface) { INFO("we no longer have an active interface"); route_state->have_non_thread_interface = false; route_state->partition_can_advertise_service = false; // Stop advertising the service, if we are doing so. partition_discontinue_all_srp_service(route_state); } #endif // RA_TESTER } #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) #if defined(POSIX_BUILD) static void wpan_reconnect_wakeup_callback(void *context) { route_state_t *route_state = context; if (route_state->wpan_reconnect_wakeup != NULL) { ioloop_wakeup_release(route_state->wpan_reconnect_wakeup); route_state->wpan_reconnect_wakeup = NULL; } // Attempt to restart the thread network... infrastructure_network_startup(context); } #endif // POSIX_BUILD static void attempt_wpan_reconnect(void *context) { route_state_t *route_state = context; #if defined(POSIX_BUILD) if (route_state->wpan_reconnect_wakeup == NULL) { route_state->wpan_reconnect_wakeup = ioloop_wakeup_create(); if (route_state->wpan_reconnect_wakeup == NULL) { ERROR("can't allocate wpan reconnect wait wakeup."); return; } INFO("delaying for ten seconds before attempt to reconnect to thread daemon."); ioloop_add_wake_event(route_state->wpan_reconnect_wakeup, NULL, wpan_reconnect_wakeup_callback, NULL, 10 * 1000); partition_state_reset(route_state); #endif } } static void cti_get_tunnel_name_callback(void *context, const char *name, cti_status_t status) { route_state_t *route_state = context; if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { INFO("disconnected"); attempt_wpan_reconnect(route_state); return; } INFO(PUB_S_SRP " %d", name != NULL ? name : "", status); if (status != kCTIStatus_NoError) { return; } route_state->num_thread_interfaces = 1; if (route_state->thread_interface_name != NULL) { free(route_state->thread_interface_name); } route_state->thread_interface_name = strdup(name); if (route_state->thread_interface_name == NULL) { ERROR("No memory to save thread interface name " PUB_S_SRP, name); return; } INFO("Thread interface at " PUB_S_SRP, route_state->thread_interface_name); partition_got_tunnel_name(route_state); } static void cti_get_role_callback(void *context, cti_network_node_type_t role, cti_status_t status) { route_state_t *route_state = context; bool am_thread_router = false; if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { INFO("disconnected"); attempt_wpan_reconnect(route_state); return; } if (status == kCTIStatus_NoError) { route_state->partition_last_role_change = ioloop_timenow(); if (role == kCTI_NetworkNodeType_Router || role == kCTI_NetworkNodeType_Leader) { am_thread_router = true; } INFO("role is: " PUB_S_SRP " (%d)\n ", am_thread_router ? "router" : "not router", role); } else { ERROR("cti_get_role_callback: nonzero status %d", status); } // Our thread role doesn't actually matter, but it's useful to report it in the logs. } static void cti_get_state_callback(void *context, cti_network_state_t state, cti_status_t status) { route_state_t *route_state = context; bool associated = false; if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { INFO("disconnected"); attempt_wpan_reconnect(context); return; } route_state->partition_last_state_change = ioloop_timenow(); if (status == kCTIStatus_NoError) { if ((state == kCTI_NCPState_Associated) || (state == kCTI_NCPState_Isolated) || (state == kCTI_NCPState_NetWake_Asleep) || (state == kCTI_NCPState_NetWake_Waking)) { associated = true; } INFO("state is: " PUB_S_SRP " (%d)\n ", associated ? "associated" : "not associated", state); } else { ERROR("cti_get_state_callback: nonzero status %d", status); } if (route_state->current_thread_state != state) { if (associated) { route_state->current_thread_state = state; partition_maybe_enable_services(route_state); // but probably not } else { route_state->current_thread_state = state; partition_disable_service(route_state); } } } static void re_evaluate_interfaces(route_state_t *route_state) { for (interface_t *interface = route_state->interfaces; interface != NULL; interface = interface->next) { interface_prefix_evaluate(interface); } partition_maybe_enable_services(route_state); } static void route_get_xpanid_callback(void *context, uint64_t new_xpanid, cti_status_t status) { route_state_t *route_state = context; if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { INFO("disconnected"); attempt_wpan_reconnect(route_state); return; } if (status == kCTIStatus_NoError) { if (route_state->partition_has_xpanid) { ERROR("Unexpected change to XPANID from %" PRIu64 " to %" PRIu64, route_state->srp_server->xpanid, new_xpanid); } else { INFO("XPANID is now %" PRIu64, new_xpanid); } } else { ERROR("nonzero status %d", status); return; } route_state->srp_server->xpanid = new_xpanid; route_state->partition_has_xpanid = true; in6addr_zero(&route_state->xpanid_prefix); route_state->xpanid_prefix.s6_addr[0] = 0xfd; for (int i = 1; i < 8; i++) { route_state->xpanid_prefix.s6_addr[i] = ((route_state->srp_server->xpanid >> ((8 - i) * 8)) & 0xFFU); } route_state->have_xpanid_prefix = true; #if SRP_FEATURE_REPLICATION if (route_state->srp_server->srp_replication_enabled) { INFO("start srp replication."); srpl_startup(route_state->srp_server); } #endif // SRP_FEATURE_REPLICATION re_evaluate_interfaces(route_state); } void adv_ctl_add_prefix(route_state_t *route_state, const uint8_t *const data) { if (route_state->omr_watcher != NULL) { omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher); omr_prefix_t *prefix = NULL; for (prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) { if (!memcmp(&prefix->prefix, data, BR_PREFIX_SLASH_64_BYTES)) { SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " already there", SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); break; } } if (prefix == NULL) { SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); INFO("adding prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); if (!omr_watcher_prefix_add(route_state->omr_watcher, (struct in6_addr *)data, BR_PREFIX_SLASH_64_BYTES, omr_prefix_priority_low)) { INFO("failed"); } } } } void adv_ctl_remove_prefix(route_state_t *route_state, const uint8_t *const data) { if (route_state->omr_watcher != NULL) { omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher); omr_prefix_t *prefix = NULL; for (prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) { if (!memcmp(&prefix->prefix, data, BR_PREFIX_SLASH_64_BYTES)) { break; } } if (prefix == NULL) { SEGMENTED_IPv6_ADDR_GEN_SRP(data, prefix_buf); INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " not present", SEGMENTED_IPv6_ADDR_PARAM_SRP(data, prefix_buf)); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); INFO("removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); if (!omr_watcher_prefix_remove(route_state->omr_watcher, data, BR_PREFIX_SLASH_64_BYTES)) { INFO("no prefix removed."); } } } } static void route_rloc16_callback(void *context, uint16_t rloc16, cti_status_t status) { route_state_t *route_state = context; if (status != kCTIStatus_NoError) { ERROR("%d", status); } else { route_state->srp_server->rloc16 = rloc16; route_state->have_rloc16 = true; INFO("server_state->rloc16 updated to %d", route_state->srp_server->rloc16); // whenever the local rloc16 is updated, we should re-evaluate if anycast // service should be advertised. partition_maybe_advertise_anycast_service(route_state); } } #endif // THREAD_BORDER_ROUTER && !RA_TESTER void infrastructure_network_startup(route_state_t *route_state) { INFO("Thread network started."); // ioloop_network_watcher_start(network_watch_event); #ifndef RA_TESTER set_thread_forwarding(); #endif } #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) static void route_omr_watcher_event(route_state_t *route_state, void *UNUSED context, omr_watcher_event_type_t event_type, omr_prefix_t *UNUSED prefixes, omr_prefix_t *UNUSED prefix) { // Whenever we get an update to the prefix list, we should check our interface addresses. if (event_type == omr_watcher_event_prefix_update_finished) { route_refresh_interface_list(route_state); int num_prefixes = 0; for (omr_prefix_t *prf = prefixes; prf != NULL; prf = prf->next) { num_prefixes++; } if (num_prefixes != route_state->num_thread_prefixes) { int old_num_prefixes = route_state->num_thread_prefixes; INFO("%d prefixes instead of %d, evaluating policy", num_prefixes, route_state->num_thread_prefixes); routing_policy_evaluate_all_interfaces(route_state, true); route_state->num_thread_prefixes = num_prefixes; if (old_num_prefixes == 0 && num_prefixes > 0) { INFO("thread prefix available, may advertise anycast"); partition_maybe_advertise_anycast_service(route_state); } if (old_num_prefixes > 0 && num_prefixes == 0) { INFO("all thread prefixes are gone, stop advertising anycast service"); partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number); } } } } static void thread_network_startup(route_state_t *route_state) { if (route_state->thread_network_shutting_down) { INFO("thread network still shutting down--canceling"); ioloop_cancel_wake_event(route_state->thread_network_shutdown_wakeup); route_state->thread_network_shutting_down = false; return; } int status = cti_get_state(route_state->srp_server, &route_state->thread_state_context, route_state, cti_get_state_callback, NULL); if (status == kCTIStatus_NoError) { status = cti_get_network_node_type(route_state->srp_server, &route_state->thread_role_context, route_state, cti_get_role_callback, NULL); } srp_server_t *server_state = route_state->srp_server; server_state->service_tracker = service_tracker_create(server_state); if (server_state->service_tracker != NULL) { service_tracker_callback_add(server_state->service_tracker, partition_service_set_changed, NULL, route_state); service_tracker_start(server_state->service_tracker); } if (status == kCTIStatus_NoError) { status = cti_get_tunnel_name(route_state->srp_server, route_state, cti_get_tunnel_name_callback, NULL); } if (status == kCTIStatus_NoError) { status = cti_get_extended_pan_id(route_state->srp_server, &route_state->thread_xpanid_context, route_state, route_get_xpanid_callback, NULL); } if (status == kCTIStatus_NoError) { status = cti_get_rloc16(route_state->srp_server, &route_state->thread_rloc16_context, route_state, route_rloc16_callback, NULL); } if (status == kCTIStatus_NoError) { status = cti_get_mesh_local_prefix(route_state->srp_server, route_state, route_get_mesh_local_prefix_callback, NULL); } if (status != kCTIStatus_NoError) { if (status == kCTIStatus_DaemonNotRunning) { attempt_wpan_reconnect(route_state); } else { ERROR("initial network setup failed"); } } #if SRP_FEATURE_NAT64 INFO("start nat64."); nat64_start(route_state); #endif route_state->omr_watcher = omr_watcher_create(route_state, attempt_wpan_reconnect); if (route_state->omr_watcher == NULL) { ERROR("omr_watcher create failed"); return; } route_state->omr_watcher_callback = omr_watcher_callback_add(route_state->omr_watcher, route_omr_watcher_event, NULL, route_state); if (route_state->omr_watcher_callback == NULL) { ERROR("omr_watcher_callback add failed"); return; } route_state->omr_publisher = omr_publisher_create(route_state, "main"); if (route_state->omr_publisher == NULL) { ERROR("omr_publisher create failed"); return; } omr_publisher_set_omr_watcher(route_state->omr_publisher, route_state->omr_watcher); omr_publisher_set_reconnect_callback(route_state->omr_publisher, attempt_wpan_reconnect); omr_publisher_start(route_state->omr_publisher); omr_watcher_start(route_state->omr_watcher); route_state->thread_network_running = true; } #endif // defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) #ifndef RA_TESTER static void thread_network_shutdown_wakeup_callback(void *context) { route_state_t *route_state = context; INFO("shutdown timer expired, shutting down."); thread_network_shutdown(route_state); } static void thread_network_shutdown_start(route_state_t *route_state) { if (route_state->thread_network_shutdown_wakeup == NULL) { route_state->thread_network_shutdown_wakeup = ioloop_wakeup_create(); } if (route_state->thread_network_shutdown_wakeup == NULL) { INFO("no memory for wakeup object"); thread_network_shutdown(route_state); } else { INFO("scheduling shutdown in ten seconds"); route_state->thread_network_shutting_down = true; ioloop_add_wake_event(route_state->thread_network_shutdown_wakeup, route_state, thread_network_shutdown_wakeup_callback, NULL, 10 * 1000); } } static void thread_network_shutdown(route_state_t *route_state) { // If we get an explicit shutdown after getting a "shut down if nothing improves", cancel the scheduled shutdown. // This code also runs when we're called from the shutdown wakeup callback and serves to cancel that state. if (route_state->thread_network_shutdown_wakeup != NULL) { ioloop_cancel_wake_event(route_state->thread_network_shutdown_wakeup); } route_state->thread_network_shutting_down = false; if (route_state->thread_state_context != NULL) { INFO("discontinuing state events"); cti_events_discontinue(route_state->thread_state_context); route_state->thread_state_context = NULL; } if (route_state->thread_role_context != NULL) { INFO("discontinuing role events"); cti_events_discontinue(route_state->thread_role_context); route_state->thread_role_context = NULL; } if (route_state->thread_route_context != NULL) { INFO("discontinuing route events"); cti_events_discontinue(route_state->thread_route_context); route_state->thread_route_context = NULL; } if (route_state->thread_xpanid_context != NULL) { INFO("discontinuing xpanid events"); cti_events_discontinue(route_state->thread_xpanid_context); route_state->thread_xpanid_context = NULL; } if (route_state->thread_rloc16_context != NULL) { INFO("discontinuing rloc16 events"); cti_events_discontinue(route_state->thread_rloc16_context); route_state->thread_rloc16_context = NULL; } if (route_state->thread_ml_prefix_connection != NULL) { INFO("discontinuing route events"); cti_events_discontinue(route_state->thread_ml_prefix_connection); route_state->thread_ml_prefix_connection = NULL; } srp_mdns_flush(route_state->srp_server); #if SRP_FEATURE_REPLICATION INFO("stop srp replication."); srpl_shutdown(route_state->srp_server); #endif #if SRP_FEATURE_NAT64 INFO("stop nat64."); nat64_stop(route_state); #endif partition_state_reset(route_state); route_state->thread_network_running = false; } #endif // RA_TESTER void infrastructure_network_shutdown(route_state_t *route_state) { interface_t *interface; #ifndef RA_TESTER if (route_state->thread_network_running) { thread_network_shutdown(route_state); } #endif INFO("Infrastructure network shutdown."); // Stop all activity on interfaces. for (interface = route_state->interfaces; interface; interface = interface->next) { interface_shutdown(interface); } // Whatever non-thread interface we may have had we just shut down, so mark it down so that we can // start it up later. route_state->have_non_thread_interface = false; } #ifndef RA_TESTER static void partition_state_reset(route_state_t *route_state) { if (route_state->omr_watcher) { if (route_state->omr_watcher_callback != NULL) { INFO("canceling omr watcher callback"); omr_watcher_callback_cancel(route_state->omr_watcher, route_state->omr_watcher_callback); route_state->omr_watcher_callback = NULL; } INFO("discontinuing omr watcher"); omr_watcher_cancel(route_state->omr_watcher); omr_watcher_release(route_state->omr_watcher); route_state->omr_watcher = NULL; } if (route_state->omr_publisher) { INFO("discontinuing omr publisher"); omr_publisher_cancel(route_state->omr_publisher); omr_publisher_release(route_state->omr_publisher); route_state->omr_publisher = NULL; } if (route_state->route_tracker) { INFO("discontinuing route tracker"); route_tracker_cancel(route_state->route_tracker); route_tracker_release(route_state->route_tracker); route_state->route_tracker = NULL; } srp_server_t *server_state = route_state->srp_server; if (server_state->service_tracker != NULL) { service_tracker_cancel(server_state->service_tracker); service_tracker_release(server_state->service_tracker); server_state->service_tracker = NULL; } route_state->current_thread_state = kCTI_NCPState_Uninitialized; route_state->partition_last_prefix_set_change = 0; route_state->partition_last_pref_id_set_change = 0; route_state->partition_last_role_change = 0; route_state->partition_last_state_change = 0; route_state->partition_settle_start = 0; route_state->partition_service_last_add_time = 0; route_state->partition_have_prefix_list = false; route_state->partition_have_pref_id_list = false; route_state->partition_tunnel_name_is_known = false; route_state->partition_can_advertise_service = false; route_state->partition_can_advertise_anycast_service = false; route_state->srp_server->srp_anycast_service_blocked = false; route_state->srp_server->srp_unicast_service_blocked = false; route_state->partition_can_provide_routing = false; route_state->partition_has_xpanid = false; route_state->partition_may_offer_service = false; route_state->partition_settle_satisfied = true; route_state->have_rloc16 = false; route_state->advertising_srp_anycast_service = false; if (route_state->partition_settle_wakeup != NULL) { ioloop_cancel_wake_event(route_state->partition_settle_wakeup); } if (route_state->partition_post_partition_wakeup != NULL) { ioloop_cancel_wake_event(route_state->partition_post_partition_wakeup); } if (route_state->partition_pref_id_wait_wakeup != NULL) { ioloop_cancel_wake_event(route_state->partition_pref_id_wait_wakeup); } if (route_state->partition_service_add_pending_wakeup != NULL) { ioloop_cancel_wake_event(route_state->partition_service_add_pending_wakeup); } if (route_state->partition_anycast_service_add_pending_wakeup != NULL) { ioloop_cancel_wake_event(route_state->partition_service_add_pending_wakeup); } if (route_state->service_set_changed_wakeup != NULL) { ioloop_cancel_wake_event(route_state->service_set_changed_wakeup); } } static void partition_proxy_listener_ready(void *context, uint16_t port) { srp_server_t *server_state = context; route_state_t *route_state = server_state->route_state; INFO("listening on port %d", port); route_state->srp_service_listen_port = port; if (route_state->have_non_thread_interface) { route_state->partition_can_advertise_service = true; partition_maybe_advertise_service(route_state); } else { partition_discontinue_srp_service(route_state); } } static void partition_srp_listener_canceled(comm_t *listener, void *context) { srp_server_t *server_state = context; route_state_t *route_state = server_state->route_state; INFO("listener is %p", listener); if (route_state->srp_listener == listener) { ioloop_comm_release(route_state->srp_listener); route_state->srp_listener = NULL; if (!server_state->srp_unicast_service_blocked) { partition_discontinue_srp_service(route_state); } } } static void partition_stop_srp_listener(route_state_t *route_state) { if (route_state->srp_listener != NULL) { INFO("discontinuing SRP service on port %d", route_state->srp_service_listen_port); ioloop_listener_cancel(route_state->srp_listener); ioloop_comm_release(route_state->srp_listener); route_state->srp_listener = NULL; } } void partition_start_srp_listener(route_state_t *route_state) { #define max_avoid_ports 100 uint16_t avoid_ports[max_avoid_ports]; int num_avoid_ports = 0; thread_service_t *service; for (service = service_tracker_services_get(route_state->srp_server->service_tracker); service != NULL; service = service->next) { if (service->service_type == unicast_service) { // Track the port regardless. if (num_avoid_ports < max_avoid_ports) { avoid_ports[num_avoid_ports] = (service->u.unicast.port[0] << 8) | (service->u.unicast.port[1]); num_avoid_ports++; } } } // Make sure we don't overwrite the listener without stopping it. partition_stop_srp_listener(route_state); INFO("starting listener."); route_state->srp_listener = srp_proxy_listen(avoid_ports, num_avoid_ports, NULL, partition_proxy_listener_ready, partition_srp_listener_canceled, NULL, NULL, route_state->srp_server); if (route_state->srp_listener == NULL) { ERROR("Unable to start SRP listener, so can't advertise it"); return; } } void partition_discontinue_srp_service(route_state_t *route_state) { partition_stop_srp_listener(route_state); // Won't match in6addr_zero(&route_state->srp_listener_ip_address); route_state->srp_service_listen_port = 0; // Stop advertising the service, if we are doing so. partition_stop_advertising_service(route_state); } void partition_discontinue_all_srp_service(route_state_t *route_state) { partition_discontinue_srp_service(route_state); partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number); } // An address on utun0 has changed. Evaluate what to do with our listener service. // This gets called from ifaddr_callback(). If we don't yet have a thread service configured, // it should be called for unchanged addresses as well as changed. static void partition_utun0_address_changed(route_state_t *route_state, const struct in6_addr *addr, enum interface_address_change change) { SEGMENTED_IPv6_ADDR_GEN_SRP(addr, addr_buf); // Is this the address we are currently using? if (!in6addr_compare(&route_state->srp_listener_ip_address, addr)) { route_state->seen_listener_address = true; // Did it go away? If so, drop the listener. if (change == interface_address_deleted) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": listener address removed.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); if (route_state->srp_listener != NULL) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": canceling listener on removed address.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); partition_discontinue_srp_service(route_state); } } else { // This should never happen. if (change == interface_address_added) { ERROR(PRI_SEGMENTED_IPv6_ADDR_SRP ": address we're listening on was added.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } else { INFO("still listening on " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } } // Nothing more to do for this address. return; } // No point in looking at addresses if we don't have prerequisites. if (!route_state->have_mesh_local_prefix || !route_state->have_non_thread_interface) { return; } // Otherwise, we don't care about deleted addresses, but added and existing addresses matter. if (change != interface_address_deleted) { // If this address isn't an address we're already listening on, check if it's an anycast address; if so, // skip it as a candidate to listen on. if (is_thread_mesh_synthetic_address(addr)) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": thread anycast address.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } // If it's not an anycast address, do an election on it against the previously-seen addresses in this // iteration of ioloop_map_interface_addresses(). Numerically lowest address wins. Note that this means // that a link-local address will always lose, and if we have any anycast address we at least have a // mesh-local address, and that's fine to use if it happens to win. If we could figure out what our // mesh-local prefix was, we'd actually prefer this address since it never changes. else { // Don't use the mesh-local prefix if (!in6prefix_compare(addr, &route_state->thread_mesh_local_prefix, 8)) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": is our mesh-local address, skipping", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } // RFC4297: Link-Scoped Unicast address range FE80::/10 else if (addr->s6_addr[0] == 0xfe && (addr->s6_addr[1] & 0xc0) == 0x80) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": is our link-local address, skipping", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } // If the address is not on the list of prefixes we know about, let's not use it. else if (route_state->omr_watcher == NULL || !omr_watcher_prefix_exists(route_state->omr_watcher, addr, 64)) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": is unknown, skipping", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } // Otherwise it's legit and we can use it else { if (!route_state->have_proposed_srp_listener_address || in6addr_compare(&route_state->proposed_srp_listener_address, addr) > 0) { if (route_state->have_proposed_srp_listener_address) { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": wins over previous winner.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } else { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": wins by being first.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } in6addr_copy(&route_state->proposed_srp_listener_address, addr); route_state->have_proposed_srp_listener_address = true; } } } } else { INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": removed.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); } } static void partition_utun0_pick_listener_address(route_state_t *route_state) { if (route_state->have_mesh_local_prefix && route_state->advertising_srp_anycast_service && route_state->have_non_thread_interface && route_state->have_proposed_srp_listener_address) { if (route_state->srp_listener == NULL) { SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->proposed_srp_listener_address, addr_buf); INFO("starting listener on" PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->proposed_srp_listener_address, addr_buf)); // Copy the winning proposed listener IP address to the listener IP address in6addr_copy(&route_state->srp_listener_ip_address, &route_state->proposed_srp_listener_address); // Set up a listener. route_state->srp_service_listen_port = 0; partition_start_srp_listener(route_state); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf); if (!route_state->seen_listener_address) { FAULT("didn't see listener address " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf)); } else { INFO("already listening on" PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf)); } } } else { INFO(PUB_S_SRP "advertising anycast service; " PUB_S_SRP " proposed listener address; " PUB_S_SRP " non-thread interface; " PUB_S_SRP " mesh-local prefix; " PUB_S_SRP " listener", route_state->advertising_srp_anycast_service ? "" : "not ", route_state->have_proposed_srp_listener_address ? "have" : "no", route_state->have_non_thread_interface ? "have" : "no", route_state->have_mesh_local_prefix ? "have" : "no", route_state->srp_listener != NULL ? "have" : "no"); // In common cases, if we are not advertising anycast service due to replication failure, // we can not advertise unicast either. One exception is that we manually block the anycast // service for testing purpose. Unicast service should not be affected in this case. if (route_state->srp_listener != NULL && !route_state->advertising_srp_anycast_service && !route_state->srp_server->srp_anycast_service_blocked) { SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf); INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": canceling listener.", SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf)); partition_discontinue_srp_service(route_state); } } // Clear all of the election state. route_state->seen_listener_address = false; route_state->have_proposed_srp_listener_address = false; in6addr_zero(&route_state->proposed_srp_listener_address); } static void partition_got_tunnel_name(route_state_t *route_state) { route_state->partition_tunnel_name_is_known = true; for (interface_t *interface = route_state->interfaces; interface; interface = interface->next) { if (!strcmp(interface->name, route_state->thread_interface_name)) { interface->is_thread = true; break; } } route_refresh_interface_list(route_state); } static void partition_remove_service_done(void *context, cti_status_t status) { route_state_t *route_state = context; INFO("%d", status); // Flush any advertisements we're currently doing, since the accessories that advertised them will // notice the service is gone and start advertising with a different service. #if defined(SRP_FEATURE_REPLICATION) if (!route_state->srp_server->srp_replication_enabled) { #endif srp_mdns_flush(route_state->srp_server); #if defined(SRP_FEATURE_REPLICATION) } #endif } static bool route_maybe_restart_service_tracker(route_state_t *route_state, int *reset, int *increment) { *reset = 0; (*increment)++; if (*increment > 5) { if (route_state->srp_server->service_tracker != NULL) { service_tracker_start(route_state->srp_server->service_tracker); } else { FAULT("service tracker not present when restarting."); } *increment = 0; return true; } return false; } static void partition_stop_advertising_service(route_state_t *route_state) { // This should remove any copy of the service that this BR is advertising. INFO("%" PRIu64 "/%x", THREAD_ENTERPRISE_NUMBER, THREAD_SRP_SERVER_OPTION); uint8_t service_info[] = { 0, 0, 0, 1 }; int status; if (route_maybe_restart_service_tracker(route_state, &route_state->times_advertised_unicast, &route_state->times_unadvertised_unicast)) { INFO("restarted service tracker."); } service_info[0] = THREAD_SRP_SERVER_OPTION & 255; status = cti_remove_service(route_state->srp_server, route_state, partition_remove_service_done, NULL, THREAD_ENTERPRISE_NUMBER, service_info, 1); if (status != kCTIStatus_NoError) { INFO("status %d", status); } route_state->advertising_srp_unicast_service = false; } void partition_stop_advertising_anycast_service(route_state_t *route_state, uint8_t sequence_number) { // This should remove any copy of the service that this BR is advertising. INFO("%" PRIu64 "/%x %x", THREAD_ENTERPRISE_NUMBER, THREAD_SRP_SERVER_ANYCAST_OPTION, sequence_number); uint8_t service_info[] = { 0, 0, 0, 1 }; int status; if (route_maybe_restart_service_tracker(route_state, &route_state->times_advertised_anycast, &route_state->times_unadvertised_anycast)) { INFO("restarted service tracker."); } service_info[0] = THREAD_SRP_SERVER_ANYCAST_OPTION & 255; service_info[1] = sequence_number; status = cti_remove_service(route_state->srp_server, route_state, partition_remove_service_done, NULL, THREAD_ENTERPRISE_NUMBER, service_info, 2); if (status != kCTIStatus_NoError) { INFO("status %d", status); } route_state->advertising_srp_anycast_service = false; if (route_state->route_tracker != NULL) { INFO("discontinuing route tracker"); route_tracker_cancel(route_state->route_tracker); route_tracker_release(route_state->route_tracker); route_state->route_tracker = NULL; } route_refresh_interface_list(route_state); } static void partition_add_service_callback(void *context, cti_status_t status) { route_state_t *UNUSED route_state = context; if (status != kCTIStatus_NoError) { INFO("status = %d", status); } else { INFO("status = %d", status); } } static void partition_start_advertising_service(route_state_t *route_state) { uint8_t service_info[] = {0, 0, 0, 1}; uint8_t server_info[18]; int ret; if (route_maybe_restart_service_tracker(route_state, &route_state->times_unadvertised_unicast, &route_state->times_advertised_unicast)) { INFO("restarted service tracker."); } memcpy(&server_info, &route_state->srp_listener_ip_address, 16); server_info[16] = (route_state->srp_service_listen_port >> 8) & 255; server_info[17] = route_state->srp_service_listen_port & 255; SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->srp_listener_ip_address.s6_addr, server_ip_buf); service_info[0] = THREAD_SRP_SERVER_OPTION & 255; INFO("%" PRIu64 "/%02x/" PRI_SEGMENTED_IPv6_ADDR_SRP ":%d" , THREAD_ENTERPRISE_NUMBER, service_info[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->srp_listener_ip_address.s6_addr, server_ip_buf), route_state->srp_service_listen_port); ret = cti_add_service(route_state->srp_server, route_state, partition_add_service_callback, NULL, THREAD_ENTERPRISE_NUMBER, service_info, 1, server_info, sizeof server_info); if (ret != kCTIStatus_NoError) { INFO("status %d", ret); } // Wait a while for the service add to be reflected in an event. partition_schedule_service_add_wakeup(route_state); route_state->advertising_srp_unicast_service = true; } static void partition_start_advertising_anycast_service(route_state_t *route_state) { uint8_t service_info[] = {0, 0, 0, 1}; int ret; if (route_maybe_restart_service_tracker(route_state, &route_state->times_unadvertised_anycast, &route_state->times_advertised_anycast)) { INFO("restarted service tracker."); } service_info[0] = THREAD_SRP_SERVER_ANYCAST_OPTION & 255; service_info[1] = route_state->thread_sequence_number; INFO("%" PRIu64 "/%02x/ %x", THREAD_ENTERPRISE_NUMBER, service_info[0], route_state->thread_sequence_number); ret = cti_add_service(route_state->srp_server, route_state, partition_add_service_callback, NULL, THREAD_ENTERPRISE_NUMBER, service_info, 2, NULL, 0); if (ret != kCTIStatus_NoError) { INFO("status %d", ret); } // Wait a while for the service add to be reflected in an event. partition_schedule_anycast_service_add_wakeup(route_state); route_state->advertising_srp_anycast_service = true; route_refresh_interface_list(route_state); if (route_state->route_tracker == NULL) { route_state->route_tracker = route_tracker_create(route_state, "main"); if (route_state->route_tracker == NULL) { ERROR("route_tracker create failed"); return; } route_tracker_set_reconnect_callback(route_state->route_tracker, attempt_wpan_reconnect); route_tracker_start(route_state->route_tracker); } else { INFO("route tracker already running."); } } static void partition_service_add_wakeup(void *context) { route_state_t *route_state = context; route_state->partition_service_last_add_time = 0; partition_maybe_advertise_service(route_state); } static void partition_anycast_service_add_wakeup(void *context) { route_state_t *route_state = context; route_state->partition_service_last_add_time = 0; partition_maybe_advertise_anycast_service(route_state); } static void partition_schedule_service_add_wakeup(route_state_t *route_state) { if (route_state->partition_service_add_pending_wakeup == NULL) { route_state->partition_service_add_pending_wakeup = ioloop_wakeup_create(); if (route_state->partition_service_add_pending_wakeup == NULL) { ERROR("Can't schedule service add pending wakeup: no memory!"); return; } } else { ioloop_cancel_wake_event(route_state->partition_service_add_pending_wakeup); } // Wait thirty seconds. ioloop_add_wake_event(route_state->partition_service_add_pending_wakeup, route_state, partition_service_add_wakeup, NULL, 30 * 1000); } static void partition_schedule_anycast_service_add_wakeup(route_state_t *route_state) { if (route_state->partition_anycast_service_add_pending_wakeup == NULL) { route_state->partition_anycast_service_add_pending_wakeup = ioloop_wakeup_create(); if (route_state->partition_anycast_service_add_pending_wakeup == NULL) { ERROR("Can't schedule anycast service add pending wakeup: no memory!"); return; } } else { ioloop_cancel_wake_event(route_state->partition_anycast_service_add_pending_wakeup); } // Wait thirty seconds. ioloop_add_wake_event(route_state->partition_anycast_service_add_pending_wakeup, route_state, partition_anycast_service_add_wakeup, NULL, 30 * 1000); } static void partition_maybe_advertise_service(route_state_t *route_state) { thread_service_t *service; int num_lower_services = 0; int num_other_services = 0; int num_legacy_services = 0; int i; int64_t last_add_time; bool advertising_service = false; // If we aren't ready to advertise a service, there's nothing to do. if (!route_state->partition_can_advertise_service) { INFO("no service to advertise yet."); return; } if (route_state->srp_server->srp_unicast_service_blocked) { INFO("service advertising is disabled."); return; } for (i = 0; i < 16; i++) { if (route_state->srp_listener_ip_address.s6_addr[i] != 0) { break; } } if (i == 16) { INFO("no listener."); return; } // The add service function requires a remove prior to the add, so if we are doing an add, we need to wait // for things to stabilize before allowing the removal of a service to trigger a re-evaluation. // Therefore, if we've done an add in the past ten seconds, wait ten seconds before trying another add. last_add_time = ioloop_timenow() - route_state->partition_service_last_add_time; INFO("last_add_time = %" PRId64, last_add_time); if (last_add_time < 10 * 1000) { schedule_wakeup: partition_schedule_service_add_wakeup(route_state); return; } // Count how many services are numerically lower than the listener address for (service = service_tracker_services_get(route_state->srp_server->service_tracker); service; service = service->next) { if (service->ignore || service->service_type != unicast_service) { continue; } if ((service->user || (route_state->have_rloc16 && service->rloc16 == route_state->srp_server->rloc16)) && (in6addr_compare(&service->u.unicast.address, &route_state->srp_listener_ip_address) || ((service->u.unicast.port[0] << 8) | service->u.unicast.port[1]) != route_state->srp_service_listen_port)) { thread_service_note("Rtr0", service, "is ours, but stale"); partition_stop_advertising_service(route_state); goto schedule_wakeup; } // See if host advertising this unicast service is also advertising an anycast service; if not, then this // unicast service doesn't count (much). bool anycast_present = false; for (thread_service_t *aservice = service_tracker_services_get(route_state->srp_server->service_tracker); aservice != NULL; aservice = aservice->next) { if (aservice->ignore || aservice->service_type != anycast_service) { continue; } if (service->rloc16 == aservice->rloc16) { anycast_present = true; break; } } if (!anycast_present) { num_legacy_services++; route_state->seen_legacy_service = true; continue; } int cmp = in6addr_compare(&service->u.unicast.address, &route_state->srp_listener_ip_address); SEGMENTED_IPv6_ADDR_GEN_SRP(&service->u.unicast.address, addr_buf); INFO(PUB_S_SRP PRI_SEGMENTED_IPv6_ADDR_SRP ": is " PUB_S_SRP " listener address.", anycast_present ? "legacy service " : "service ", SEGMENTED_IPv6_ADDR_PARAM_SRP(&service->u.unicast.address, addr_buf), cmp < 0 ? "less than" : cmp > 0 ? "greater than" : "equal to"); if (cmp < 0) { num_lower_services++; } else if (cmp == 0) { advertising_service = true; } else { num_other_services++; } } // We only want to advertise our service if there are no services being advertised on addresses that are lower than // ours. Also, if we notice that a service is being advertised by our rloc16 with a different IP address than the // listener address, it's a stale address, so remove it. If we have seen a legacy service, and there is only one // other non-legacy service, continue to advertise a second service, since cooperating services are preferable. if ((num_lower_services > 0 && !route_state->seen_legacy_service) || num_lower_services > 1) { if (advertising_service) { SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf); INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": stopping advertising unicast service.", SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf)); partition_stop_advertising_service(route_state); route_state->partition_service_last_add_time = ioloop_timenow(); } else { INFO("not advertising unicast service."); } } // If there is not some other service published, and we are not publishing, publish. If there is a legacy (no // anycast) service published, publish a second service so as to encourage the legacy service to withdraw, because // cooperating services are preferable to competing services. else if (num_other_services < 1 || (num_other_services < 2 && route_state->seen_legacy_service)) { if (num_legacy_services > 1 && num_other_services > 1) { ERROR("%d legacy services present!", num_legacy_services); } else if (!advertising_service) { SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf); INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": starting advertising unicast service.", SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf)); partition_start_advertising_service(route_state); route_state->partition_service_last_add_time = ioloop_timenow(); } else { INFO("already advertising unicast service."); } } // There is some other service published. else { INFO("another service is present, no need to advertise."); } } void partition_maybe_advertise_anycast_service(route_state_t *route_state) { int64_t last_add_time; bool publish = false; // If we aren't ready to advertise a service, there's nothing to do. if (!route_state->have_non_thread_interface) { INFO("no active interface."); return; } if (!route_state->partition_can_advertise_service) { INFO("service advertisements are blocked."); return; } if (!route_state->partition_can_advertise_anycast_service) { INFO("no service to advertise yet."); return; } if (route_state->srp_server->srp_anycast_service_blocked) { INFO("service advertising is disabled."); return; } if (route_state->num_thread_prefixes == 0) { INFO("OMR prefix is not yet advertised."); return; } // The add service function requires a remove prior to the add, so if we are doing an add, we need to wait // for things to stabilize before allowing the removal of a service to trigger a re-evaluation. // Therefore, if we've done an add in the past ten seconds, wait ten seconds before trying another add. last_add_time = ioloop_timenow() - route_state->partition_service_last_add_time; INFO("last_add_time = %" PRId64, last_add_time); if (last_add_time < 10 * 1000) { schedule_wakeup: partition_schedule_anycast_service_add_wakeup(route_state); return; } // Find the highest (two's complement math) sequence number uint8_t winning_seq = route_state->thread_sequence_number; for (thread_service_t *service = service_tracker_services_get(route_state->srp_server->service_tracker); service; service = service->next) { if (service->service_type != anycast_service) { continue; } struct thread_anycast_service *aservice = &service->u.anycast; // Eliminate stale services before doing anything else. if ((service->user || (route_state->have_rloc16 && service->rloc16 == route_state->srp_server->rloc16)) && (aservice->sequence_number != route_state->thread_sequence_number || !route_state->advertising_srp_anycast_service)) { thread_service_note("Rtr0", service, "is ours, but stale"); partition_stop_advertising_anycast_service(route_state, aservice->sequence_number); goto schedule_wakeup; } #ifdef PING_ANYCAST_SERVICE if (service->ignore) { continue; } route_ping_aservice(route_state, service); #endif uint8_t service_seq = aservice->sequence_number; int8_t distance = service_seq - winning_seq; if (distance > 0) { winning_seq = service_seq; } if (distance == -128) { if ((int8_t)service_seq > (int8_t)winning_seq) { winning_seq = service_seq; } } } if (winning_seq != route_state->thread_sequence_number) { INFO("our sequence number (0x%02x) loses the election to 0x%02x", route_state->thread_sequence_number, winning_seq); goto publication; } // Count how many services on our anycast sequence number have lower RLOCs int num_less = 0; for (thread_service_t *service = service_tracker_services_get(route_state->srp_server->service_tracker); service; service = service->next) { struct thread_anycast_service *aservice = &service->u.anycast; if (aservice->sequence_number == winning_seq) { if (service->rloc16 < route_state->srp_server->rloc16) { num_less++; } } } if (num_less >= MAX_ANYCAST_NUM) { INFO("our sequence number (0x%02x) wins, but there are %d other services published already", route_state->thread_sequence_number, num_less); goto publication; } INFO("our sequence number (0x%02x) wins, and there are %d (<5) other services published already", route_state->thread_sequence_number, num_less); publish = true; publication: if (publish) { if (!route_state->advertising_srp_anycast_service) { INFO("advertising our anycast service with sequence number 0x%02x", route_state->thread_sequence_number); partition_start_advertising_anycast_service(route_state); route_state->partition_service_last_add_time = ioloop_timenow(); } else { INFO("already advertising our anycast service with sequence number 0x%x", route_state->thread_sequence_number); } } else { if (!route_state->advertising_srp_anycast_service) { INFO("not advertising our anycast service with sequence number 0x%02x", route_state->thread_sequence_number); } else { INFO("withdrawing our anycast service advertisement with sequence number 0x%02x", route_state->thread_sequence_number); partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number); route_state->partition_service_last_add_time = ioloop_timenow(); } } } static void partition_service_set_changed_callback(void *context) { route_state_t *route_state = context; service_tracker_t *tracker = route_state->srp_server->service_tracker; // If we discover an advertised service with our rloc, but listening address or port does not // match, we need to remove it. for (thread_service_t *service = service_tracker_services_get(tracker); service != NULL; service = service->next) { if (service->ignore) { continue; } if (service->service_type == unicast_service) { if (service->user || (route_state->srp_server->have_rloc16 && service->rloc16 == route_state->srp_server->rloc16)) { uint16_t port = (service->u.unicast.port[0] << 8) | service->u.unicast.port[1]; if (in6addr_compare(&service->u.unicast.address, &route_state->srp_listener_ip_address) || port != route_state->srp_service_listen_port) { service_tracker_thread_service_note(tracker, service, "is ours, but stale"); partition_stop_advertising_service(route_state); service->ignore = true; } } } else if (service->service_type == anycast_service) { // If we discover an advertised service with our rloc, and either we aren't advertising an anycast service, // or the sequence number isn't the one we're supposed to be advertising, we need to remove it. if ((service->user || (route_state->srp_server->have_rloc16 && service->rloc16 == route_state->srp_server->rloc16)) && (!route_state->advertising_srp_anycast_service || service->u.anycast.sequence_number != route_state->thread_sequence_number)) { service_tracker_thread_service_note(tracker, service, "is ours, but stale"); partition_stop_advertising_anycast_service(route_state, service->u.anycast.sequence_number); service->ignore = true; } } } partition_maybe_advertise_service(route_state); partition_maybe_advertise_anycast_service(route_state); } static void partition_service_set_changed(void *context) { route_state_t *route_state = context; if (route_state->service_set_changed_wakeup == NULL) { route_state->service_set_changed_wakeup = ioloop_wakeup_create(); if (route_state->service_set_changed_wakeup == NULL) { ERROR("Can't schedule service list change wakeup: no memory!"); return; } } else { ioloop_cancel_wake_event(route_state->service_set_changed_wakeup); } int timeout = srp_random16() % 20000; // Randomly wait between zero and twenty seconds INFO("waiting %d milliseconds before processing service state change.", timeout); ioloop_add_wake_event(route_state->service_set_changed_wakeup, route_state, partition_service_set_changed_callback, NULL, timeout); } static void partition_maybe_enable_services(route_state_t *route_state) { bool am_associated = route_state->current_thread_state == kCTI_NCPState_Associated; if (am_associated) { bool restart = false; INFO("Enabling service, which was disabled because of the thread role or state."); route_state->partition_may_offer_service = true; route_state->partition_can_provide_routing = true; route_refresh_interface_list(route_state); routing_policy_evaluate_all_interfaces(route_state, true); if (route_state->omr_watcher == NULL) { if (route_state->omr_publisher != NULL) { omr_publisher_cancel(route_state->omr_publisher); omr_publisher_release(route_state->omr_publisher); route_state->omr_publisher = NULL; } route_state->omr_watcher = omr_watcher_create(route_state, attempt_wpan_reconnect); if (route_state->omr_watcher == NULL) { ERROR("omr_watcher create failed"); return; } route_state->omr_watcher_callback = omr_watcher_callback_add(route_state->omr_watcher, route_omr_watcher_event, NULL, route_state); if (route_state->omr_watcher_callback == NULL) { ERROR("omr_watcher_callback add failed"); return; } restart = true; } if (route_state->omr_publisher == NULL) { route_state->omr_publisher = omr_publisher_create(route_state, "main"); if (route_state->omr_publisher == NULL) { ERROR("omr_publisher create failed"); return; } omr_publisher_set_omr_watcher(route_state->omr_publisher, route_state->omr_watcher); omr_publisher_set_reconnect_callback(route_state->omr_publisher, attempt_wpan_reconnect); restart = true; } if (restart) { omr_publisher_start(route_state->omr_publisher); omr_watcher_start(route_state->omr_watcher); } } else { INFO("Not enabling service: " PUB_S_SRP, am_associated ? "associated" : "!associated"); } } static void partition_disable_service(route_state_t *route_state) { bool done_something = false; // When our node type or state is such that we should no longer be publishing a prefix, the NCP will // automatically remove the published prefix. In case this happens, we do not want to remember the // prefix as already having been published. So drop our recollection of the published // prefix; this will get cleaned up when the network comes back if there's an inconsistency. if (route_state->omr_publisher != NULL) { omr_publisher_cancel(route_state->omr_publisher); omr_publisher_release(route_state->omr_publisher); route_state->omr_publisher = NULL; done_something = true; } if (route_state->omr_watcher != NULL) { if (route_state->omr_watcher_callback != NULL) { omr_watcher_callback_cancel(route_state->omr_watcher, route_state->omr_watcher_callback); route_state->omr_watcher_callback = NULL; } omr_watcher_cancel(route_state->omr_watcher); omr_watcher_release(route_state->omr_watcher); route_state->omr_watcher = NULL; done_something = true; } // We want to always say something when we pass through this state. if (done_something) { INFO("did something"); } else { INFO("did nothing."); } route_state->partition_may_offer_service = false; route_state->partition_can_provide_routing = false; } void partition_block_anycast_service(route_state_t *route_state, bool block) { if (block) { if (!route_state->srp_server->srp_anycast_service_blocked) { route_state->srp_server->srp_anycast_service_blocked = block; partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number); } } else { if (route_state->srp_server->srp_anycast_service_blocked) { route_state->srp_server->srp_anycast_service_blocked = block; partition_maybe_advertise_anycast_service(route_state); } } } #endif // RA_TESTER #if SRP_FEATURE_LOCAL_DISCOVERY int route_get_current_infra_interface_index(void) { extern srp_server_t *srp_servers; if (srp_servers == NULL) { INFO("no SRP servers"); return -1; } route_state_t *route_state = srp_servers->route_state; if (route_state == NULL) { INFO("no route state"); return -1; } for (interface_t *interface = route_state->interfaces; interface != NULL; interface = interface->next) { if (!interface->inactive && !interface->is_thread) { return (int)interface->index; // real interface indexes are always positive integers } } return -1; } #endif // SRP_FEATURE_LOCAL_DISCOVERY #endif // STUB_ROUTER // Local Variables: // mode: C // tab-width: 4 // c-file-style: "bsd" // c-basic-offset: 4 // fill-column: 120 // indent-tabs-mode: nil // End: