| /* route.c |
| * |
| * Copyright (c) 2019-2022 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 "adv-ctl-server.h" |
| #include "srp-crypto.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" |
| |
| #ifdef LINUX |
| #define CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG 1 |
| #endif |
| |
| struct icmp_listener { |
| io_t *io_state; |
| int sock; |
| uint32_t unsolicited_interval; |
| }; |
| |
| struct thread_prefix { |
| int ref_count; |
| thread_prefix_t *next; |
| struct in6_addr prefix; |
| int prefix_len; |
| bool user, ncp, stable; |
| bool previous_user, previous_ncp, previous_stable; |
| }; |
| |
| struct thread_pref_id { |
| int ref_count; |
| thread_pref_id_t *next; |
| uint8_t partition_id[4]; // Partition id on which this prefix is claimed |
| uint8_t prefix[5]; // 40-bit ULA prefix identifier (no need to keep the whole prefix) |
| bool user, ncp, stable; |
| bool previous_user, previous_ncp, previous_stable; |
| }; |
| |
| struct thread_service { |
| int ref_count; |
| thread_service_t *next; |
| uint8_t address[16]; // IPv6 address on which service is offered |
| uint8_t port[2]; // Port (network byte order) |
| bool user, ncp, stable; |
| bool previous_user, previous_ncp, previous_stable; |
| }; |
| |
| #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 |
| |
| icmp_listener_t icmp_listener; |
| 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 refresh_interface_list(route_state_t *route_state); |
| static void router_advertisement_send(interface_t *NONNULL interface, const struct in6_addr *destination); |
| static void neighbor_solicit_send(interface_t *interface, struct in6_addr *destination); |
| static void icmp_send(uint8_t *NONNULL message, size_t length, |
| interface_t *NONNULL interface, const struct in6_addr *NONNULL destination); |
| 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 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 interface_active_state_evaluate(interface_t *interface, bool active_known, bool active); |
| 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 partition_state_reset(route_state_t *route_state); |
| static void partition_unpublish_prefix(route_state_t *route_state, thread_prefix_t *NONNULL prefix); |
| static void partition_unpublish_adopted_prefix(route_state_t *route_state, bool wait); |
| static void partition_adopt_prefix(route_state_t *route_state, thread_prefix_t *NONNULL prefix); |
| static bool partition_prefix_is_present(route_state_t *route_state, struct in6_addr *prefix_addr, int length); |
| static bool partition_pref_id_is_present(route_state_t *route_state, struct in6_addr *NONNULL prefix_addr); |
| static thread_prefix_t *NULLABLE partition_find_lowest_valid_prefix(route_state_t *route_state); |
| static thread_pref_id_t *NULLABLE partition_find_lowest_valid_pref_id(route_state_t *route_state); |
| static void partition_pref_id_timeout(void *UNUSED NULLABLE context); |
| static void partition_post_election_wakeup(void *UNUSED NULLABLE context); |
| static void partition_post_partition_timeout(void *UNUSED NULLABLE context); |
| static void partition_utun0_address_changed(route_state_t *route_state, const struct in6_addr *NONNULL addr, enum interface_address_change change); |
| static bool partition_wait_for_prefix_settling(route_state_t *route_state, wakeup_callback_t NONNULL callback, uint64_t now); |
| static void partition_got_tunnel_name(route_state_t *route_state); |
| static void partition_prefix_set_changed(route_state_t *route_state); |
| static void partition_pref_id_set_changed(route_state_t *route_state); |
| static void partition_id_changed(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(route_state_t *route_state); |
| static void partition_maybe_enable_services(route_state_t *route_state); |
| static void partition_disable_service(route_state_t *route_state); |
| static void partition_schedule_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); |
| ret->name = strdup(name); |
| if (ret->name == NULL) { |
| ERROR("interface_create: no memory for name"); |
| RELEASE(ret, interface_finalize); |
| 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_finalize); |
| return NULL; |
| } |
| |
| ret->route_state = route_state; |
| ret->index = ifindex; |
| ret->inactive = true; |
| if (!strcmp(name, "lo") || !strcmp(name, "wpan0")) { |
| ret->ineligible = true; |
| } else { |
| ret->ineligible = false; |
| } |
| } |
| return ret; |
| } |
| |
| #ifndef RA_TESTER |
| static void |
| thread_prefix_finalize(thread_prefix_t *prefix) |
| { |
| free(prefix); |
| } |
| |
| #define thread_prefix_create(prefix, prefix_length) thread_prefix_create_(prefix, prefix_length, __FILE__, __LINE__) |
| static thread_prefix_t * |
| thread_prefix_create_(struct in6_addr *address, int prefix_length, const char *file, int line) |
| { |
| thread_prefix_t *prefix; |
| |
| prefix = calloc(1, (sizeof *prefix)); |
| if (prefix != NULL) { |
| memcpy(&prefix->prefix, address, 16); |
| prefix->prefix_len = prefix_length; |
| RETAIN(prefix); |
| } |
| return prefix; |
| } |
| |
| static void |
| thread_service_finalize(thread_service_t *service) |
| { |
| free(service); |
| } |
| |
| #define thread_service_create(address, port) thread_service_create_(address, port, __FILE__, __LINE__) |
| static thread_service_t * |
| thread_service_create_(uint8_t *address, uint8_t *port, const char *file, int line) |
| { |
| thread_service_t *service; |
| |
| service = calloc(1, sizeof(*service)); |
| if (service != NULL) { |
| memcpy(&service->address, address, 16); |
| memcpy(&service->port, port, 2); |
| RETAIN(service); |
| } |
| return service; |
| } |
| |
| static void |
| thread_pref_id_finalize(thread_pref_id_t *pref_id) |
| { |
| free(pref_id); |
| } |
| |
| #define thread_pref_id_create(partition_id, prefix) thread_pref_id_create_(partition_id, prefix, __FILE__, __LINE__) |
| static thread_pref_id_t * |
| thread_pref_id_create_(uint8_t *partition_id, uint8_t *prefix, const char *file, int line) |
| { |
| thread_pref_id_t *pref_id; |
| |
| pref_id = calloc(1, sizeof(*pref_id)); |
| if (pref_id != NULL) { |
| memcpy(&pref_id->partition_id, partition_id, 4); |
| memcpy(&pref_id->prefix, prefix, 5); |
| RETAIN(pref_id); |
| } |
| return pref_id; |
| } |
| #endif // RA_TESTER |
| |
| static void |
| icmp_message_free(icmp_message_t *message) |
| { |
| if (message->options != NULL) { |
| free(message->options); |
| } |
| if (message->wakeup != NULL) { |
| ioloop_cancel_wake_event(message->wakeup); |
| ioloop_wakeup_release(message->wakeup); |
| } |
| free(message); |
| } |
| |
| static void |
| icmp_message_dump(icmp_message_t *message, |
| const struct in6_addr * const source_address, const struct in6_addr * const destination_address) |
| { |
| link_layer_address_t *lladdr; |
| prefix_information_t *prefix_info; |
| route_information_t *route_info; |
| int i; |
| char retransmission_timer_buf[11]; // Maximum size of a uint32_t printed as decimal. |
| char *retransmission_timer = "infinite"; |
| |
| if (message->retransmission_timer != ND6_INFINITE_LIFETIME) { |
| snprintf(retransmission_timer_buf, sizeof(retransmission_timer_buf), "%" PRIu32, message->retransmission_timer); |
| retransmission_timer = retransmission_timer_buf; |
| } |
| |
| SEGMENTED_IPv6_ADDR_GEN_SRP(source_address->s6_addr, src_addr_buf); |
| SEGMENTED_IPv6_ADDR_GEN_SRP(destination_address->s6_addr, dst_addr_buf); |
| if (message->type == icmp_type_router_advertisement) { |
| INFO("router advertisement from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP |
| " hop_limit %d on " PUB_S_SRP ": checksum = %x " |
| "cur_hop_limit = %d flags = %x router_lifetime = %d reachable_time = %" PRIu32 |
| " retransmission_timer = " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), |
| message->hop_limit, message->interface->name, message->checksum, message->cur_hop_limit, message->flags, |
| message->router_lifetime, message->reachable_time, retransmission_timer); |
| } else if (message->type == icmp_type_router_solicitation) { |
| INFO("router solicitation from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP |
| " hop_limit %d on " PUB_S_SRP ": code = %d checksum = %x", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), |
| message->hop_limit, message->interface->name, |
| message->code, message->checksum); |
| } else { |
| INFO("icmp message from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on " |
| PUB_S_SRP ": type = %d code = %d checksum = %x", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), |
| message->hop_limit, message->interface->name, message->type, |
| message->code, message->checksum); |
| } |
| |
| for (i = 0; i < message->num_options; i++) { |
| icmp_option_t *option = &message->options[i]; |
| switch(option->type) { |
| case icmp_option_source_link_layer_address: |
| lladdr = &option->option.link_layer_address; |
| INFO(" source link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address)); |
| break; |
| case icmp_option_target_link_layer_address: |
| lladdr = &option->option.link_layer_address; |
| INFO(" destination link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address)); |
| break; |
| case icmp_option_prefix_information: |
| prefix_info = &option->option.prefix_information; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_info->prefix.s6_addr, prefix_buf); |
| INFO(" prefix info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %" PRIu32 " %" PRIu32, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_info->prefix.s6_addr, prefix_buf), prefix_info->length, |
| prefix_info->flags, prefix_info->valid_lifetime, prefix_info->preferred_lifetime); |
| break; |
| case icmp_option_route_information: |
| route_info = &option->option.route_information; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_info->prefix.s6_addr, router_prefix_buf); |
| INFO(" route info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_info->prefix.s6_addr, router_prefix_buf), route_info->length, |
| route_info->flags, route_info->route_lifetime); |
| break; |
| default: |
| INFO(" option type %d", option->type); |
| break; |
| } |
| } |
| } |
| |
| static bool |
| icmp_message_parse_options(icmp_message_t *message, uint8_t *icmp_buf, unsigned length, unsigned *offset) |
| { |
| uint8_t option_type, option_length_8; |
| unsigned option_length; |
| unsigned scan_offset = *offset; |
| icmp_option_t *option; |
| uint32_t reserved32; |
| prefix_information_t *prefix_information; |
| route_information_t *route_information; |
| int prefix_bytes; |
| |
| // Count the options and validate the lengths |
| message->num_options = 0; |
| while (scan_offset < length) { |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) { |
| return false; |
| } |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) { |
| return false; |
| } |
| if (option_length_8 == 0) { // RFC4191 section 4.6: The value 0 is invalid. |
| ERROR("icmp_option_parse: option type %d length 0 is invalid.", option_type); |
| return false; |
| } |
| if (scan_offset + option_length_8 * 8 - 2 > length) { |
| ERROR("icmp_option_parse: option type %d length %d is longer than remaining available space %u", |
| option_type, option_length_8 * 8, length - scan_offset + 2); |
| return false; |
| } |
| scan_offset += option_length_8 * 8 - 2; |
| message->num_options++; |
| } |
| // If there are no options, we're done. No options is valid, so return true. |
| if (message->num_options == 0) { |
| return true; |
| } |
| message->options = calloc(message->num_options, sizeof(*message->options)); |
| if (message->options == NULL) { |
| ERROR("No memory for icmp options."); |
| return false; |
| } |
| option = message->options; |
| while (*offset < length) { |
| scan_offset = *offset; |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) { |
| return false; |
| } |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) { |
| return false; |
| } |
| // We already validated the length in the previous pass. |
| option->type = option_type; |
| option_length = option_length_8 * 8; |
| |
| switch(option_type) { |
| case icmp_option_source_link_layer_address: |
| case icmp_option_target_link_layer_address: |
| // At this juncture we are assuming that everything we care about looks like an |
| // ethernet interface. So for this case, length should be 8. |
| if (option_length != 8) { |
| INFO("Ignoring unexpectedly long link layer address: %d", option_length); |
| // Don't store the option. |
| message->num_options--; |
| *offset += option_length; |
| continue; |
| } |
| option->option.link_layer_address.length = 6; |
| memcpy(option->option.link_layer_address.address, &icmp_buf[scan_offset], 6); |
| break; |
| case icmp_option_prefix_information: |
| prefix_information = &option->option.prefix_information; |
| // Only a length of 32 is valid. This is an invalid ICMP packet, not just misunderunderstood |
| if (option_length != 32) { |
| return false; |
| } |
| // prefix length 8 |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->length)) { |
| return false; |
| } |
| // flags 8a |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->flags)) { |
| return false; |
| } |
| // valid lifetime 32 |
| if (!dns_u32_parse(icmp_buf, length, &scan_offset, |
| &prefix_information->valid_lifetime)) { |
| return false; |
| } |
| // preferred lifetime 32 |
| if (!dns_u32_parse(icmp_buf, length, &scan_offset, |
| &prefix_information->preferred_lifetime)) { |
| return false; |
| } |
| // reserved2 32 |
| if (!dns_u32_parse(icmp_buf, length, &scan_offset, &reserved32)) { |
| return false; |
| } |
| // prefix 128 |
| memcpy(&prefix_information->prefix, &icmp_buf[scan_offset], 16); |
| break; |
| case icmp_option_route_information: |
| route_information = &option->option.route_information; |
| |
| // route length 8 |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->length)) { |
| return false; |
| } |
| switch(option_length) { |
| case 8: |
| prefix_bytes = 0; |
| break; |
| case 16: |
| prefix_bytes = 8; |
| break; |
| case 24: |
| prefix_bytes = 16; |
| break; |
| default: |
| ERROR("invalid route information option length %d for route length %d", |
| option_length, route_information->length); |
| return false; |
| } |
| // flags 8 |
| if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->flags)) { |
| return false; |
| } |
| // route lifetime 32 |
| if (!dns_u32_parse(icmp_buf, length, &scan_offset, &route_information->route_lifetime)) { |
| return false; |
| } |
| // route (64, 96 or 128) |
| if (prefix_bytes > 0) { |
| memcpy(&route_information->prefix, &icmp_buf[scan_offset], prefix_bytes); |
| } |
| memset(&((uint8_t *)&route_information->prefix)[prefix_bytes], 0, 16 - prefix_bytes); |
| break; |
| default: |
| case icmp_option_mtu: |
| case icmp_option_redirected_header: |
| // don't care |
| break; |
| } |
| *offset += option_length; |
| option++; |
| } |
| return true; |
| } |
| |
| |
| static void |
| interface_wakeup_finalize(void *context) |
| { |
| interface_t *interface = context; |
| interface->beacon_wakeup = NULL; |
| } |
| |
| static void |
| interface_deconfigure_finalize(void *context) |
| { |
| interface_t *interface = context; |
| interface->deconfigure_wakeup = NULL; |
| } |
| |
| 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 = (uint32_t)((interface->deprecate_deadline - now) / 1000); |
| // When we're deprecating the prefix, we don't actually want to make the preferred lifetime zero, because |
| // this will cause IP address flapping if we miss a router advertisement. |
| #define FIVE_MINUTES 5 * 60 |
| if (interface->preferred_lifetime > FIVE_MINUTES) { |
| interface->preferred_lifetime = FIVE_MINUTES; |
| } |
| 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, |
| interface_deconfigure_finalize, 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, interface_wakeup_finalize, 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; |
| } |
| |
| #ifdef FLUSH_STALE_ROUTERS |
| static void |
| flush_stale_routers(interface_t *interface, uint64_t now) |
| { |
| icmp_message_t *router, **p_router; |
| |
| // Flush stale routers. |
| for (p_router = &interface->routers; *p_router != NULL; ) { |
| router = *p_router; |
| if (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; |
| } |
| } |
| } |
| #endif // FLUSH_STALE_ROUTERS |
| |
| 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); |
| } |
| 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 |
| 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_stale_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_stale_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(icmp_message_t *router, prefix_information_t *prefix) |
| { |
| if (router->interface == NULL) { |
| FAULT("no interface"); |
| return false; |
| } |
| return ((prefix->flags & ND_OPT_PI_FLAG_ONLINK) && |
| ((prefix->flags & ND_OPT_PI_FLAG_AUTO) || |
| (router->interface->route_state->config_enable_dhcpv6_prefixes && (router->flags & ND_RA_FLAG_MANAGED))) && |
| prefix->preferred_lifetime > 0); |
| } |
| |
| 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; |
| if (prefix_usable(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 (memcmp(&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 = FIVE_MINUTES; |
| } 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 && |
| (!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); |
| } |
| } |
| |
| #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) { |
| 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(); |
| } |
| } |
| |
| static 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 && !memcmp(&message->source, |
| &iface->link_local, sizeof(message->source))) { |
| 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 && !memcmp(&message->source, &solicit->source, sizeof(message->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); |
| } |
| } |
| |
| static 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 && !memcmp(&message->source, |
| &iface->link_local, sizeof(message->source))) { |
| INFO("dropping router advertisement sent from this host."); |
| icmp_message_free(message); |
| return; |
| } |
| } |
| |
| // See if we've had other advertisements from this router. |
| int num_ras_this_router = 0; |
| 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 (!memcmp(&router->source, &message->source, sizeof(message->source))) { |
| int router_usable_prefixes = 0, message_usable_prefixes = 0; |
| int prefixes_withdrawn = 0; |
| int prefixes_added = 0; |
| int prefixes_unchanged = 0; |
| icmp_option_t *router_option = router->options; |
| int i, j; |
| |
| // Remember how many RAs we have from this router. |
| num_ras_this_router++; |
| |
| // Count the number of usable prefixes retained, withdrawn, and revived, as well as the total number of |
| // usable prefixes in the old RA. |
| for (i = 0; i < router->num_options; i++, router_option++) { |
| if (router_option->type == icmp_option_prefix_information) { |
| icmp_option_t *message_option = message->options; |
| prefix_information_t *router_prefix = &router_option->option.prefix_information; |
| bool router_usable = prefix_usable(router, router_prefix); |
| |
| if (router_usable) { |
| router_usable_prefixes++; |
| } |
| |
| for (j = 0; j < message->num_options; j++, message_option++) { |
| if (message_option->type == icmp_option_prefix_information) { |
| prefix_information_t *message_prefix = &message_option->option.prefix_information; |
| |
| // Same prefix? |
| if (router_prefix->length == message_prefix->length && |
| !memcmp(&router_prefix->prefix, &message_prefix->prefix, sizeof(router_prefix->prefix))) |
| { |
| // Is it still usable? |
| message_prefix->found = true; |
| bool message_usable = prefix_usable(message, message_prefix); |
| if (router_usable && !message_usable) { |
| prefixes_withdrawn++; |
| } else if (!router_usable && message_usable) { |
| prefixes_added++; // Added in the sense that it became usable. |
| } else { |
| prefixes_unchanged++; |
| } |
| } |
| } |
| } |
| } |
| } |
| // Count the number of /new/ usable prefixes added in the new RA, and the total number of usable prefixes in |
| // the new RA. |
| icmp_option_t *option = message->options; |
| for (i = 0; i < message->num_options; i++, option++) { |
| if (option->type == icmp_option_prefix_information) { |
| prefix_information_t *prefix = &option->option.prefix_information; |
| if (prefix_usable(message, prefix)) { |
| message_usable_prefixes++; |
| if (!prefix->found) { |
| prefixes_added++; |
| } |
| } |
| } |
| } |
| INFO("router_usable: %d message_usable: %d withdrawn: %d added: %d unchanged: %d", |
| router_usable_prefixes, message_usable_prefixes, |
| prefixes_withdrawn, prefixes_added, prefixes_unchanged); |
| if ( |
| // We have to discard the old RA if all its prefixes were withdrawn. |
| (router_usable_prefixes > 0 && prefixes_withdrawn == router_usable_prefixes) || |
| // We don't need the old RA if all the prefixes that weren't withdrawn are also in the new RA |
| // This is true even if there are no un-withdrawn prefixes in the new RA. |
| (router_usable_prefixes - prefixes_withdrawn == message_usable_prefixes - prefixes_added)) |
| { |
| message->next = router->next; |
| *rp = message; |
| icmp_message_free(router); |
| break; |
| } |
| // We need both otherwise, so just continue down the list. |
| } |
| } |
| // 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; |
| } |
| |
| // Limit the number of RAs we'll retain from an individual router to five (arbitrarily). This prevents weird patterns |
| // of prefix adds and withdrawals from causing the list to grow without bound. Because we lose information by doing this, |
| // it's possible that we could wind up advertising a usable prefix when we don't need to, but this is a safe failure |
| // mode. |
| #define MAX_RAS_PER_ROUTER 5 |
| if (num_ras_this_router > MAX_RAS_PER_ROUTER) { |
| for (rp = &message->interface->routers; *rp != NULL; ) { |
| router = *rp; |
| if (!memcmp(&router->source, &message->source, sizeof(message->source))) { |
| *rp = router->next; |
| icmp_message_free(router); |
| // This should always be true, but it's no problem if it's not. |
| if (--num_ras_this_router <= MAX_RAS_PER_ROUTER) { |
| break; |
| } |
| } else { |
| rp = &(*rp)->next; |
| } |
| } |
| } |
| |
| // 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; |
| |
| // Something may have changed, so do a policy recalculation for this interface |
| routing_policy_evaluate(message->interface, false); |
| } |
| |
| static 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 (!memcmp(&message->source, &router->source, sizeof(message->source))) { |
| 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; |
| return; |
| } else { |
| router->latest_na = ioloop_timenow(); |
| router->reached = true; |
| return; |
| } |
| } |
| } |
| return; |
| } |
| |
| static void |
| icmp_message(route_state_t *route_state, uint8_t *icmp_buf, unsigned length, int ifindex, int hop_limit, addr_t *src, addr_t *dest) |
| { |
| unsigned offset = 0; |
| uint32_t reserved32; |
| interface_t *interface; |
| icmp_message_t *message = calloc(1, sizeof(*message)); |
| if (message == NULL) { |
| ERROR("Unable to allocate icmp_message_t for parsing"); |
| return; |
| } |
| |
| message->source = src->sin6.sin6_addr; |
| message->destination = dest->sin6.sin6_addr; |
| message->hop_limit = hop_limit; |
| for (interface = route_state->interfaces; interface; interface = interface->next) { |
| if (interface->index == ifindex) { |
| message->interface = interface; |
| break; |
| } |
| } |
| message->received_time = ioloop_timenow(); |
| message->received_time_already_adjusted = false; |
| message->new_router = true; |
| message->route_state = route_state; |
| |
| if (message->interface == NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, src_buf); |
| SEGMENTED_IPv6_ADDR_GEN_SRP(message->destination.s6_addr, dst_buf); |
| INFO("ICMP message type %d from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP |
| " on interface index %d, which isn't listed.", |
| icmp_buf[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, src_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(message->destination.s6_addr, dst_buf), ifindex); |
| icmp_message_free(message); |
| return; |
| } |
| |
| if (length < sizeof (struct icmp6_hdr)) { |
| ERROR("Short ICMP message: length %d is shorter than ICMP header length %zd", length, sizeof(struct icmp6_hdr)); |
| icmp_message_free(message); |
| return; |
| } |
| INFO("length %d", length); |
| |
| // The increasingly innaccurately named dns parse functions will work fine for this. |
| if (!dns_u8_parse(icmp_buf, length, &offset, &message->type)) { |
| goto out; |
| } |
| if (!dns_u8_parse(icmp_buf, length, &offset, &message->code)) { |
| goto out; |
| } |
| // XXX check the checksum |
| if (!dns_u16_parse(icmp_buf, length, &offset, &message->checksum)) { |
| goto out; |
| } |
| switch(message->type) { |
| case icmp_type_router_advertisement: |
| if (!dns_u8_parse(icmp_buf, length, &offset, &message->cur_hop_limit)) { |
| goto out; |
| } |
| if (!dns_u8_parse(icmp_buf, length, &offset, &message->flags)) { |
| goto out; |
| } |
| if (!dns_u16_parse(icmp_buf, length, &offset, &message->router_lifetime)) { |
| goto out; |
| } |
| if (!dns_u32_parse(icmp_buf, length, &offset, &message->reachable_time)) { |
| goto out; |
| } |
| if (!dns_u32_parse(icmp_buf, length, &offset, &message->retransmission_timer)) { |
| goto out; |
| } |
| |
| if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) { |
| goto out; |
| } |
| icmp_message_dump(message, &message->source, &message->destination); |
| router_advertisement(message); |
| // router_advertisement() is given ownership of the message |
| return; |
| |
| case icmp_type_router_solicitation: |
| if (!dns_u32_parse(icmp_buf, length, &offset, &reserved32)) { |
| goto out; |
| } |
| if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) { |
| goto out; |
| } |
| icmp_message_dump(message, &message->source, &message->destination); |
| router_solicit(message); |
| // router_solicit() is given ownership of the message. |
| return; |
| |
| case icmp_type_neighbor_advertisement: |
| icmp_message_dump(message, &message->source, &message->destination); |
| neighbor_advertisement(message); |
| break; |
| |
| case icmp_type_neighbor_solicitation: |
| case icmp_type_echo_request: |
| case icmp_type_echo_reply: |
| case icmp_type_redirect: |
| break; |
| } |
| |
| out: |
| icmp_message_free(message); |
| return; |
| } |
| |
| #ifndef FUZZING |
| static |
| #endif |
| void |
| icmp_callback(io_t *NONNULL io, void *UNUSED context) |
| { |
| ssize_t rv; |
| uint8_t icmp_buf[1500]; |
| int ifindex = 0; |
| addr_t src, dest; |
| int hop_limit = 0; |
| |
| #ifndef FUZZING |
| rv = ioloop_recvmsg(io->fd, &icmp_buf[0], sizeof(icmp_buf), &ifindex, &hop_limit, &src, &dest); |
| #else |
| rv = read(io->fd, &icmp_buf, sizeof(icmp_buf)); |
| #endif |
| if (rv < 0) { |
| ERROR("icmp_callback: can't read ICMP message: " PUB_S_SRP, strerror(errno)); |
| return; |
| } |
| for (route_state_t *route_state = route_states; route_state != NULL; route_state = route_state->next) { |
| icmp_message(route_state, icmp_buf, (unsigned)rv, ifindex, hop_limit, &src, &dest); // rv will never be > sizeof(icmp_buf) |
| } |
| } |
| |
| #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); |
| 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); |
| } |
| |
| 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 |
| } |
| |
| #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 |
| |
| static void |
| cti_add_prefix_callback(void *context, cti_status_t status) |
| { |
| route_state_t *route_state = context; |
| interface_t *interface; |
| INFO("%d", status); |
| for (interface = route_state->interfaces; interface; interface = interface->next) { |
| if (!interface->inactive) { |
| interface_beacon_schedule(interface, 0); |
| } |
| } |
| } |
| |
| static thread_prefix_t * |
| get_advertised_thread_prefix(route_state_t *route_state) |
| { |
| if (route_state->published_thread_prefix != NULL) { |
| return route_state->published_thread_prefix; |
| } else { |
| return route_state->adopted_thread_prefix; |
| } |
| return NULL; |
| } |
| |
| static void |
| set_thread_prefix(route_state_t *route_state) |
| { |
| char addrbuf[INET6_ADDRSTRLEN]; |
| thread_prefix_t *advertised_thread_prefix = get_advertised_thread_prefix(route_state); |
| if (advertised_thread_prefix == NULL) { |
| ERROR("set_thread_prefix: no advertised thread prefix."); |
| return; |
| } |
| SEGMENTED_IPv6_ADDR_GEN_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf); |
| inet_ntop(AF_INET6, &advertised_thread_prefix->prefix, addrbuf, sizeof addrbuf); |
| #ifdef ADD_PREFIX_WITH_WPANCTL |
| char *args[] = { "add-prefix", "--stable", "--preferred", "--slaac", "--default-route", "--on-mesh", addrbuf }; |
| INFO("/usr/local/bin/wpanctl " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " PUB_S_SRP " " |
| PRI_SEGMENTED_IPv6_ADDR_SRP, args[0], args[1], args[2], args[3], args[4], args[5], |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf)); |
| route_state->thread_prefix_adder_process = ioloop_subproc("/usr/local/bin/wpanctl", args, 7, thread_prefix_done, |
| NULL, NULL); |
| if (route_state->thread_prefix_adder_process == NULL) { |
| ERROR("Unable to add thread interface prefix."); |
| } |
| #else |
| INFO("add_prefix(true, true, true, true, " PRI_SEGMENTED_IPv6_ADDR_SRP ")", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf)); |
| int status = cti_add_prefix(route_state, cti_add_prefix_callback, NULL, |
| &advertised_thread_prefix->prefix, advertised_thread_prefix->prefix_len, |
| true, true, true, true); |
| if (status != kCTIStatus_NoError) { |
| ERROR("Unable to add thread interface prefix."); |
| } |
| #endif |
| } |
| #endif // THREAD_BORDER_ROUTRER && !RA_TESTER |
| |
| static void |
| router_advertisement_send(interface_t *interface, const struct in6_addr *destination) |
| { |
| uint8_t *message; |
| dns_towire_state_t towire; |
| route_state_t *route_state = interface->route_state; |
| |
| // Thread blocks RAs so no point sending them. |
| if (interface->inactive |
| #ifndef RA_TESTER |
| || interface->is_thread |
| #endif |
| ) { |
| return; |
| } |
| |
| #define MAX_ICMP_MESSAGE 1280 |
| message = malloc(MAX_ICMP_MESSAGE); |
| if (message == NULL) { |
| ERROR("router_advertisement_send: unable to construct ICMP Router Advertisement: no memory"); |
| return; |
| } |
| |
| // Construct the ICMP header and options for each interface. |
| memset(&towire, 0, sizeof towire); |
| towire.p = message; |
| towire.lim = message + MAX_ICMP_MESSAGE; |
| |
| // Construct the ICMP header. |
| // We use the DNS message construction functions because it's easy; probably should just make |
| // the towire functions more generic. |
| dns_u8_to_wire(&towire, ND_ROUTER_ADVERT); // icmp6_type |
| dns_u8_to_wire(&towire, 0); // icmp6_code |
| dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). |
| dns_u8_to_wire(&towire, 0); // Hop limit, we don't set. |
| dns_u8_to_wire(&towire, 0); // Flags. We don't offer DHCP, so We set neither the M nor the O bit. |
| // We are not a home agent, so no H bit. Lifetime is 0, so Prf is 0. |
| #ifdef ROUTER_LIFETIME_HACK |
| dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime, hacked. This shouldn't ever be enabled. |
| #else |
| #ifdef RA_TESTER |
| // Advertise a default route on the simulated thread network |
| if (!strcmp(interface->name, route_state->thread_interface_name)) { |
| dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime for default route |
| } else { |
| #endif |
| dns_u16_to_wire(&towire, 0); // Router lifetime for non-default default route(s). |
| #ifdef RA_TESTER |
| } |
| #endif // RA_TESTER |
| #endif // ROUTER_LIFETIME_HACK |
| dns_u32_to_wire(&towire, 0); // Reachable time for NUD, we have no opinion on this. |
| dns_u32_to_wire(&towire, 0); // Retransmission timer, again we have no opinion. |
| |
| // Send Source link-layer address option |
| if (interface->have_link_layer_address) { |
| dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); |
| dns_u8_to_wire(&towire, 1); // length / 8 |
| dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); |
| INFO("advertising source lladdr " PRI_MAC_ADDR_SRP |
| " on " PUB_S_SRP, MAC_ADDR_PARAM_SRP(interface->link_layer), interface->name); |
| } |
| |
| #ifndef RA_TESTER |
| // Send MTU of 1280 for Thread? |
| if (interface->is_thread) { |
| dns_u8_to_wire(&towire, ND_OPT_MTU); |
| dns_u8_to_wire(&towire, 1); // length / 8 |
| dns_u32_to_wire(&towire, 1280); |
| INFO("advertising MTU of 1280 on " PUB_S_SRP, interface->name); |
| } |
| #endif |
| |
| // Send Prefix Information option if there's no IPv6 on the link. |
| if (interface->our_prefix_advertised && !interface->suppress_ipv6_prefix) { |
| dns_u8_to_wire(&towire, ND_OPT_PREFIX_INFORMATION); |
| dns_u8_to_wire(&towire, 4); // length / 8 |
| dns_u8_to_wire(&towire, 64); // On-link prefix is always 64 bits |
| dns_u8_to_wire(&towire, ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO); // On link, autoconfig |
| dns_u32_to_wire(&towire, interface->valid_lifetime); |
| dns_u32_to_wire(&towire, interface->preferred_lifetime); |
| dns_u32_to_wire(&towire, 0); // Reserved |
| dns_rdata_raw_data_to_wire(&towire, &interface->ipv6_prefix, sizeof interface->ipv6_prefix); |
| SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf); |
| INFO("advertising on-link prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf), interface->name); |
| |
| } |
| |
| #ifndef ND_OPT_ROUTE_INFORMATION |
| #define ND_OPT_ROUTE_INFORMATION 24 |
| #endif |
| // In principle we can either send routes to links that are reachable by this router, |
| // or just advertise a router to the entire ULA /48. In theory it doesn't matter |
| // which we do; if we support HNCP at some point we probably need to be specific, but |
| // for now being general is fine because we have no way to share a ULA. |
| // Unfortunately, some RIO implementations do not work with specific routes, so for now |
| // We are doing it the easy way and just advertising the /48. |
| #define SEND_INTERFACE_SPECIFIC_RIOS 1 |
| #ifdef SEND_INTERFACE_SPECIFIC_RIOS |
| |
| // If neither ROUTE_BETWEEN_NON_THREAD_LINKS nor RA_TESTER are defined, then we never want to |
| // send an RIO other than for the thread network prefix. |
| #if defined (ROUTE_BETWEEN_NON_THREAD_LINKS) || defined(RA_TESTER) |
| interface_t *ifroute; |
| // Send Route Information option for other interfaces. |
| for (ifroute = route_state->interfaces; ifroute; ifroute = ifroute->next) { |
| if (ifroute->inactive) { |
| continue; |
| } |
| if (want_routing(route_state) && |
| ifroute->our_prefix_advertised && |
| #ifdef SEND_ON_LINK_ROUTE |
| // In theory we don't want to send RIO for the on-link prefix, but there's this bug, see. |
| true && |
| #else |
| ifroute != interface && |
| #endif |
| #ifdef RA_TESTER |
| // For the RA tester, we don't need to send an RIO to the thread network because we're the |
| // default router for that network. |
| strcmp(interface->name, route_state->thread_interface_name) |
| #else |
| true |
| #endif |
| ) |
| { |
| dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION); |
| dns_u8_to_wire(&towire, 2); // length / 8 |
| dns_u8_to_wire(&towire, 64); // Interface prefixes are always 64 bits |
| dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another |
| dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes) |
| dns_rdata_raw_data_to_wire(&towire, &ifroute->ipv6_prefix, 8); // /64 requires 8 bytes. |
| SEGMENTED_IPv6_ADDR_GEN_SRP(ifroute->ipv6_prefix.s6_addr, ipv6_prefix_buf); |
| INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(ifroute->ipv6_prefix.s6_addr, ipv6_prefix_buf), |
| ifroute->name, interface->name); |
| } |
| } |
| #endif // ROUTE_BETWEEN_NON_THREAD_LINKS || RA_TESTER |
| |
| #ifndef RA_TESTER |
| // Send route information option for thread prefix |
| thread_prefix_t *advertised_thread_prefix = get_advertised_thread_prefix(route_state); |
| if (advertised_thread_prefix != NULL) { |
| dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION); |
| dns_u8_to_wire(&towire, 2); // length / 8 |
| dns_u8_to_wire(&towire, 64); // Interface prefixes are always 64 bits |
| dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another |
| dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes) |
| dns_rdata_raw_data_to_wire(&towire, &advertised_thread_prefix->prefix, 8); // /64 requires 8 bytes. |
| SEGMENTED_IPv6_ADDR_GEN_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf); |
| INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(advertised_thread_prefix->prefix.s6_addr, thread_prefix_buf), |
| route_state->thread_interface_name, interface->name); |
| } |
| #endif |
| #else |
| #ifndef SKIP_SLASH_48 |
| dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION); |
| dns_u8_to_wire(&towire, 3); // length / 8 |
| dns_u8_to_wire(&towire, 48); // ULA prefixes are always 48 bits |
| dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another |
| dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes) |
| dns_rdata_raw_data_to_wire(&towire, &route_state->srp_server->ula_prefix, 16); // /48 requires 16 bytes |
| #endif // SKIP_SLASH_48 |
| #endif // SEND_INTERFACE_SPECIFIC_RIOS |
| |
| if (towire.error) { |
| ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); |
| towire.error = 0; |
| } else { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(destination->s6_addr, destination_buf); |
| INFO("sending advertisement to " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(destination->s6_addr, destination_buf), |
| interface->name); |
| icmp_send(message, towire.p - message, interface, destination); |
| } |
| free(message); |
| } |
| |
| static void |
| router_solicit_send(interface_t *interface) |
| { |
| uint8_t *message; |
| dns_towire_state_t towire; |
| |
| // Thread blocks RSs so no point sending them. |
| if (interface->inactive |
| #ifndef RA_TESTER |
| || interface->is_thread |
| #endif |
| ) { |
| return; |
| } |
| |
| #define MAX_ICMP_MESSAGE 1280 |
| message = malloc(MAX_ICMP_MESSAGE); |
| if (message == NULL) { |
| ERROR("Unable to construct ICMP Router Advertisement: no memory"); |
| return; |
| } |
| |
| // Construct the ICMP header and options for each interface. |
| memset(&towire, 0, sizeof towire); |
| towire.p = message; |
| towire.lim = message + MAX_ICMP_MESSAGE; |
| |
| // Construct the ICMP header. |
| // We use the DNS message construction functions because it's easy; probably should just make |
| // the towire functions more generic. |
| dns_u8_to_wire(&towire, ND_ROUTER_SOLICIT); // icmp6_type |
| dns_u8_to_wire(&towire, 0); // icmp6_code |
| dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). |
| dns_u32_to_wire(&towire, 0); // Reserved32 |
| |
| // Send Source link-layer address option |
| if (interface->have_link_layer_address) { |
| dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); |
| dns_u8_to_wire(&towire, 1); // length / 8 |
| dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); |
| } |
| |
| if (towire.error) { |
| ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); |
| } else { |
| icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allrouters); |
| } |
| free(message); |
| } |
| |
| static void |
| neighbor_solicit_send(interface_t *interface, struct in6_addr *destination) |
| { |
| uint8_t *message; |
| dns_towire_state_t towire; |
| |
| #define MAX_ICMP_MESSAGE 1280 |
| message = malloc(MAX_ICMP_MESSAGE); |
| if (message == NULL) { |
| ERROR("Unable to construct ICMP Router Advertisement: no memory"); |
| return; |
| } |
| |
| // Construct the ICMP header and options for each interface. |
| memset(&towire, 0, sizeof towire); |
| towire.p = message; |
| towire.lim = message + MAX_ICMP_MESSAGE; |
| |
| // Construct the ICMP header. |
| // We use the DNS message construction functions because it's easy; probably should just make |
| // the towire functions more generic. |
| dns_u8_to_wire(&towire, ND_NEIGHBOR_SOLICIT); // icmp6_type |
| dns_u8_to_wire(&towire, 0); // icmp6_code |
| dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). |
| dns_u32_to_wire(&towire, 0); // Reserved32 |
| dns_rdata_raw_data_to_wire(&towire, destination, sizeof(*destination)); // Target address of solicit |
| |
| // Send Source link-layer address option |
| if (interface->have_link_layer_address) { |
| dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); |
| dns_u8_to_wire(&towire, 1); // length / 8 |
| dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); |
| } |
| |
| if (towire.error) { |
| ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); |
| } else { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(destination, dest_buf); |
| INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf)); |
| icmp_send(message, towire.p - message, interface, destination); |
| } |
| free(message); |
| } |
| |
| static void |
| icmp_send(uint8_t *message, size_t length, interface_t *interface, const struct in6_addr *destination) |
| { |
| #ifdef FUZZING |
| char buffer[length]; |
| memcpy(buffer, message, length); |
| return; |
| #endif |
| struct iovec iov; |
| struct in6_pktinfo *packet_info; |
| socklen_t cmsg_length = CMSG_SPACE(sizeof(*packet_info)) + CMSG_SPACE(sizeof (int)); |
| uint8_t *cmsg_buffer; |
| struct msghdr msg_header; |
| struct cmsghdr *cmsg_pointer; |
| int hop_limit = 255; |
| ssize_t rv; |
| struct sockaddr_in6 dest; |
| |
| // Make space for the control message buffer. |
| cmsg_buffer = calloc(1, cmsg_length); |
| if (cmsg_buffer == NULL) { |
| ERROR("Unable to construct ICMP Router Advertisement: no memory"); |
| return; |
| } |
| |
| // Send the message |
| memset(&dest, 0, sizeof(dest)); |
| dest.sin6_family = AF_INET6; |
| dest.sin6_scope_id = interface->index; |
| #ifndef NOT_HAVE_SA_LEN |
| dest.sin6_len = sizeof(dest); |
| #endif |
| msg_header.msg_namelen = sizeof(dest); |
| dest.sin6_addr = *destination; |
| |
| msg_header.msg_name = &dest; |
| iov.iov_base = message; |
| iov.iov_len = length; |
| msg_header.msg_iov = &iov; |
| msg_header.msg_iovlen = 1; |
| msg_header.msg_control = cmsg_buffer; |
| msg_header.msg_controllen = cmsg_length; |
| |
| // Specify the interface |
| cmsg_pointer = CMSG_FIRSTHDR(&msg_header); |
| cmsg_pointer->cmsg_level = IPPROTO_IPV6; |
| cmsg_pointer->cmsg_type = IPV6_PKTINFO; |
| cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(*packet_info)); |
| packet_info = (struct in6_pktinfo *)CMSG_DATA(cmsg_pointer); |
| memset(packet_info, 0, sizeof(*packet_info)); |
| packet_info->ipi6_ifindex = interface->index; |
| |
| // Router advertisements and solicitations have a hop limit of 255 |
| cmsg_pointer = CMSG_NXTHDR(&msg_header, cmsg_pointer); |
| cmsg_pointer->cmsg_level = IPPROTO_IPV6; |
| cmsg_pointer->cmsg_type = IPV6_HOPLIMIT; |
| cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(int)); |
| memcpy(CMSG_DATA(cmsg_pointer), &hop_limit, sizeof(hop_limit)); |
| |
| // Send it |
| rv = sendmsg(icmp_listener.io_state->fd, &msg_header, 0); |
| if (rv < 0) { |
| uint8_t *in6_addr_bytes = ((struct sockaddr_in6 *)(msg_header.msg_name))->sin6_addr.s6_addr; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_bytes, in6_addr_buf); |
| ERROR("icmp_send: sending " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface " PUB_S_SRP |
| " index %d: " PUB_S_SRP, message[0] == ND_ROUTER_SOLICIT ? "solicit" : "advertise", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_bytes, in6_addr_buf), |
| interface->name, interface->index, strerror(errno)); |
| } else if ((size_t)rv != iov.iov_len) { |
| ERROR("icmp_send: short send to interface " PUB_S_SRP ": %zd < %zd", interface->name, rv, iov.iov_len); |
| } |
| free(cmsg_buffer); |
| } |
| |
| 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_stale_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; |
| } |
| |
| memset(&ula_prefix, 0, sizeof(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_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_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 |
| } |
| } |
| |
| bool |
| start_icmp_listener(void) |
| { |
| int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); |
| int true_flag = 1; |
| #ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES |
| int false_flag = 0; |
| #endif |
| struct icmp6_filter filter; |
| ssize_t rv; |
| |
| if (sock < 0) { |
| ERROR("Unable to listen for icmp messages: " PUB_S_SRP, strerror(errno)); |
| close(sock); |
| return false; |
| } |
| |
| // Only accept router advertisements and router solicits. |
| ICMP6_FILTER_SETBLOCKALL(&filter); |
| ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); |
| ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); |
| ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); |
| rv = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); |
| if (rv < 0) { |
| ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); |
| close(sock); |
| return false; |
| } |
| |
| // We want a source address and interface index |
| rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &true_flag, sizeof(true_flag)); |
| if (rv < 0) { |
| ERROR("Can't set IPV6_RECVPKTINFO: " PUB_S_SRP ".", strerror(errno)); |
| close(sock); |
| return false; |
| } |
| |
| // We need to be able to reject RAs arriving from off-link. |
| rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &true_flag, sizeof(true_flag)); |
| if (rv < 0) { |
| ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); |
| close(sock); |
| return false; |
| } |
| |
| #ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES |
| // Prevent our router advertisements from updating our routing table. |
| rv = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof(false_flag)); |
| if (rv < 0) { |
| ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); |
| close(sock); |
| return false; |
| } |
| #endif |
| |
| icmp_listener.io_state = ioloop_file_descriptor_create(sock, NULL, NULL); |
| if (icmp_listener.io_state == NULL) { |
| ERROR("No memory for ICMP I/O structure."); |
| close(sock); |
| return false; |
| } |
| |
| // Beacon out a router advertisement every three minutes. |
| icmp_listener.unsolicited_interval = 3 * 60 * 1000; |
| ioloop_add_reader(icmp_listener.io_state, icmp_callback); |
| |
| return true; |
| } |
| |
| static void |
| interface_router_solicit_finalize(void *context) |
| { |
| interface_t *interface = context; |
| interface->router_solicit_wakeup = NULL; |
| } |
| |
| 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, interface_router_solicit_finalize, |
| 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, |
| interface_router_solicit_finalize, 128 + srp_random16() % 896); |
| } |
| |
| static void |
| icmp_interface_subscribe(interface_t *interface, bool added) |
| { |
| struct ipv6_mreq req; |
| int rv; |
| |
| if (icmp_listener.io_state == NULL) { |
| ERROR("Interface subscribe without ICMP listener."); |
| return; |
| } |
| |
| memset(&req, 0, sizeof req); |
| if (interface->index == -1) { |
| ERROR("icmp_interface_subscribe called before interface index fetch for " PUB_S_SRP, interface->name); |
| return; |
| } |
| |
| req.ipv6mr_multiaddr = in6addr_linklocal_allrouters; |
| req.ipv6mr_interface = interface->index; |
| rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req, |
| sizeof req); |
| if (rv < 0) { |
| ERROR("Unable to " PUB_S_SRP " all-routers multicast group on " PUB_S_SRP ": " PUB_S_SRP, |
| added ? "join" : "leave", interface->name, strerror(errno)); |
| return; |
| } else { |
| INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un", |
| interface->name); |
| } |
| |
| req.ipv6mr_multiaddr = in6addr_linklocal_allnodes; |
| req.ipv6mr_interface = interface->index; |
| rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req, |
| sizeof req); |
| if (rv < 0) { |
| ERROR("Unable to " PUB_S_SRP " all-nodes multicast group on " PUB_S_SRP ": " PUB_S_SRP, |
| added ? "join" : "leave", interface->name, strerror(errno)); |
| return; |
| } else { |
| INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un", |
| interface->name); |
| } |
| |
| } |
| |
| 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; |
| } |
| |
| static void |
| interface_active_state_evaluate(interface_t *interface, bool active_known, bool active) |
| { |
| route_state_t *route_state = interface->route_state; |
| INFO("evaluating interface active status - ifname: " PUB_S_SRP |
| ", active_known: " PUB_S_SRP ", active: " PUB_S_SRP ", inactive: " PUB_S_SRP, |
| interface->name, active_known ? "true" : "false", active ? "true" : "false", |
| interface->inactive ? "true" : "false"); |
| |
| if (active_known && !active) { |
| if (!interface->inactive) { |
| #ifndef RA_TESTER |
| bool active_infrastructure = false; |
| for (interface_t *scan = route_state->interfaces; scan != NULL; scan = scan->next) { |
| if (scan != interface && !scan->inactive && !scan->ineligible) { |
| active_infrastructure = true; |
| } |
| } |
| // Don't be a border router if there is no infrastructure interface left. |
| if (route_state->thread_network_running && !active_infrastructure && !interface->inactive && !interface->ineligible) { |
| INFO("interface " PUB_S_SRP " went away, and there is no other infrastructure interface: shutting down thread network", |
| interface->name); |
| |
| thread_network_shutdown(route_state); |
| } else { |
| INFO("interface " PUB_S_SRP " went away, " |
| PUB_S_SRP "thread " PUB_S_SRP "infra " PUB_S_SRP "inactive " PUB_S_SRP "ineligible.", |
| interface->name, |
| route_state->thread_network_running ? "" : "!", active_infrastructure ? "" : "!", |
| interface->inactive ? "" : "!", interface->ineligible ? "" : "!"); |
| } |
| #else // RA_TESTER |
| INFO("interface " PUB_S_SRP " went away.", interface->name); |
| #endif |
| // Set up the thread-local prefix |
| interface_prefix_evaluate(interface); |
| |
| // We need to reevaluate routing policy on the new primary interface now, because |
| // there may be no new event there to trigger one. |
| routing_policy_evaluate(interface, true); |
| |
| // Clean the slate. |
| icmp_interface_subscribe(interface, false); |
| interface_shutdown(interface); |
| |
| // Zero IPv4 addresses. |
| interface->num_ipv4_addresses = 0; |
| |
| #if !defined(RA_TESTER) && SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY && SRP_FEATURE_DYNAMIC_CONFIGURATION |
| // Clear the corresponding served_domain_t in dnssd-proxy that is associated with this removed interface. |
| delete_served_domain_by_interface_name(interface->name); |
| #endif |
| |
| INFO("interface went down - ifname: " PUB_S_SRP, interface->name); |
| } |
| } else if (active_known) { |
| if (interface->inactive) { |
| #ifndef RA_TESTER |
| bool active_infrastructure = false; |
| for (interface_t *scan = route_state->interfaces; scan != NULL; scan = scan->next) { |
| if (scan != interface && !scan->inactive && !scan->ineligible) { |
| active_infrastructure = true; |
| } |
| } |
| // If this is the first infrastructure interface to show up, start the thread network |
| if (!route_state->thread_network_running && |
| !active_infrastructure && interface->inactive && !interface->ineligible) |
| { |
| INFO("interface " PUB_S_SRP " showed up, and there is no other infrastructure interface: starting thread network", |
| interface->name); |
| thread_network_startup(route_state); |
| } else { |
| INFO("interface " PUB_S_SRP " showed up, " |
| PUB_S_SRP "thread " PUB_S_SRP "infra " PUB_S_SRP "inactive " PUB_S_SRP "ineligible.", |
| interface->name, |
| route_state->thread_network_running ? "" : "!", active_infrastructure ? "" : "!", |
| interface->inactive ? "" : "!", interface->ineligible ? "" : "!"); |
| } |
| #else // !RA_TESTER |
| INFO("interface " PUB_S_SRP " showed up.", interface->name); |
| #endif |
| #ifdef RA_TESTER |
| if (!strcmp(interface->name, route_state->thread_interface_name) || |
| !strcmp(interface->name, route_state->home_interface_name)) |
| { |
| #endif |
| // Zero IPv4 addresses. |
| interface->num_ipv4_addresses = 0; |
| |
| icmp_interface_subscribe(interface, true); |
| interface->inactive = false; |
| |
| interface_prefix_evaluate(interface); |
| if (want_routing(route_state)) { |
| INFO("starting router discovery"); |
| router_discovery_start(interface); |
| |
| // If we already have a thread prefix, trigger beaconing now. |
| if (route_state->published_thread_prefix != NULL || route_state->adopted_thread_prefix != NULL) { |
| interface_beacon_schedule(interface, 0); |
| } else { |
| INFO("No prefix on thread network, so not scheduling beacon."); |
| } |
| } else { |
| INFO("Can't provide routing, so not scheduling beacon."); |
| } |
| #ifdef RA_TESTER |
| } else { |
| INFO("skipping interface " PUB_S_SRP " because it's not home or thread.", interface->name); |
| } |
| #endif |
| } |
| } |
| } |
| |
| |
| static void |
| ifaddr_callback(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) { |
| 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); |
| } 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(context, name, address, mask, flags, change); |
| } |
| } else { // change == interface_address_removed |
| dnssd_proxy_ifaddr_callback(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_INET || address->sa.sa_family == AF_INET6) && |
| change == interface_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) { |
| memcpy(interface->link_layer, address->ether_addr.addr, 6); |
| interface->have_link_layer_address = true; |
| } |
| #endif |
| } |
| #if defined(POSIX_BUILD) |
| interface_active_state_evaluate(interface, true, true); |
| #endif |
| } |
| |
| static void |
| refresh_interface_list(route_state_t *route_state) |
| { |
| interface_t *interface; |
| bool UNUSED have_active = false; |
| ioloop_map_interface_addresses_here(&route_state->interface_addresses, NULL, route_state, ifaddr_callback); |
| for (interface = route_state->interfaces; interface; interface = interface->next) { |
| if (!interface->ineligible && !interface->inactive) { |
| have_active = true; |
| } |
| } |
| |
| #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; |
| } 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; |
| // Stop advertising the service, if we are doing so. |
| partition_discontinue_srp_service(route_state); |
| } |
| #endif // RA_TESTER |
| } |
| |
| |
| #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) |
| #if !defined(RA_TESTER) |
| #if defined(POSIX_BUILD) |
| static void |
| wpan_reconnect_wakeup_callback(void *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 |
| |
| static void |
| attempt_wpan_reconnect(route_state_t *route_state) |
| { |
| #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("attempt_wpan_reconnect: 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 |
| } |
| } |
| #endif // RA_TESTER |
| |
| 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 |
| cti_get_partition_id_callback(void *context, uint64_t partition_id, 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) { |
| INFO("Partition ID changed to %" PRIu64, partition_id); |
| // Partition ID is actually only 32 bits |
| route_state->thread_partition_id[0] = (uint8_t)((partition_id >> 24) & 255); |
| route_state->thread_partition_id[1] = (uint8_t)((partition_id >> 16) & 255); |
| route_state->thread_partition_id[2] = (uint8_t)((partition_id >> 8) & 255); |
| route_state->thread_partition_id[3] = (uint8_t)(partition_id & 255); |
| |
| partition_id_changed(route_state); |
| } else { |
| ERROR("nonzero status %d", status); |
| } |
| } |
| |
| 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 |
| cti_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->xpanid, new_xpanid); |
| } else { |
| INFO("XPANID is now %" PRIu64, new_xpanid); |
| } |
| } else { |
| ERROR("nonzero status %d", status); |
| return; |
| } |
| |
| route_state->xpanid = new_xpanid; |
| route_state->partition_has_xpanid = true; |
| memset(&route_state->xpanid_prefix, 0, sizeof(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->xpanid >> ((15 - i) * 8)) & 255); |
| } |
| route_state->have_xpanid_prefix = true; |
| re_evaluate_interfaces(route_state); |
| } |
| |
| static void |
| thread_service_note(thread_service_t *service, const char *event_description) |
| { |
| uint16_t port; |
| |
| port = (service->port[0] << 8) | service->port[1]; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(service->address, service_add_buf); |
| INFO("SRP service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, service_add_buf), |
| port, event_description); |
| } |
| |
| static void |
| thread_pref_id_note(thread_pref_id_t *pref_id, const char *event_description) |
| { |
| struct in6_addr addr; |
| |
| addr.s6_addr[0] = 0xfd; |
| memcpy(&addr.s6_addr[1], pref_id->prefix, 5); |
| memset(&addr.s6_addr[6], 0, 10); |
| SEGMENTED_IPv6_ADDR_GEN_SRP(addr.s6_addr, addr_buf); |
| INFO("pref:id " PRI_SEGMENTED_IPv6_ADDR_SRP ":%02x%02x%02x%02x " PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr.s6_addr, addr_buf), |
| pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2], pref_id->partition_id[3], |
| event_description); |
| } |
| |
| typedef struct state_debug_accumulator { |
| char change[20]; // " +stable +user +ncp" |
| char *p_change; |
| size_t left; |
| bool changed; |
| } accumulator_t; |
| |
| static void |
| accumulator_init(accumulator_t *accumulator) |
| { |
| memset(accumulator, 0, sizeof(*accumulator)); |
| accumulator->p_change = accumulator->change; |
| accumulator->left = sizeof(accumulator->change); |
| } |
| |
| static void |
| accumulate(accumulator_t *accumulator, bool previous, bool cur, const char *name) |
| { |
| size_t len; |
| if (previous != cur) { |
| snprintf(accumulator->p_change, accumulator->left, "%s%s%s", |
| accumulator->p_change == accumulator->change ? "" : " ", cur ? "+" : "-", name); |
| len = strlen(accumulator->p_change); |
| accumulator->p_change += len; |
| accumulator->left -= len; |
| accumulator->changed = true; |
| } |
| } |
| |
| static void |
| cti_service_list_callback(void *context, cti_service_vec_t *services, cti_status_t status) |
| { |
| route_state_t *route_state = context; |
| size_t i; |
| thread_service_t **pservice = &route_state->thread_services, *service = NULL; |
| thread_pref_id_t **ppref_id = &route_state->thread_pref_ids, *pref_id = NULL; |
| |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| attempt_wpan_reconnect(route_state); |
| return; |
| } |
| |
| if (status != kCTIStatus_NoError) { |
| ERROR("cti_service_list_callback: %d", status); |
| } else { |
| // Delete any SRP services that are not in the list provided by Thread. |
| while (*pservice != NULL) { |
| service = *pservice; |
| for (i = 0; i < services->num; i++) { |
| cti_service_t *cti_service = services->services[i]; |
| // Is this a valid SRP service? |
| if (IS_SRP_SERVICE(cti_service)) { |
| // Is this service still present? |
| if (!memcmp(&service->address, cti_service->server, 16) && |
| !memcmp(&service->port, &cti_service->server[16], 2)) { |
| break; |
| } |
| } |
| } |
| if (i == services->num) { |
| thread_service_note(service, "went away"); |
| *pservice = service->next; |
| RELEASE_HERE(service, thread_service_finalize); |
| service = NULL; |
| } else { |
| // We'll re-initialize these flags from the service list when we check for duplicates. |
| service->previous_user = service->user; |
| service->user = false; |
| service->previous_stable = service->stable; |
| service->stable = false; |
| service->previous_ncp = service->ncp; |
| service->ncp = false; |
| pservice = &service->next; |
| } |
| } |
| // On exit, pservice is pointing to the end-of-list pointer. |
| |
| // Delete any pref_id services that are not in the list provided by Thread. |
| while (*ppref_id != NULL) { |
| pref_id = *ppref_id; |
| for (i = 0; i < services->num; i++) { |
| cti_service_t *cti_service = services->services[i]; |
| // Is this an SRP service? |
| if (IS_PREF_ID_SERVICE(cti_service)) { |
| // Is this service still present? |
| if (!memcmp(&pref_id->partition_id, cti_service->server, 4) && |
| !memcmp(pref_id->prefix, &cti_service->server[4], 5)) |
| { |
| break; |
| } |
| } |
| } |
| if (i == services->num) { |
| thread_pref_id_note(pref_id, "went away"); |
| *ppref_id = pref_id->next; |
| RELEASE_HERE(pref_id, thread_pref_id_finalize); |
| } else { |
| // We'll re-initialize these flags from the service list when we check for duplicates. |
| pref_id->previous_user = pref_id->user; |
| pref_id->user = false; |
| pref_id->previous_stable = pref_id->stable; |
| pref_id->stable = false; |
| pref_id->previous_ncp = pref_id->ncp; |
| pref_id->ncp = false; |
| ppref_id = &pref_id->next; |
| } |
| } |
| // On exit, pservice is pointing to the end-of-list pointer. |
| |
| // Add any services that are not present. |
| for (i = 0; i < services->num; i++) { |
| cti_service_t *cti_service = services->services[i]; |
| if (IS_SRP_SERVICE(cti_service)) { |
| for (service = route_state->thread_services; service != NULL; service = service->next) { |
| if (!memcmp(&service->address, cti_service->server, 16) && |
| !memcmp(&service->port, &cti_service->server[16], 2)) { |
| break; |
| } |
| } |
| if (service == NULL) { |
| service = thread_service_create(cti_service->server, &cti_service->server[16]); |
| if (service == NULL) { |
| ERROR("cti_service_list_callback: no memory for service."); |
| } else { |
| thread_service_note(service, "showed up"); |
| *pservice = service; |
| pservice = &service->next; |
| } |
| } |
| // Also, since we're combing the list, update ncp, user and stable flags. Note that a service can |
| // appear more than once in the thread service list. |
| if (service != NULL) { |
| if (cti_service->flags & kCTIFlag_NCP) { |
| service->ncp = true; |
| } else { |
| service->user = true; |
| } |
| if (cti_service->flags & kCTIFlag_Stable) { |
| service->stable = true; |
| } |
| } |
| } else if (IS_PREF_ID_SERVICE(cti_service)) { |
| for (pref_id = route_state->thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) { |
| if (!memcmp(&pref_id->partition_id, cti_service->server, 4) && |
| !memcmp(pref_id->prefix, &cti_service->server[4], 5)) |
| { |
| break; |
| } |
| } |
| if (pref_id == NULL) { |
| pref_id = thread_pref_id_create(cti_service->server, &cti_service->server[4]); |
| if (pref_id == NULL) { |
| ERROR("cti_service_list_callback: no memory for pref_id."); |
| } else { |
| thread_pref_id_note(pref_id, "showed up"); |
| *ppref_id = pref_id; |
| ppref_id = &pref_id->next; |
| } |
| } |
| // Also, since we're combing the list, update ncp, user and stable flags. Note that a pref_id can |
| // appear more than once in the thread pref_id list. |
| if (pref_id != NULL) { |
| if (!pref_id->ncp && (cti_service->flags & kCTIFlag_NCP)) { |
| pref_id->ncp = true; |
| } else if (!pref_id->user && !(cti_service->flags & kCTIFlag_NCP)) { |
| pref_id->user = true; |
| } |
| if (cti_service->flags & kCTIFlag_Stable) { |
| pref_id->stable = true; |
| } |
| } |
| } |
| } |
| |
| accumulator_t accumulator; |
| for (service = route_state->thread_services; service != NULL; service = service->next) { |
| accumulator_init(&accumulator); |
| accumulate(&accumulator, service->previous_ncp, service->ncp, "ncp"); |
| accumulate(&accumulator, service->previous_stable, service->ncp, "stable"); |
| accumulate(&accumulator, service->previous_user, service->user, "user"); |
| if (accumulator.changed) { |
| thread_service_note(service, accumulator.change); |
| } |
| } |
| for (pref_id = route_state->thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) { |
| accumulator_init(&accumulator); |
| accumulate(&accumulator, pref_id->previous_ncp, pref_id->ncp, "ncp"); |
| accumulate(&accumulator, pref_id->previous_stable, pref_id->ncp, "stable"); |
| accumulate(&accumulator, pref_id->previous_user, pref_id->user, "user"); |
| if (accumulator.changed) { |
| thread_pref_id_note(pref_id, accumulator.change); |
| } |
| } |
| |
| // At this point the thread prefix list contains the same information as what we just received. |
| // Trigger a "prefix set changed" event. |
| partition_service_set_changed(route_state); |
| } |
| } |
| |
| void |
| adv_ctl_add_prefix(route_state_t *route_state, const uint8_t *const data) |
| { |
| thread_prefix_t **ppref = &route_state->thread_prefixes, *prefix = NULL; |
| |
| for (prefix = route_state->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) { |
| prefix = thread_prefix_create((struct in6_addr *)data, BR_PREFIX_SLASH_64_BYTES); |
| if (prefix == NULL) { |
| ERROR("no memory for prefix."); |
| } else { |
| 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)); |
| prefix->next = *ppref; |
| *ppref = prefix; |
| } |
| } |
| } |
| |
| void |
| adv_ctl_remove_prefix(route_state_t *route_state, const uint8_t *const data) |
| { |
| thread_prefix_t **ppref = &route_state->thread_prefixes, *prefix = NULL; |
| |
| while (*ppref != NULL) { |
| prefix = *ppref; |
| if (!memcmp(&prefix->prefix, data, BR_PREFIX_SLASH_64_BYTES)) { |
| *ppref = prefix->next; |
| 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)); |
| RELEASE_HERE(prefix, thread_prefix_finalize); |
| return; |
| } else { |
| ppref = &prefix->next; |
| } |
| } |
| INFO("no prefix removed."); |
| } |
| |
| static void |
| cti_prefix_list_callback(void *context, cti_prefix_vec_t *prefixes, cti_status_t status) |
| { |
| route_state_t *route_state = context; |
| size_t i; |
| thread_prefix_t **ppref = &route_state->thread_prefixes, *prefix = NULL; |
| |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| attempt_wpan_reconnect(route_state); |
| return; |
| } |
| |
| if (status != kCTIStatus_NoError) { |
| ERROR("cti_get_prefix_list_callback: %d", status); |
| } else { |
| // Delete any prefixes that are not in the list provided by Thread. |
| while (*ppref != NULL) { |
| prefix = *ppref; |
| for (i = 0; i < prefixes->num; i++) { |
| cti_prefix_t *cti_prefix = prefixes->prefixes[i]; |
| // Is this prefix still present? |
| if (!memcmp(&prefix->prefix, &cti_prefix->prefix, 8)) { |
| break; |
| } |
| } |
| if (i == prefixes->num) { |
| *ppref = prefix->next; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " went away", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); |
| RELEASE_HERE(prefix, thread_prefix_finalize); |
| } else { |
| // We'll re-initialize these flags from the prefix list when we check for duplicates. |
| prefix->user = false; |
| prefix->stable = false; |
| prefix->ncp = false; |
| ppref = &prefix->next; |
| } |
| } |
| // On exit, ppref is pointing to the end-of-list pointer. |
| |
| // Add any prefixes that are not present. |
| for (i = 0; i < prefixes->num; i++) { |
| cti_prefix_t *cti_prefix = prefixes->prefixes[i]; |
| for (prefix = route_state->thread_prefixes; prefix != NULL; prefix = prefix->next) { |
| if (!memcmp(&prefix->prefix, &cti_prefix->prefix, 16)) { |
| break; |
| } |
| } |
| if (prefix == NULL) { |
| prefix = thread_prefix_create(&cti_prefix->prefix, cti_prefix->prefix_length); |
| if (prefix == NULL) { |
| ERROR("cti_prefix_list_callback: no memory for prefix."); |
| } else { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " showed up", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); |
| *ppref = prefix; |
| ppref = &prefix->next; |
| } |
| } |
| // Also, since we're combing the list, update ncp, user and stable flags. Note that a prefix can |
| // appear more than once in the thread prefix list. |
| if (prefix != NULL) { |
| if (cti_prefix->flags & kCTIFlag_NCP) { |
| prefix->ncp = true; |
| } else { |
| prefix->user = true; |
| } |
| if (cti_prefix->flags & kCTIFlag_Stable) { |
| prefix->stable = true; |
| } |
| } |
| } |
| |
| // At this point the thread prefix list contains the same information as what we just received. |
| // Trigger a "prefix set changed" event. |
| partition_prefix_set_changed(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); |
| set_thread_forwarding(); |
| } |
| |
| #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) |
| static void |
| thread_network_startup(route_state_t *route_state) |
| { |
| int status = cti_get_state(&route_state->thread_state_context, route_state, cti_get_state_callback, NULL); |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_network_node_type(&route_state->thread_role_context, route_state, cti_get_role_callback, NULL); |
| } |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_service_list(&route_state->thread_service_context, route_state, cti_service_list_callback, NULL); |
| } |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_prefix_list(&route_state->thread_prefix_context, route_state, cti_prefix_list_callback, NULL); |
| } |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_tunnel_name(route_state, cti_get_tunnel_name_callback, NULL); |
| } |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_partition_id(&route_state->thread_partition_id_context, route_state, |
| cti_get_partition_id_callback, NULL); |
| } |
| if (status == kCTIStatus_NoError) { |
| status = cti_get_extended_pan_id(&route_state->thread_xpanid_context, route_state, cti_get_xpanid_callback, NULL); |
| } |
| if (status != kCTIStatus_NoError) { |
| if (status == kCTIStatus_DaemonNotRunning) { |
| attempt_wpan_reconnect(route_state); |
| } else { |
| ERROR("initial network setup failed"); |
| } |
| } |
| route_state->thread_network_running = true; |
| } |
| #endif // defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER) |
| |
| #ifndef RA_TESTER |
| static void |
| thread_network_shutdown(route_state_t *route_state) |
| { |
| // Stop publishing a default route on the Thread network. |
| if (route_state->adopted_thread_prefix != NULL) { |
| partition_unpublish_prefix(route_state, route_state->adopted_thread_prefix); |
| INFO("removing adopted thread prefix to remove."); |
| } else { |
| INFO("no adopted thread prefix to remove."); |
| } |
| if (route_state->published_thread_prefix != NULL) { |
| partition_unpublish_prefix(route_state, route_state->published_thread_prefix); |
| INFO("removing published thread prefix to remove."); |
| } else { |
| INFO("no published thread prefix to remove."); |
| } |
| |
| if (route_state->thread_state_context) { |
| INFO("discontinuing state events"); |
| cti_events_discontinue(route_state->thread_state_context); |
| route_state->thread_state_context = NULL; |
| } |
| if (route_state->thread_role_context) { |
| INFO("discontinuing role events"); |
| cti_events_discontinue(route_state->thread_role_context); |
| route_state->thread_role_context = NULL; |
| } |
| if (route_state->thread_service_context) { |
| INFO("discontinuing service events"); |
| cti_events_discontinue(route_state->thread_service_context); |
| route_state->thread_service_context = NULL; |
| } |
| if (route_state->thread_prefix_context) { |
| INFO("discontinuing prefix events"); |
| cti_events_discontinue(route_state->thread_prefix_context); |
| route_state->thread_prefix_context = NULL; |
| } |
| if (route_state->thread_partition_id_context) { |
| INFO("discontinuing partition ID events"); |
| cti_events_discontinue(route_state->thread_partition_id_context); |
| route_state->thread_partition_id_context = NULL; |
| } |
| partition_state_reset(route_state); |
| } |
| #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); |
| } |
| } |
| |
| #ifndef RA_TESTER |
| static void |
| partition_state_reset(route_state_t *route_state) |
| { |
| thread_prefix_t *prefix, *next_prefix = NULL; |
| thread_service_t *service, *next_service = NULL; |
| thread_pref_id_t *pref_id, *next_pref_id = NULL; |
| |
| // Remove any saved state from the thread network. |
| for (prefix = route_state->thread_prefixes; prefix != NULL; prefix = next_prefix) { |
| next_prefix = prefix->next; |
| RELEASE_HERE(prefix, thread_prefix_finalize); |
| } |
| route_state->thread_prefixes = NULL; |
| |
| if (route_state->published_thread_prefix != NULL) { |
| RELEASE_HERE(route_state->published_thread_prefix, thread_prefix_finalize); |
| route_state->published_thread_prefix = NULL; |
| } |
| if (route_state->adopted_thread_prefix != NULL) { |
| RELEASE_HERE(route_state->adopted_thread_prefix, thread_prefix_finalize); |
| route_state->adopted_thread_prefix = NULL; |
| } |
| |
| for (service = route_state->thread_services; service != NULL; service = next_service) { |
| next_service = service->next; |
| RELEASE_HERE(service, thread_service_finalize); |
| } |
| route_state->thread_services = NULL; |
| |
| for (pref_id = route_state->thread_pref_ids; pref_id != NULL; pref_id = next_pref_id) { |
| next_pref_id = pref_id->next; |
| RELEASE_HERE(pref_id, thread_pref_id_finalize); |
| } |
| route_state->thread_pref_ids = 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_partition_id_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_id_is_known = false; |
| 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_provide_routing = false; |
| route_state->partition_has_xpanid = false; |
| route_state->partition_may_offer_service = false; |
| route_state->partition_settle_satisfied = true; |
| |
| 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); |
| } |
| |
| route_state->thread_network_running = false; |
| } |
| |
| static int UNUSED |
| prefcmp(uint8_t *a, uint8_t *b, int len) |
| { |
| int i; |
| for (i = 0; i < len; i++) { |
| if (a[i] < b[i]) return -1; |
| if (a[i] > b[i]) return 1; |
| } |
| return 0; |
| } |
| |
| static void |
| partition_prefix_remove_callback(void *context, cti_status_t status) |
| { |
| route_state_t *UNUSED route_state = context; |
| if (status != kCTIStatus_NoError) { |
| ERROR("partition_prefix_remove_callback: failed to unpublish my prefix: %d.", status); |
| } else { |
| INFO("done unpublishing my prefix."); |
| } |
| } |
| |
| static void |
| partition_stop_advertising_pref_id_done(void *UNUSED context, cti_status_t status) |
| { |
| route_state_t *UNUSED route_state = context; |
| INFO("%d", status); |
| } |
| |
| void |
| partition_stop_advertising_pref_id(route_state_t *route_state) |
| { |
| // This should remove any copy of the service that this BR is advertising. |
| uint8_t service_info[] = { 0, 0, 0, 1 }; |
| int status; |
| |
| INFO("%" PRIu64 "/%02x" , THREAD_ENTERPRISE_NUMBER, service_info[0]); |
| service_info[0] = THREAD_PREF_ID_OPTION & 255; |
| status = cti_remove_service(route_state, partition_stop_advertising_pref_id_done, |
| NULL, THREAD_ENTERPRISE_NUMBER, service_info, 1); |
| if (status != kCTIStatus_NoError) { |
| INFO("status %d", status); |
| } |
| } |
| |
| static void |
| partition_advertise_pref_id_done(void *context, cti_status_t status) |
| { |
| route_state_t *UNUSED route_state = context; |
| INFO("%d", status); |
| } |
| |
| static void |
| partition_advertise_pref_id(route_state_t *route_state, uint8_t *prefix) |
| { |
| // This should remove any copy of the service that this BR is advertising. |
| uint8_t service_info[] = { 0, 0, 0, 1 }; |
| uint8_t pref_id[9]; |
| memcpy(pref_id, route_state->thread_partition_id, 4); |
| memcpy(&pref_id[4], prefix, 5); |
| uint8_t full_prefix[6] = {0xfd, prefix[0], prefix[1], prefix[2], prefix[3], prefix[4]}; |
| |
| service_info[0] = THREAD_PREF_ID_OPTION & 255; |
| IPv6_PREFIX_GEN_SRP(full_prefix, sizeof(full_prefix), prefix_buf); |
| INFO("%" PRIu64 "/%02x/%02x%02x%02x%02x" PRI_IPv6_PREFIX_SRP, |
| THREAD_ENTERPRISE_NUMBER, service_info[0], pref_id[0], pref_id[1], pref_id[2], pref_id[3], |
| IPv6_PREFIX_PARAM_SRP(prefix_buf)); |
| int status = cti_add_service(route_state, partition_advertise_pref_id_done, NULL, |
| THREAD_ENTERPRISE_NUMBER, service_info, 1, pref_id, sizeof pref_id); |
| if (status != kCTIStatus_NoError) { |
| INFO("status %d", status); |
| } |
| } |
| |
| static void |
| partition_id_update(route_state_t *route_state) |
| { |
| thread_prefix_t *advertised_prefix = get_advertised_thread_prefix(route_state); |
| if (advertised_prefix == NULL) { |
| INFO("no advertised prefix, not advertising pref:id."); |
| } else if (advertised_prefix == route_state->adopted_thread_prefix) { |
| INFO("not advertising pref:id for adopted prefix."); |
| partition_stop_advertising_pref_id(route_state); |
| } else { |
| partition_advertise_pref_id(route_state, ((uint8_t *)&advertised_prefix->prefix) + 1); |
| INFO("advertised pref:id for our prefix."); |
| } |
| } |
| |
| static void |
| partition_unpublish_prefix(route_state_t *route_state, thread_prefix_t *prefix) |
| { |
| cti_status_t status = cti_remove_prefix(route_state, partition_prefix_remove_callback, NULL, |
| &prefix->prefix, 64); |
| if (status != kCTIStatus_NoError) { |
| ERROR("partition_unpublish_prefix: prefix remove failed: %d.", status); |
| } |
| partition_stop_advertising_pref_id(route_state); |
| } |
| |
| static void |
| partition_refresh_and_re_evaluate(route_state_t *route_state) |
| { |
| refresh_interface_list(route_state); |
| routing_policy_evaluate_all_interfaces(route_state, true); |
| } |
| |
| typedef struct unadvertised_prefix_remove_state unadvertised_prefix_remove_state_t; |
| struct unadvertised_prefix_remove_state { |
| int ref_count; |
| int num_unadvertised_prefixes; |
| int num_removals; |
| route_state_t *route_state; |
| void (*continuation)(route_state_t *route_state); |
| }; |
| |
| static void |
| unadvertised_prefix_remove_state_finalize(unadvertised_prefix_remove_state_t *state) |
| { |
| void (*continuation)(route_state_t *route_state) = state->continuation; |
| route_state_t *route_state = state->route_state; |
| free(state); |
| if (continuation != NULL) { |
| continuation(route_state); |
| } else { |
| INFO("no continuation."); |
| } |
| } |
| |
| static void |
| partition_remove_all_prefixes_done(void *context, cti_status_t status) |
| { |
| unadvertised_prefix_remove_state_t *state = context; |
| state->num_removals++; |
| if (state->num_removals == state->num_unadvertised_prefixes) { |
| INFO("DONE: status = %d num_removals = %d num_unadvertised = %d", |
| status, state->num_removals, state->num_unadvertised_prefixes); |
| } else { |
| INFO("!DONE: status = %d num_removals = %d num_unadvertised = %d", |
| status, state->num_removals, state->num_unadvertised_prefixes); |
| } |
| #ifndef __clang_analyzer__ // clang_analyzer is unable to follow the reference through the cti code. |
| RELEASE_HERE(state, unadvertised_prefix_remove_state_finalize); |
| #endif |
| } |
| |
| static void |
| partition_remove_all_unwanted_prefixes_inner(unadvertised_prefix_remove_state_t *state, |
| thread_prefix_t *prefix, bool check) |
| { |
| route_state_t *route_state = state->route_state; |
| // Retain a copy of state for the partition_remove_all_prefixes_done callback, which is either called |
| // when the remove finishes or directly if we decide not to remove this prefix. |
| RETAIN_HERE(state); |
| |
| // Don't unpublish the adopted or published prefix. |
| if (!check || |
| ((route_state->published_thread_prefix == NULL || |
| memcmp(&route_state->published_thread_prefix->prefix, &prefix->prefix, 8)) && |
| (route_state->adopted_thread_prefix == NULL || |
| memcmp(&route_state->adopted_thread_prefix->prefix, &prefix->prefix, 8)))) |
| { |
| 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)); |
| cti_status_t status = cti_remove_prefix(state, partition_remove_all_prefixes_done, |
| NULL, &prefix->prefix, 64); |
| if (status != kCTIStatus_NoError) { |
| partition_remove_all_prefixes_done(state, status); |
| } |
| } else { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("not removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " because it's the " PUB_S_SRP " prefix", |
| route_state->published_thread_prefix == NULL ? "adopted" : "published", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); |
| // Do the accounting anyway. |
| partition_remove_all_prefixes_done(state, kCTIStatus_NotPermitted); |
| } |
| } |
| |
| static void |
| partition_remove_all_unwanted_prefixes(route_state_t *route_state, void (*continuation)(route_state_t *route_state), |
| thread_prefix_t *prefix_1, thread_prefix_t *prefix_2) |
| { |
| unadvertised_prefix_remove_state_t *state = calloc(1, sizeof(*state)); |
| if (state == NULL) { |
| INFO("no memory"); |
| return; |
| } |
| // Retain our copy of state |
| RETAIN_HERE(state); |
| state->route_state = route_state; |
| |
| // It's possible for us to get into a state where a prefix is published by this BR, but doesn't |
| // have a pref:id and isn't recognized as belonging to this BR. This should never happen in practice, |
| // but if it does happen, the only thing that will eliminate it is a reboot. In case this happens, |
| // we go through the list of prefixes that are marked ncp and unpublish them. |
| thread_prefix_t *prefix; |
| |
| state->continuation = continuation; |
| for (prefix = route_state->thread_prefixes; prefix; prefix = prefix->next) { |
| if (!partition_pref_id_is_present(route_state, &prefix->prefix)) { |
| // It's possible for partition_remove_all_unwanted_prefixes to get called before we have a full list of |
| // recently-published prefixes. It is possible for either the published prefix or the adopted prefix to |
| // not be on the list of prefixes. The caller may however have wanted to change either of those pointers; |
| // in this case, it will pass in either or both of those pointers as prefix_1 and prefix_2; if we see those |
| // prefixes on the list, we don't need to unpublish them twice. |
| if (prefix_1 != NULL && !memcmp(&prefix->prefix, &prefix_1->prefix, 8)) { |
| prefix_1 = prefix; |
| } else if (prefix_2 != NULL && !memcmp(&prefix->prefix, &prefix_2->prefix, 8)) { |
| prefix_2 = prefix; |
| } else { |
| state->num_unadvertised_prefixes++; |
| } |
| } |
| } |
| if (prefix_1 != NULL) { |
| state->num_unadvertised_prefixes++; |
| } |
| if (prefix_2 != NULL) { |
| state->num_unadvertised_prefixes++; |
| } |
| |
| // Now actually remove the prefixes. |
| for (prefix = route_state->thread_prefixes; prefix; prefix = prefix->next) { |
| if (prefix_1 != prefix && prefix_2 != prefix) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| if (!partition_pref_id_is_present(route_state, &prefix->prefix)) { |
| INFO("removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); |
| partition_remove_all_unwanted_prefixes_inner(state, prefix, true); |
| } else { |
| INFO("not removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " because pref id is present", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf)); |
| } |
| } |
| } |
| if (prefix_1 != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_1->prefix.s6_addr, prefix_buf); |
| INFO("removing prefix_1 " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_1->prefix.s6_addr, prefix_buf)); |
| partition_remove_all_unwanted_prefixes_inner(state, prefix_1, false); |
| } |
| if (prefix_2 != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_2->prefix.s6_addr, prefix_buf); |
| INFO("removing prefix_2 " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_2->prefix.s6_addr, prefix_buf)); |
| partition_remove_all_unwanted_prefixes_inner(state, prefix_2, false); |
| } |
| |
| RELEASE_HERE(state, unadvertised_prefix_remove_state_finalize); |
| } |
| |
| static void |
| partition_unpublish_adopted_prefix(route_state_t *route_state, bool wait) |
| { |
| // Unpublish the adopted prefix |
| if (route_state->adopted_thread_prefix != NULL) { |
| partition_unpublish_prefix(route_state, route_state->adopted_thread_prefix); |
| INFO("started to unadopt prefix."); |
| RELEASE_HERE(route_state->adopted_thread_prefix, thread_prefix_finalize); |
| route_state->adopted_thread_prefix = NULL; |
| } |
| |
| // Something changed, so do a routing policy update unless wait==true |
| if (!wait) { |
| partition_refresh_and_re_evaluate(route_state); |
| } |
| } |
| |
| static void |
| partition_publish_prefix_finish(route_state_t *route_state) |
| { |
| INFO("prefix unpublishing has completed, time to update the prefix."); |
| |
| partition_id_update(route_state); |
| set_thread_prefix(route_state); |
| |
| // Something changed, so do a routing policy update. |
| partition_refresh_and_re_evaluate(route_state); |
| } |
| |
| void |
| partition_publish_my_prefix(route_state_t *route_state) |
| { |
| void (*continuation)(route_state_t *route_state) = NULL; |
| thread_prefix_t *prefix_1 = NULL; |
| thread_prefix_t *prefix_2 = NULL; |
| |
| if (route_state->adopted_thread_prefix != NULL) { |
| prefix_1 = route_state->adopted_thread_prefix; |
| route_state->adopted_thread_prefix = NULL; |
| } |
| |
| // If we already have a published thread prefix, it really should be my_thread_prefix. |
| if (route_state->published_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf); |
| // This should always be false. |
| if (memcmp(&route_state->published_thread_prefix->prefix, &route_state->my_thread_prefix, 8)) { |
| INFO("Published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is not my prefix", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf)); |
| prefix_2 = route_state->published_thread_prefix; |
| route_state->published_thread_prefix = NULL; |
| continuation = partition_publish_prefix_finish; |
| } else { |
| INFO("Published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is my prefix", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf)); |
| } |
| } |
| if (route_state->published_thread_prefix == NULL) { |
| // Publish the prefix |
| route_state->published_thread_prefix = thread_prefix_create(&route_state->my_thread_prefix, 64); |
| if (route_state->published_thread_prefix == NULL) { |
| ERROR("partition_publish_my_prefix: No memory for locally-advertised thread prefix"); |
| goto out; |
| } |
| continuation = partition_publish_prefix_finish; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->my_thread_prefix.s6_addr, prefix_buf); |
| INFO("Publishing my prefix: " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->my_thread_prefix.s6_addr, prefix_buf)); |
| } |
| partition_remove_all_unwanted_prefixes(route_state, continuation, prefix_1, prefix_2); |
| out: |
| if (prefix_1 != NULL) { |
| RELEASE_HERE(prefix_1, thread_prefix_finalize); |
| } |
| if (prefix_2 != NULL) { |
| RELEASE_HERE(prefix_2, thread_prefix_finalize); |
| } |
| } |
| |
| static void |
| partition_adopt_prefix(route_state_t *route_state, thread_prefix_t *prefix) |
| { |
| void (*continuation)(route_state_t *route_state) = NULL; |
| thread_prefix_t *prefix_1 = NULL; |
| thread_prefix_t *prefix_2 = NULL; |
| |
| if (route_state->published_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("Removing published prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf)); |
| prefix_1 = route_state->published_thread_prefix; |
| route_state->published_thread_prefix = NULL; |
| } |
| |
| // If we already have an advertised thread prefix, it might not have changed. |
| if (route_state->adopted_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf); |
| if (memcmp(&route_state->adopted_thread_prefix->prefix, &prefix->prefix, 8)) { |
| INFO("Removing previously adopted prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf)); |
| prefix_2 = route_state->adopted_thread_prefix; |
| continuation = partition_publish_prefix_finish; |
| } else { |
| INFO("Keeping previously adopted prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf)); |
| } |
| } |
| if (route_state->adopted_thread_prefix == NULL) { |
| // Adopt the prefix |
| route_state->adopted_thread_prefix = prefix; |
| RETAIN_HERE(route_state->adopted_thread_prefix); |
| continuation = partition_publish_prefix_finish; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("Adopting prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf)); |
| } |
| partition_remove_all_unwanted_prefixes(route_state, continuation, prefix_1, prefix_2); |
| |
| if (prefix_1 != NULL) { |
| RELEASE_HERE(prefix_1, thread_prefix_finalize); |
| } |
| if (prefix_2 != NULL) { |
| RELEASE_HERE(prefix_2, thread_prefix_finalize); |
| } |
| } |
| |
| // Check to see if a specific prefix is still present. |
| static bool |
| partition_prefix_is_present(route_state_t *route_state, struct in6_addr *address, int length) |
| { |
| thread_prefix_t *prefix; |
| // For now we assume that the comparison is as a /64. |
| for (prefix = route_state->thread_prefixes; prefix; prefix = prefix->next) { |
| if (prefix->prefix_len == length && !memcmp((uint8_t *)&prefix->prefix, (uint8_t *)address, 8)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Check to see if a valid pref:id for the specified prefix is present. |
| static bool |
| partition_pref_id_is_present(route_state_t *route_state, struct in6_addr *prefix_addr) |
| { |
| thread_pref_id_t *pref_id; |
| uint8_t *prefix_bytes = (uint8_t *)prefix_addr; |
| |
| INFO("published_thread_prefix = %p; prefix = %p", route_state->published_thread_prefix, |
| prefix_addr); |
| |
| // The published prefix's pref:id is always considered present. |
| if (route_state->published_thread_prefix != NULL && |
| !memcmp(prefix_addr, &route_state->published_thread_prefix->prefix, 8)) { |
| INFO("prefix is published prefix"); |
| return true; |
| } |
| |
| for (pref_id = route_state->thread_pref_ids; pref_id; pref_id = pref_id->next) { |
| // A pref:id is valid if the partition ID matches the current partition ID. |
| // A pref:id matches a prefix if the 40 variable bits in the ULA /48 are the same. |
| if (!memcmp(route_state->thread_partition_id, pref_id->partition_id, 4) && |
| !memcmp(prefix_bytes + 1, pref_id->prefix, 5)) |
| { |
| INFO("pref:id is present"); |
| return true; |
| } else { |
| IPv6_PREFIX_GEN_SRP(pref_id->prefix, sizeof(pref_id->prefix), pref_id_prefix); |
| if (memcmp(route_state->thread_partition_id, pref_id->partition_id, 4)) { |
| INFO("pref:id for " PRI_IPv6_PREFIX_SRP |
| ":%02x%02x%02x%02x does not match partition id %02x%02x%02x%02x", |
| IPv6_PREFIX_PARAM_SRP(pref_id_prefix), |
| pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2], |
| pref_id->partition_id[3], |
| route_state->thread_partition_id[0], route_state->thread_partition_id[1], |
| route_state->thread_partition_id[2], route_state->thread_partition_id[3]); |
| } else { |
| INFO("pref:id for " PRI_IPv6_PREFIX_SRP ":%02x%02x%02x%02x does not match prefix %02x%02x%02x%02x%02x", |
| IPv6_PREFIX_PARAM_SRP(pref_id_prefix), |
| pref_id->partition_id[0], pref_id->partition_id[1], pref_id->partition_id[2], |
| pref_id->partition_id[3], |
| prefix_bytes[1], prefix_bytes[2], prefix_bytes[3], prefix_bytes[4], prefix_bytes[5]); |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Find the lowest valid prefix present. The return value may be the published prefix. |
| static thread_prefix_t * |
| partition_find_lowest_valid_prefix(route_state_t *route_state) |
| { |
| thread_prefix_t *prefix, *lowest = route_state->published_thread_prefix; |
| |
| // Are there other prefixes published? |
| for (prefix = route_state->thread_prefixes; prefix != NULL; prefix = prefix->next) { |
| // The prefix we publish doesn't count. |
| if (route_state->published_thread_prefix != NULL && !memcmp(&prefix->prefix, |
| &route_state->published_thread_prefix->prefix, 8)) { |
| continue; |
| } |
| if (partition_pref_id_is_present(route_state, &prefix->prefix)) { |
| if (lowest == NULL || memcmp(&prefix->prefix, &lowest->prefix, 8) < 0) { |
| lowest = prefix; |
| } |
| break; |
| } |
| } |
| return lowest; |
| } |
| |
| // Find the lowest valid pref:id. The return value may be the pref:id for the published prefix. |
| static thread_pref_id_t * |
| partition_find_lowest_valid_pref_id(route_state_t *route_state) |
| { |
| thread_pref_id_t *lowest = NULL; |
| thread_pref_id_t *pref_id; |
| |
| for (pref_id = route_state->thread_pref_ids; pref_id != NULL; pref_id = pref_id->next) { |
| if (lowest == NULL || memcmp(pref_id->prefix, lowest->prefix, 5) < 0) { |
| lowest = pref_id; |
| } |
| } |
| return lowest; |
| } |
| |
| // The prefix ID timeout has gone off. At this time we evaluate the state of the network: the fact that we |
| // got a wakeup means that there has been no partition event and nothing has changed about the set of |
| // prefixes published on the thread mesh since the wakeup was scheduled. We don't schedule this wakeup unless |
| // there is more than one prefix on the thread mesh. So that means that when the wakeup is called, there |
| // is still more than one prefix+pref:id pair active on the link--an undesirable situation. So we now |
| // hold an election. If we lose, we drop our prefix+pref:id pair in favor of the winner. If we win, |
| // we do nothing--we are expecting the BR(s) publishing the other prefix+pref:id pair(s) to drop them. |
| static void |
| partition_pref_id_timeout(void *context) |
| { |
| route_state_t *route_state = context; |
| thread_prefix_t *prefix = partition_find_lowest_valid_prefix(route_state); |
| |
| // This should never happen because we wouldn't have set the timeout. |
| if (prefix == NULL) { |
| INFO("no published prefix."); |
| return; |
| } |
| |
| // If we won, do nothing. |
| if (route_state->published_thread_prefix != NULL && |
| (prefix == route_state->published_thread_prefix || |
| !memcmp(&prefix->prefix, &route_state->published_thread_prefix->prefix, 8))) |
| { |
| INFO("published prefix is the lowest; keeping it."); |
| return; |
| } |
| |
| // published_thread_prefix should never be null here. |
| // If our published prefix is not the lowest prefix, then we should drop it and adopt the lowest prefix. |
| if (route_state->published_thread_prefix != NULL && |
| memcmp(&prefix->prefix, &route_state->published_thread_prefix->prefix, 8)) |
| { |
| INFO("published prefix is not lowest valid prefix. Adopting lowest valid prefix."); |
| partition_adopt_prefix(route_state, prefix); |
| return; |
| } |
| |
| // We should never get here. |
| if (route_state->adopted_thread_prefix != NULL) { |
| if (!memcmp(&route_state->adopted_thread_prefix->prefix, &prefix->prefix, 8)) { |
| ERROR("no published prefix. Already adopted lowest."); |
| return; |
| } |
| // Unadopt this prefix since it's not lowest. |
| partition_unpublish_adopted_prefix(route_state, false); |
| // adopted_thread_prefix is now NULL |
| } |
| |
| // And we should never get here. |
| ERROR("no published prefix. Adopting lowest."); |
| partition_adopt_prefix(route_state, prefix); |
| } |
| |
| // When we see a new partition, if there isn't a prefix to adopt and we aren't publishing one, |
| // we wait n seconds to see if some other BR publishes a prefix, but also publish our own pref:id. |
| // If no router on the partition is publishing a prefix, then after n seconds we hold an election, |
| // choosing the pref:id with the lowest ULA. If that's this router, then we will publish our prefix, |
| // but if not, we want to check after a bit to make sure a prefix /does/ get published. If after |
| // another n seconds, we still don't see a valid prefix+pref:id pair, we publish our own; if this |
| // later turns out to have been a mistake, we will hold the election again and remove the one we |
| // published if we don't win. |
| static void |
| partition_post_election_wakeup(void *context) |
| { |
| route_state_t *route_state = context; |
| thread_prefix_t *prefix = partition_find_lowest_valid_prefix(route_state); |
| |
| // There is no valid prefix published. Publish ours. |
| if (prefix == NULL) { |
| INFO("no valid thread prefix present, publishing mine."); |
| partition_publish_my_prefix(route_state); |
| return; |
| } |
| |
| // It's perfectly valid to not have adopted the lowest prefix at this point. |
| // However, if we have adopted a prefix, we shouldn't be here because the timeout should have been |
| // canceled. |
| if (route_state->adopted_thread_prefix != NULL && |
| memcmp(&route_state->adopted_thread_prefix->prefix, &prefix->prefix, 8)) |
| { |
| ERROR("partition_post_election_wakeup: adopted prefix is not lowest."); |
| } else { |
| ERROR("partition_post_election_wakeup: adopted prefix is lowest."); |
| } |
| } |
| |
| // This is the initial wakeup as described under partition_post_election_wakeup. At this time |
| // if there is a valid published pref:id pair, we adopt it; if not, then we hold an election based |
| // on all of the on-partition pref:id pairs that we see. If we win, we publish our prefix; otherwise |
| // give the winner time to publish its prefix. |
| static void |
| partition_post_partition_timeout(void *context) |
| { |
| route_state_t *route_state = context; |
| thread_prefix_t *prefix = partition_find_lowest_valid_prefix(route_state); |
| thread_pref_id_t *pref_id; |
| |
| // Is there a prefix+pref:id published? |
| // Actually at this point we should already have adopted it and the wakeup should have been canceled. |
| if (prefix != NULL) { |
| ERROR("partition_post_partition_timeout: wakeup when there's a valid lowest prefix."); |
| return; |
| } |
| |
| // Are there pref:id services published that list a lower ULA than ours? |
| pref_id = partition_find_lowest_valid_pref_id(route_state); |
| |
| if (pref_id == NULL) { |
| INFO("There are no prefixes published, publishing my prefix."); |
| partition_publish_my_prefix(route_state); |
| return; |
| } |
| |
| // If not, publish ours. |
| if (memcmp(((uint8_t *)&route_state->my_thread_prefix) + 1, pref_id->prefix, 5) < 0) { |
| INFO("my prefix id is lowest, publishing my prefix."); |
| partition_publish_my_prefix(route_state); |
| return; |
| } |
| |
| // If so, wait another ten seconds to see if one of them publishes a prefix |
| // If we have adopted a prefix, set a timer after which we will drop it and start advertising if nothing has |
| // happened |
| if (route_state->partition_post_partition_wakeup != NULL) { // shouldn't be! |
| ioloop_cancel_wake_event(route_state->partition_post_partition_wakeup); |
| } else { |
| route_state->partition_post_partition_wakeup = ioloop_wakeup_create(); |
| if (route_state->partition_post_partition_wakeup == NULL) { |
| ERROR("partition_post_partition_timeout: can't allocate pref:id wait wakeup."); |
| return; |
| } |
| } |
| // Allow ten seconds for the services state to settle, after which time we should either have a pref:id backing |
| // up a prefix, or should advertise a prefix. |
| INFO("waiting for other BR to publish its prefixes."); |
| ioloop_add_wake_event(route_state->partition_post_partition_wakeup, NULL, |
| partition_post_election_wakeup, NULL, 10 * 1000); |
| } |
| |
| 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); |
| } |
| } |
| |
| 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 = route_state->thread_services; service; service = service->next) { |
| // Track the port regardless. |
| if (num_avoid_ports < max_avoid_ports) { |
| avoid_ports[num_avoid_ports] = (service->port[0] << 8) | (service->port[1]); |
| num_avoid_ports++; |
| } |
| } |
| |
| INFO("starting listener."); |
| route_state->srp_listener = srp_proxy_listen(avoid_ports, num_avoid_ports, |
| partition_proxy_listener_ready, route_state->srp_server); |
| if (route_state->srp_listener == NULL) { |
| ERROR("partition_start_srp_listener: Unable to start SRP Proxy listener, so can't advertise it"); |
| return; |
| } |
| } |
| |
| void |
| partition_discontinue_srp_service(route_state_t *route_state) |
| { |
| if (route_state->srp_listener != NULL) { |
| INFO("discontinuing proxy service on port %d", route_state->srp_service_listen_port); |
| srp_proxy_listener_cancel(route_state->srp_listener); |
| route_state->srp_listener = NULL; |
| } |
| |
| // Won't match |
| memset(&route_state->srp_listener_ip_address, 0, 16); |
| route_state->srp_service_listen_port = 0; |
| route_state->partition_can_advertise_service = false; |
| |
| // Stop advertising the service, if we are doing so. |
| partition_stop_advertising_service(route_state); |
| } |
| |
| // 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) |
| { |
| thread_prefix_t *advertised_prefix = NULL; |
| |
| // Figure out what our current prefix is. |
| if (route_state->published_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("partition_utun0_address_changed: advertised prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " is my prefix.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf)); |
| advertised_prefix = route_state->published_thread_prefix; |
| } else if (route_state->adopted_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("partition_utun0_address_changed: advertised prefix " PRI_SEGMENTED_IPv6_ADDR_SRP |
| " is another router's prefix.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf)); |
| advertised_prefix = route_state->adopted_thread_prefix; |
| } |
| |
| SEGMENTED_IPv6_ADDR_GEN_SRP(addr, addr_buf); |
| // Is this the address we are currently using? |
| if (!memcmp(&route_state->srp_listener_ip_address, addr, 16)) { |
| // Did it go away? If so, drop the listener. |
| if (change == interface_address_deleted) { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": listener address removed.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| if (route_state->srp_listener != NULL) { |
| INFO("partition_utun0_address_changed: " 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("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP |
| ": address we're listening on was added.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| } |
| |
| // Is it on the prefix we're currently publishing? |
| if (advertised_prefix != NULL && !memcmp(&advertised_prefix->prefix, addr, 8)) { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP |
| ": listener address is on the advertised prefix--no action needed.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| } else { |
| // In this case hopefully we'll get a new IP address we _can_ listen on in a subsequent call. |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP |
| ": listener address is not on the advertised prefix--no action taken.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| } |
| } |
| |
| // In no case can we do anything further. |
| return; |
| } |
| |
| // If we have a prefix, see if we need to do anything. |
| if (advertised_prefix != NULL) { |
| // If this is not the address we are currently using, and it showed up, is it on the prefix we |
| // are advertising? |
| if (!memcmp(&advertised_prefix->prefix, addr, 8)) { |
| // If we are not listening on an address, or we are listening on an address that isn't on the |
| // prefix we are advertising, we need to stop, if needed, and start up a new listener. |
| if (route_state->srp_listener_ip_address.s6_addr[0] == 0 || |
| memcmp(&advertised_prefix->prefix, &route_state->srp_listener_ip_address, 8)) |
| { |
| // See if we already have a listener; if so, stop it. |
| if (route_state->srp_listener_ip_address.s6_addr[0] != 0) { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": stopping old listener.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| srp_proxy_listener_cancel(route_state->srp_listener); |
| route_state->srp_listener = NULL; |
| memset(&route_state->srp_listener_ip_address, 0, sizeof(route_state->srp_listener_ip_address)); |
| } |
| if (route_state->srp_listener == NULL) { |
| if (!route_state->have_non_thread_interface) { |
| INFO("partition_utun0_address_changed: not starting a listener because we have no infrastructure"); |
| } else { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP ": starting a new listener.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| memcpy(&route_state->srp_listener_ip_address, addr, 16); |
| route_state->srp_service_listen_port = 0; |
| partition_start_srp_listener(route_state); |
| } |
| } |
| } |
| } else { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP |
| ": this address not on advertised prefix, so no action to take.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| } |
| } else { |
| INFO("partition_utun0_address_changed: " PRI_SEGMENTED_IPv6_ADDR_SRP |
| ": no advertised prefix, so no action to take.", SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf)); |
| } |
| } |
| |
| // We call this function to see if we have a complete, recent set of information; if not, we wait a bit for the set |
| // to become complete, but after 500ms we assume it won't be and proceed. |
| static bool |
| partition_wait_for_prefix_settling(route_state_t *route_state, wakeup_callback_t callback, uint64_t now) |
| { |
| // Remember when we started waiting for the partition data to settle. |
| if (route_state->partition_settle_satisfied) { |
| route_state->partition_settle_start = now; |
| route_state->partition_settle_satisfied = false; |
| } |
| |
| if (route_state->partition_settle_wakeup != NULL) { |
| ioloop_cancel_wake_event(route_state->partition_settle_wakeup); |
| } |
| |
| // If we aren't able to offer service, just wait. |
| if (!route_state->partition_may_offer_service) { |
| INFO("not able to offer service--deferring."); |
| return true; |
| } |
| |
| // If we've gotten updates on everything, we're good to go. The reason for comparing against |
| // partition_settle_start is that if we've been seriously throttled for some reason, it might take |
| // more than 500ms to get a callback, even though all the events came in between when we asked |
| // for the initial callback and when we got it. Tunnel ID shouldn't change after startup. |
| if (route_state->partition_last_prefix_set_change >= route_state->partition_settle_start && |
| route_state->partition_last_pref_id_set_change >= route_state->partition_settle_start && |
| route_state->partition_last_partition_id_change >= route_state->partition_settle_start && |
| route_state->partition_last_role_change >= route_state->partition_settle_start && |
| route_state->partition_last_state_change >= route_state->partition_settle_start && |
| route_state->partition_tunnel_name_is_known) |
| { |
| route_state->partition_settle_satisfied = true; |
| INFO("satisfied after %" PRIu64 "ms.", now - route_state->partition_settle_start); |
| return false; // means don't wait |
| } |
| |
| // If we've waited longer than 500ms and aren't satisfied, complain, but then proceed. |
| if (now - route_state->partition_settle_start >= 500) { |
| ERROR("unsatisfied after %" PRIu64 "ms", |
| now - route_state->partition_settle_start); |
| route_state->partition_settle_satisfied = true; // not really, but there's always next time. |
| return false; // proceed if possible. |
| } |
| |
| // Otherwise, wake up 500ms after we started waiting for things to settle, and reconnoiter. |
| if (route_state->partition_settle_wakeup == NULL) { |
| route_state->partition_settle_wakeup = ioloop_wakeup_create(); |
| if (route_state->partition_settle_wakeup == NULL) { |
| ERROR("Unable to postpone partition settlement wakeup: no memory."); |
| route_state->partition_settle_satisfied = true; |
| return false; |
| } |
| } |
| ioloop_add_wake_event(route_state->partition_settle_wakeup, route_state, callback, NULL, |
| 500 - (int)(now - route_state->partition_settle_start)); |
| return true; |
| } |
| |
| 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; |
| } |
| } |
| refresh_interface_list(route_state); |
| } |
| |
| // We have a recent prefix list and either have a recent pref:id list or one probably isn't coming. |
| static void |
| partition_prefix_list_or_pref_id_list_changed(void *context) |
| { |
| route_state_t *route_state = context; |
| |
| // If we haven't had a pref:id update recently, wait a bit to see if one came with the most recent network data. |
| if (partition_wait_for_prefix_settling(route_state, partition_prefix_list_or_pref_id_list_changed, ioloop_timenow())) { |
| ERROR("waiting for prefix info to settle."); |
| return; |
| } |
| |
| // If we aren't ready to advertise service, do nothing. |
| if (!route_state->partition_may_offer_service) { |
| INFO("can't offer service yet."); |
| return; |
| } |
| |
| // If there are no prefixes, then it doesn't matter what's on the prefix ID list: publish a prefix now. |
| if (route_state->thread_prefixes == NULL) { |
| INFO("have no prefixes, publishing my prefix"); |
| partition_publish_my_prefix(route_state); |
| return; |
| } |
| |
| // It is a failure of the thread network software for us to get to this point without knowing the thread |
| // partition ID. We should have received it on startup. So the case where this would happen would be if |
| // on startup we simply didn't get it, which should never happen. What we'll do if this happens is make |
| // one up. |
| if (route_state->partition_id_is_known == false) { |
| ERROR("partition ID never showed up!"); |
| } |
| |
| // If we are already publishing a prefix and pref:id, we don't have to do anything to the prefix right now. |
| if (route_state->published_thread_prefix != NULL) { |
| // We do need to trigger an interface scan though. |
| refresh_interface_list(route_state); |
| |
| // Also, if there's more than one prefix present, set a timer for an hour from now, at which point we will |
| // consider dropping our prefix. |
| if (route_state->thread_prefixes != NULL && route_state->thread_prefixes->next != NULL) { |
| INFO("published prefix is unchanged, setting up the pref:id timer"); |
| if (route_state->partition_pref_id_wait_wakeup != NULL) { |
| ioloop_cancel_wake_event(route_state->partition_pref_id_wait_wakeup); |
| } else { |
| route_state->partition_pref_id_wait_wakeup = ioloop_wakeup_create(); |
| if (route_state->partition_pref_id_wait_wakeup == NULL) { |
| ERROR("Unable to set a timer to wake up after the an hour to check the partition id."); |
| return; |
| } |
| } |
| // The thread network can be pretty chaotic right after the BR comes up, so if we see a partition during the |
| // first 60 seconds, don't treat it as a real partition event, and do the re-election in 60 seconds rather |
| // than an hour. |
| uint64_t time_since_zero = ioloop_timenow() - route_state->partition_last_state_change; |
| uint32_t pref_id_timeout_time = 3600 * 1000; |
| if (time_since_zero < 60 * 1000) { |
| pref_id_timeout_time = 60 * 1000; |
| } |
| ioloop_add_wake_event(route_state->partition_pref_id_wait_wakeup, route_state, partition_pref_id_timeout, NULL, |
| pref_id_timeout_time); |
| INFO("added partition pref id timeout"); |
| } else { |
| INFO("published prefix is unchanged"); |
| } |
| return; |
| } |
| |
| // If we have adopted a prefix and the prefix and pref:id are still present, do nothing. |
| if (route_state->adopted_thread_prefix != NULL) { |
| if (partition_prefix_is_present(route_state, &route_state->adopted_thread_prefix->prefix, |
| route_state->adopted_thread_prefix->prefix_len) && |
| partition_pref_id_is_present(route_state, &route_state->adopted_thread_prefix->prefix)) |
| { |
| INFO("adopted prefix is unchanged"); |
| return; |
| } |
| // If the adopted prefix is no longer present, stop using it. |
| partition_unpublish_adopted_prefix(route_state, false); |
| // adopted_thread_prefix is now NULL. |
| } |
| |
| // If there is a prefix present for which there is already a matching pref:id, adopt that prefix and pref:id now. |
| // drop the thread_post_partition_timeout timer. |
| thread_prefix_t *prefix; |
| for (prefix = route_state->thread_prefixes; prefix; prefix = prefix->next) { |
| if (partition_pref_id_is_present(route_state, &prefix->prefix)) { |
| INFO("adopting new prefix"); |
| partition_adopt_prefix(route_state, prefix); |
| // When we adopt a prefix, it was already on-link, and quite possibly we already have an address |
| // configured on that prefix on utun0. Calling refresh_interface_list() will trigger the listener |
| // if in fact that's the case. If the address hasn't come up on utun0 yet, then when it comes up |
| // that will trigger the listener. |
| refresh_interface_list(route_state); |
| return; |
| } |
| if (route_state->partition_post_partition_wakeup != NULL) { |
| ioloop_cancel_wake_event(route_state->partition_post_partition_wakeup); |
| } |
| } |
| |
| // At this point there is a prefix, but no pref:id, and it's /not/ the prefix that we published. This |
| // means that a partition has happened and the BR that published the prefix is on the other partition, |
| // or else that the BR that published the prefix has gone offline and has been offline for at least |
| // four minutes. |
| // It's possible that either condition will heal, but in the meantime publish a prefix. The reason for |
| // the urgency is that if we have a partition, and both routers are still online, then routing will be |
| // screwed up until we publish a new prefix and migrate all the accessories on our partition to the |
| // new prefix. |
| INFO("there is a prefix, but no pref:id, so it's stale. Publishing my prefix."); |
| partition_publish_my_prefix(route_state); |
| } |
| |
| // The list of published prefix has changed. Evaluate what to do with our partition state. |
| // Mostly what we do when the prefix list changes is the same as what we do if the pref:id list |
| // changes, but if we get an empty prefix list, it doesn't matter what's on the pref:id list, |
| // so we act immediately. |
| static void |
| partition_prefix_set_changed(route_state_t *route_state) |
| { |
| // Time stamp most recent prefix set update. |
| route_state->partition_last_prefix_set_change = ioloop_timenow(); |
| |
| // Otherwise, we have a prefix list and a pref:id list, so we can make decisions. |
| partition_prefix_list_or_pref_id_list_changed(route_state); |
| } |
| |
| // The set of published pref:id's changed. Evaluate what to do with our pref:id |
| static void |
| partition_pref_id_set_changed(route_state_t *route_state) |
| { |
| // Time stamp most recent prefix set update. |
| route_state->partition_last_prefix_set_change = ioloop_timenow(); |
| |
| // Otherwise, we have a prefix list and a pref:id list, so we can make decisions. |
| partition_prefix_list_or_pref_id_list_changed(route_state); |
| } |
| |
| // The partition ID changed. |
| static void |
| partition_id_changed(route_state_t *route_state) |
| { |
| route_state->partition_last_partition_id_change = ioloop_timenow(); |
| |
| // If we've never seen a partition ID before, this is not (necessarily) a partition. |
| if (!route_state->partition_id_is_known) { |
| INFO("first time through."); |
| route_state->partition_id_is_known = true; |
| return; |
| } |
| |
| // If we get a partition ID when we aren't a router, we should (I think!) ignore it. |
| if (!route_state->partition_can_provide_routing) { |
| INFO("we aren't able to offer routing yet, so ignoring."); |
| return; |
| } |
| |
| // If we are advertising a prefix, update our pref:id |
| if (route_state->published_thread_prefix != NULL) { |
| INFO("updating advertised prefix id"); |
| partition_id_update(route_state); |
| // In principle we didn't change anything material to the routing subsystem, so no need to re-evaluate current |
| // policy. |
| return; |
| } |
| |
| // Propose our prefix as a possible lowest prefix in case there's an election. |
| partition_stop_advertising_pref_id(route_state); |
| partition_advertise_pref_id(route_state, ((uint8_t *)(&route_state->my_thread_prefix)) + 1); |
| |
| // If we have adopted a prefix, set a timer after which we will drop it and start advertising if nothing has |
| // happened |
| if (route_state->partition_post_partition_wakeup != NULL) { |
| ioloop_cancel_wake_event(route_state->partition_post_partition_wakeup); |
| } else { |
| route_state->partition_post_partition_wakeup = ioloop_wakeup_create(); |
| if (route_state->partition_post_partition_wakeup == NULL) { |
| ERROR("partition_id_changed: can't allocate pref:id wait wakeup."); |
| return; |
| } |
| } |
| // Allow ten seconds for the services state to settle, after which time we should either have a pref:id backing |
| // up a prefix, or should advertise a prefix. |
| INFO("waiting for other BRs to propose their prefixes."); |
| ioloop_add_wake_event(route_state->partition_post_partition_wakeup, route_state, |
| partition_post_partition_timeout, NULL, 10 * 1000); |
| } |
| |
| 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 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; |
| |
| service_info[0] = THREAD_SRP_SERVER_OPTION & 255; |
| status = cti_remove_service(route_state, partition_remove_service_done, NULL, |
| THREAD_ENTERPRISE_NUMBER, service_info, 1); |
| if (status != kCTIStatus_NoError) { |
| INFO("status %d", status); |
| } |
| } |
| |
| 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; |
| |
| 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, 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); |
| } |
| |
| 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_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 ten seconds. |
| ioloop_add_wake_event(route_state->partition_service_add_pending_wakeup, route_state, |
| partition_service_add_wakeup, NULL, 10 * 1000); |
| } |
| |
| static void |
| partition_maybe_advertise_service(route_state_t *route_state) |
| { |
| thread_service_t *service, *lowest[2]; |
| int num_services = 0; |
| int i; |
| bool should_remove_service = false; |
| bool should_advertise_service = false; |
| int64_t last_add_time; |
| |
| // 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->partition_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) { |
| partition_schedule_service_add_wakeup(route_state); |
| return; |
| } |
| lowest[0] = NULL; |
| lowest[1] = NULL; |
| |
| for (service = route_state->thread_services; service; service = service->next) { |
| int port = (service->port[0] << 8) | service->port[1]; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(service->address, srv_addr_buf); |
| |
| // A service only counts if its prefix is present and its prefix id is present and matches the |
| // current partition id. |
| if (partition_prefix_is_present(route_state, (struct in6_addr *)service->address, 64)) { |
| if (partition_pref_id_is_present(route_state, (struct in6_addr *)service->address)) { |
| num_services++; |
| for (i = 0; i < 2; i++) { |
| if (lowest[i] == NULL) { |
| lowest[i] = service; |
| INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d goes in open slot %d.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port, i); |
| break; |
| } else if (memcmp(service->address, lowest[i]->address, 16) < 0) { |
| int lowport; |
| |
| if (lowest[1] != NULL) { |
| lowport = (lowest[1]->port[0] << 8) | lowest[1]->port[1]; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(lowest[1]->address, lowest_1_buf); |
| INFO("Superseding " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d in slot 1", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(lowest[1]->address, lowest_1_buf), lowport); |
| } |
| if (i == 0) { |
| lowport = (lowest[0]->port[0] << 8)| lowest[0]->port[1]; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(lowest[0]->address, lowest_0_buf); |
| INFO("Moving " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d from slot 0 to slot 1", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(lowest[0]->address, lowest_0_buf), lowport); |
| lowest[1] = lowest[0]; |
| } |
| INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d goes in slot %d.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port, i); |
| lowest[i] = service; |
| break; |
| } |
| } |
| } else { |
| INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d doesn't count because the pref:id is not present.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port); |
| } |
| } else { |
| INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d doesn't count because the prefix is not present.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(service->address, srv_addr_buf), port); |
| } |
| } |
| |
| should_remove_service = true; |
| for (i = 0; i < 2; i++) { |
| if (lowest[i] == NULL) { |
| INFO("adding service because there's an open slot."); |
| should_remove_service = false; |
| should_advertise_service = true; |
| break; |
| } else { |
| int sign = memcmp(((uint8_t *)(&route_state->srp_listener_ip_address)), lowest[i]->address, 16); |
| if (sign == 0) { |
| // We're already advertising the service and we win the election. |
| // If the port hasn't changed, don't update the service |
| uint16_t port = (lowest[i]->port[0] << 8) | lowest[i]->port[1]; |
| if (port != route_state->srp_service_listen_port) { |
| INFO("old service was present and prefix would win election."); |
| should_remove_service = false; |
| should_advertise_service = true; |
| } else { |
| INFO("service already present and would win election."); |
| should_remove_service = false; |
| should_advertise_service = false; |
| } |
| break; |
| } else if (sign < 0) { |
| INFO("service not present but wins election."); |
| should_remove_service = false; |
| should_advertise_service = true; |
| break; |
| } else { |
| INFO("Service would not win election with lowest[%d]", i); |
| } |
| } |
| } |
| |
| // Always remove service before adding it, but also remove it if it lost the election. |
| if (should_remove_service) { |
| partition_stop_advertising_service(route_state); |
| route_state->partition_service_last_add_time = ioloop_timenow(); |
| } |
| if (should_advertise_service) { |
| partition_start_advertising_service(route_state); |
| route_state->partition_service_last_add_time = ioloop_timenow(); |
| } |
| } |
| |
| static void |
| partition_service_set_changed(route_state_t *route_state) |
| { |
| partition_pref_id_set_changed(route_state); |
| partition_maybe_advertise_service(route_state); |
| } |
| |
| 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) { |
| 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; |
| refresh_interface_list(route_state); |
| partition_prefix_list_or_pref_id_list_changed(route_state); |
| routing_policy_evaluate_all_interfaces(route_state, true); |
| } 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 adopted and published |
| // prefixes; this will get cleaned up when the network comes back if there's an inconsistency. |
| if (route_state->adopted_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("unadopting prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->adopted_thread_prefix->prefix.s6_addr, prefix_buf)); |
| RELEASE_HERE(route_state->adopted_thread_prefix, thread_prefix_finalize); |
| route_state->adopted_thread_prefix = NULL; |
| done_something = true; |
| } |
| if (route_state->published_thread_prefix != NULL) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf); |
| INFO("un-publishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->published_thread_prefix->prefix.s6_addr, prefix_buf)); |
| RELEASE_HERE(route_state->published_thread_prefix, thread_prefix_finalize); |
| route_state->published_thread_prefix = NULL; |
| done_something = true; |
| } |
| |
| // We want to always say something when we pass through this state. |
| if (!done_something) { |
| INFO("nothing to do."); |
| } |
| |
| route_state->partition_may_offer_service = false; |
| route_state->partition_can_provide_routing = false; |
| } |
| #endif // RA_TESTER |
| #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: |