| /* 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 <netinet/in.h> |
| #include <net/if.h> |
| #include <netinet6/in6_var.h> |
| #include <netinet6/nd6.h> |
| #include <net/if_media.h> |
| #include <sys/stat.h> |
| #else |
| #define _GNU_SOURCE |
| #include <netinet/in.h> |
| #include <fcntl.h> |
| #include <bsd/stdlib.h> |
| #include <net/if.h> |
| #endif |
| #include <sys/socket.h> |
| #include <sys/ioctl.h> |
| #include <net/route.h> |
| #include <netinet/icmp6.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <arpa/inet.h> |
| #if !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING |
| #ifndef LINUX |
| #include <sys/sysctl.h> |
| #endif // LINUX |
| #endif // !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <dns_sd.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| |
| #ifdef IOLOOP_MACOS |
| #include <xpc/xpc.h> |
| |
| #include <TargetConditionals.h> |
| #include <SystemConfiguration/SystemConfiguration.h> |
| #include <SystemConfiguration/SCPrivate.h> |
| #include <SystemConfiguration/SCNetworkConfigurationPrivate.h> |
| #include <SystemConfiguration/SCNetworkSignature.h> |
| #include <network_information.h> |
| |
| #include <CoreUtils/CoreUtils.h> |
| #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 (<MAX_INITIAL_RTR_ADVERT_INTERVAL) |
| interface_beacon_schedule(interface, 8000 + srp_random16() % 8000); |
| } else { |
| interface_beacon_schedule(interface, icmp_listener.unsolicited_interval); |
| } |
| interface->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<<i)) { |
| break; |
| } |
| --preflen; |
| } |
| inet_ntop(address->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 : "<null>", 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 : "<null>", 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: |