| /* route-tracker.c |
| * |
| * Copyright (c) 2023 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 file contains the implementation for a route tracker for tracking prefixes and routes on infrastructure so that |
| * they can be published on the Thread network. |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <pwd.h> |
| #include <errno.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <time.h> |
| #include <dns_sd.h> |
| #include <net/if.h> |
| #include <inttypes.h> |
| #include <sys/resource.h> |
| #include <netinet/icmp6.h> |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "srp-crypto.h" |
| #include "ioloop.h" |
| #include "srp-gw.h" |
| #include "srp-proxy.h" |
| #include "cti-services.h" |
| #include "srp-mdns-proxy.h" |
| #include "dnssd-proxy.h" |
| #include "config-parse.h" |
| #include "cti-services.h" |
| #include "route.h" |
| #include "nat64.h" |
| #include "nat64-macos.h" |
| |
| #define STATE_MACHINE_IMPLEMENTATION 1 |
| typedef enum { |
| route_tracker_state_invalid, |
| } state_machine_state_t; |
| #define state_machine_state_invalid route_tracker_state_invalid |
| |
| #include "state-machine.h" |
| #include "thread-service.h" |
| #include "omr-watcher.h" |
| #include "omr-publisher.h" |
| #include "route-tracker.h" |
| |
| typedef struct prefix_tracker prefix_tracker_t; |
| struct prefix_tracker { |
| int ref_count; |
| prefix_tracker_t *next; // This is for the prefix advertise queue |
| struct in6_addr prefix; |
| int prefix_length; |
| uint32_t preferred_lifetime, valid_lifetime; |
| int num_routers; |
| int new_num_routers; |
| bool pending; |
| }; |
| |
| // The route tracker keeps a set of prefixes that it's tracking. These prefixes are what's published on the |
| // Thread mesh. |
| struct route_tracker { |
| int ref_count; |
| int max_prefixes; |
| void (*reconnect_callback)(void *); |
| route_state_t *route_state; |
| char *name; |
| prefix_tracker_t **prefixes; |
| prefix_tracker_t *update_queue; |
| interface_t *infrastructure; |
| bool canceled; |
| #ifdef BUILD_TEST_ENTRY_POINTS |
| uint32_t current_mask, add_mask, remove_mask, intended_mask; |
| cti_reply_t callback; |
| int iterations; |
| #endif |
| }; |
| |
| |
| static void route_tracker_add_prefix_to_queue(route_tracker_t *tracker, prefix_tracker_t *prefix); |
| |
| #ifdef BUILD_TEST_ENTRY_POINTS |
| static void route_tracker_test_update_queue_empty(route_tracker_t *tracker); |
| #endif |
| |
| static void |
| prefix_tracker_finalize(prefix_tracker_t *prefix) |
| { |
| free(prefix); |
| } |
| |
| static prefix_tracker_t * |
| prefix_tracker_create(struct in6_addr *prefix_bits, int prefix_length, uint32_t preferred_lifetime, uint32_t valid_lifetime) |
| { |
| prefix_tracker_t *prefix = calloc(1, sizeof (*prefix)); |
| if (prefix == NULL) { |
| ERROR("no memory for prefix"); |
| return NULL; |
| } |
| RETAIN_HERE(prefix, prefix_tracker); |
| prefix->prefix = *prefix_bits; |
| prefix->prefix_length = prefix_length; |
| prefix->preferred_lifetime = preferred_lifetime; |
| prefix->valid_lifetime = valid_lifetime; |
| return prefix; |
| } |
| |
| static void |
| route_tracker_finalize(route_tracker_t *tracker) |
| { |
| free(tracker->prefixes); |
| free(tracker->name); |
| free(tracker); |
| } |
| |
| #ifndef BUILD_TEST_ENTRY_POINTS |
| RELEASE_RETAIN_FUNCS(route_tracker); |
| #endif // BUILD_TEST_ENTRY_POINTS |
| |
| void |
| route_tracker_cancel(route_tracker_t *tracker) |
| { |
| if (tracker->prefixes != NULL) { |
| for (int i = 0; i < tracker->max_prefixes; i++) { |
| prefix_tracker_t *prefix = tracker->prefixes[i]; |
| if (prefix != NULL) { |
| tracker->prefixes[i] = NULL; |
| // If we have published a route to this prefix, queue it for removal. |
| if (prefix->num_routers != 0) { |
| prefix->num_routers = 0; |
| route_tracker_add_prefix_to_queue(tracker, prefix); |
| } |
| RELEASE_HERE(prefix, prefix_tracker); |
| } |
| } |
| } |
| #ifndef BUILD_TEST_ENTRY_POINTS |
| if (tracker->infrastructure) { |
| interface_release(tracker->infrastructure); |
| tracker->infrastructure = NULL; |
| } |
| #endif // BUILD_TEST_ENTRY_POINTS |
| tracker->canceled = true; |
| } |
| |
| route_tracker_t * |
| route_tracker_create(route_state_t *NONNULL route_state, const char *NONNULL name) |
| { |
| route_tracker_t *ret = NULL, *tracker = calloc(1, sizeof(*tracker)); |
| if (tracker == NULL) { |
| INFO("no memory for tracker"); |
| return tracker; |
| } |
| RETAIN_HERE(tracker, route_tracker); |
| tracker->route_state = route_state; |
| tracker->name = strdup(name); |
| if (tracker->name == NULL) { |
| goto out; |
| } |
| tracker->max_prefixes = 10; |
| tracker->prefixes = calloc(tracker->max_prefixes, sizeof (*tracker->prefixes)); |
| if (tracker->prefixes == NULL) { |
| INFO("no memory for prefix vector"); |
| goto out; |
| } |
| |
| ret = tracker; |
| tracker = NULL; |
| out: |
| if (tracker != NULL) { |
| RELEASE_HERE(tracker, route_tracker); |
| } |
| return ret; |
| } |
| |
| static bool |
| route_tracker_add_prefix(route_tracker_t *tracker, prefix_tracker_t *prefix) |
| { |
| int open_slot = -1; |
| for (int i = 0; i < tracker->max_prefixes; i++) { |
| if (tracker->prefixes[i] == NULL && open_slot == -1) { |
| open_slot = i; |
| } |
| if (tracker->prefixes[i] == prefix) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&prefix->prefix, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is already present", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&prefix->prefix, prefix_buf), prefix->prefix_length); |
| return true; |
| } |
| } |
| if (open_slot != -1) { |
| tracker->prefixes[open_slot] = prefix; |
| RETAIN_HERE(tracker->prefixes[open_slot], prefix_tracker); |
| return true; |
| } |
| |
| int new_max = tracker->max_prefixes * 2; |
| prefix_tracker_t **prefixes = calloc(new_max, sizeof (*prefixes)); |
| if (prefixes == NULL) { |
| INFO("no memory to add prefix"); |
| return false; |
| } |
| memcpy(prefixes, tracker->prefixes, tracker->max_prefixes * sizeof(*tracker->prefixes)); |
| free(tracker->prefixes); |
| tracker->prefixes = prefixes; |
| tracker->prefixes[tracker->max_prefixes] = prefix; |
| RETAIN_HERE(tracker->prefixes[tracker->max_prefixes], prefix_tracker); |
| tracker->max_prefixes = new_max; |
| return true; |
| } |
| |
| |
| void |
| route_tracker_set_reconnect_callback(route_tracker_t *tracker, void (*reconnect_callback)(void *context)) |
| { |
| tracker->reconnect_callback = reconnect_callback; |
| } |
| |
| void |
| route_tracker_start(route_tracker_t *tracker) |
| { |
| INFO("starting tracker " PUB_S_SRP, tracker->name); |
| return; |
| } |
| |
| static void route_tracker_start_next_update(route_tracker_t *tracker); |
| |
| static void |
| route_tracker_remove_prefix(route_tracker_t *tracker, prefix_tracker_t *prefix) |
| { |
| for (int i = 0; i < tracker->max_prefixes; i++) { |
| if (tracker->prefixes[i] == prefix) { |
| RELEASE_HERE(tracker->prefixes[i], prefix_tracker); |
| tracker->prefixes[i] = NULL; |
| return; |
| } |
| } |
| } |
| |
| static void |
| route_tracker_update_callback(void *context, cti_status_t status) |
| { |
| route_tracker_t *tracker = context; |
| prefix_tracker_t *prefix = tracker->update_queue; |
| INFO("status %d", status); |
| if (tracker->update_queue == NULL) { |
| ERROR("update seems to have disappeared"); |
| #ifdef BUILD_TEST_ENTRY_POINTS |
| route_tracker_test_update_queue_empty(tracker); |
| #endif |
| return; |
| } |
| if (prefix->pending) { |
| prefix->pending = false; |
| tracker->update_queue = prefix->next; |
| prefix->next = NULL; |
| if (prefix->num_routers == 0) { |
| route_tracker_remove_prefix(tracker, prefix); |
| } |
| RELEASE_HERE(prefix, prefix_tracker); |
| } |
| if (tracker->update_queue != NULL) { |
| route_tracker_start_next_update(tracker); |
| } else { |
| // The update queue holds a reference to the tracker when there is something on the |
| // queue. |
| RELEASE_HERE(tracker, route_tracker); |
| #ifdef BUILD_TEST_ENTRY_POINTS |
| route_tracker_test_update_queue_empty(tracker); |
| #endif |
| } |
| } |
| |
| static void |
| route_tracker_start_next_update(route_tracker_t *tracker) |
| { |
| prefix_tracker_t *prefix = tracker->update_queue; |
| if (prefix == NULL) { |
| ERROR("start_next_update called with no update"); |
| return; |
| } |
| |
| cti_status_t status; |
| |
| // If num_routers is zero, remove the prefix. |
| if (prefix->num_routers == 0) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&prefix->prefix, prefix_buf); |
| INFO("removing route: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&prefix->prefix, prefix_buf), prefix->prefix_length); |
| status = cti_remove_route(tracker->route_state->srp_server, tracker, route_tracker_update_callback, |
| NULL, &prefix->prefix, prefix->prefix_length, 0); |
| } else { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&prefix->prefix, prefix_buf); |
| INFO(" adding route: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&prefix->prefix, prefix_buf), prefix->prefix_length); |
| status = cti_add_route(tracker->route_state->srp_server, tracker, route_tracker_update_callback, |
| NULL, &prefix->prefix, prefix->prefix_length, |
| offmesh_route_preference_medium, 0, true, false); |
| } |
| if (status != kCTIStatus_NoError) { |
| ERROR("route update failed: %d", status); |
| } else { |
| prefix->pending = true; |
| } |
| } |
| |
| static void |
| route_tracker_add_prefix_to_queue(route_tracker_t *tracker, prefix_tracker_t *prefix) |
| { |
| prefix_tracker_t **ptp, *old_queue = tracker->update_queue; |
| // Find the prefix on the queue, or find the end of the queue. |
| for (ptp = &tracker->update_queue; *ptp != NULL && *ptp != prefix; ptp = &(*ptp)->next) |
| ; |
| // Not on the queue... |
| if (*ptp == NULL) { |
| *ptp = prefix; |
| RETAIN_HERE(prefix, prefix_tracker); |
| // Turns out we added it to the beginning of the queue. |
| if (tracker->update_queue == prefix) { |
| route_tracker_start_next_update(tracker); |
| } |
| goto out; |
| } |
| // We have started to update the prefix, but haven't gotten the callback yet. Since we have put the prefix |
| // back on the update queue, and it's at the beginning, mark it not pending so that when we get the callback |
| // from the update function, we update this route again rather than going on to the next. |
| if (prefix == tracker->update_queue) { |
| prefix->pending = false; |
| } |
| // If we get to here, the prefix is already on the update queue and its update hasn't started yet, so we can just leave it. |
| out: |
| // As long as there is anything in the queue, the queue needs to hold a reference to the tracker, so that if it's |
| // canceled and released, we finish running the queue before stopping. |
| if (old_queue == NULL && tracker->update_queue != NULL) { |
| RETAIN_HERE(tracker, route_tracker); |
| } |
| } |
| |
| static void |
| route_tracker_track_prefix(route_tracker_t *tracker, struct in6_addr *prefix_bits, int prefix_length, |
| uint32_t preferred_lifetime, uint32_t valid_lifetime, bool count) |
| { |
| prefix_tracker_t *prefix = NULL; |
| int i; |
| for (i = 0; i < tracker->max_prefixes; i++) { |
| prefix = tracker->prefixes[i]; |
| if (prefix == NULL) { |
| continue; |
| } |
| if (prefix->prefix_length == prefix_length && !in6addr_compare(&prefix->prefix, prefix_bits)) { |
| break; |
| } |
| } |
| if (i == tracker->max_prefixes) { |
| prefix = prefix_tracker_create(prefix_bits, prefix_length, preferred_lifetime, valid_lifetime); |
| if (prefix == NULL) { |
| return; |
| } |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_bits, prefix_buf); |
| INFO("adding prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_bits, prefix_buf)); |
| if (!route_tracker_add_prefix(tracker, prefix)) { |
| // Weren't able to add it. |
| RELEASE_HERE(prefix, prefix_tracker); |
| return; |
| } |
| if (count) { |
| prefix->new_num_routers++; |
| } |
| // To avoid confusion, route_tracker_add_prefix retains the prefix. That means the reference we got from |
| // creating it is still held, so release it. |
| RELEASE_HERE(prefix, prefix_tracker); |
| } else { |
| if (count && prefix != NULL) { |
| prefix->new_num_routers++; |
| } |
| } |
| } |
| |
| #ifndef BUILD_TEST_ENTRY_POINTS |
| #if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES |
| static void |
| route_tracker_count_prefixes(route_tracker_t *tracker, icmp_message_t *router, bool have_routable_omr_prefix) |
| { |
| icmp_option_t *router_option = router->options; |
| for (int i = 0; i < router->num_options; i++, router_option++) { |
| // Always track PIO if it's on-link |
| if (router_option->type == icmp_option_prefix_information && |
| (router_option->option.route_information.flags & ND_OPT_PI_FLAG_ONLINK)) |
| { |
| route_tracker_track_prefix(tracker, |
| &router_option->option.prefix_information.prefix, |
| router_option->option.prefix_information.length, |
| router_option->option.prefix_information.preferred_lifetime, |
| router_option->option.prefix_information.valid_lifetime, true); |
| } else if (have_routable_omr_prefix && router_option->type == icmp_option_prefix_information) { |
| route_tracker_track_prefix(tracker, |
| &router_option->option.route_information.prefix, |
| router_option->option.route_information.length, |
| router_option->option.route_information.route_lifetime, |
| router_option->option.route_information.route_lifetime, true); |
| } |
| } |
| } |
| #endif |
| #endif // BUILD_TEST_ENTRY_POINTS |
| |
| static void |
| route_tracker_reset_counts(route_tracker_t *tracker) |
| { |
| // Set num_routers to zero on each prefix |
| for (int i = 0; i < tracker->max_prefixes; i++) { |
| prefix_tracker_t *prefix = tracker->prefixes[i]; |
| if (prefix != NULL) { |
| prefix->new_num_routers = 0; |
| } |
| } |
| } |
| |
| static void |
| route_tracker_publish_changes(route_tracker_t *tracker) |
| { |
| // Now go through the prefixes and queue updates for anything that changed. |
| for (int i = 0; i < tracker->max_prefixes; i++) { |
| prefix_tracker_t *prefix = tracker->prefixes[i]; |
| if (prefix != NULL) { |
| // If the number of routers advertising the prefix changed, and the total number of routers either went to zero |
| // or was previously zero, put the prefix on the queue to publish. |
| if (prefix->new_num_routers != prefix->num_routers && |
| (prefix->new_num_routers == 0 || prefix->num_routers == 0)) |
| { |
| prefix->num_routers = prefix->new_num_routers; |
| route_tracker_add_prefix_to_queue(tracker, prefix); |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&prefix->prefix, prefix_buf); |
| INFO("(not) " PUB_S_SRP " prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, prefix->num_routers ? "adding" : "removing", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&prefix->prefix, prefix_buf)); |
| } else { |
| // Update the number of routers, but don't do anything else. |
| prefix->num_routers = prefix->new_num_routers; |
| } |
| } |
| } |
| } |
| |
| #ifndef BUILD_TEST_ENTRY_POINTS |
| void |
| route_tracker_route_state_changed(route_tracker_t *tracker, interface_t *interface) |
| { |
| if (tracker->route_state == NULL) { |
| ERROR("tracker has no route_state"); |
| return; |
| } |
| route_state_t *route_state = tracker->route_state; |
| |
| bool have_routable_omr_prefix; |
| if (route_state->omr_publisher != NULL && omr_publisher_have_routable_prefix(route_state->omr_publisher)) { |
| have_routable_omr_prefix = true; |
| } else { |
| have_routable_omr_prefix = false; |
| } |
| if (interface != tracker->infrastructure) { |
| return; |
| } |
| INFO("interface: " PUB_S_SRP, interface != NULL ? interface->name : "(no interface)"); |
| |
| route_tracker_reset_counts(tracker); |
| |
| // If we have no interface, then all we really care about is that any routes we're publishing should be |
| // removed. |
| if (interface != NULL) { |
| #if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES |
| for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) { |
| if (have_routable_omr_prefix && router->router_lifetime != 0) { |
| #endif |
| static struct in6_addr default_prefix; |
| route_tracker_track_prefix(tracker, &default_prefix, 0, 1800, 1800, true); |
| #if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES |
| } |
| route_tracker_count_prefixes(tracker, router, have_routable_omr_prefix); |
| } |
| #endif |
| #if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES |
| // Track our own prefix |
| if (interface->on_link_prefix_configured) { |
| route_tracker_track_prefix(tracker, &interface->ipv6_prefix, 64, 1800, 1800, true); |
| } |
| #endif |
| } |
| #if SRP_FEATURE_NAT64 |
| nat64_omr_route_update(route_state->nat64, have_routable_omr_prefix); |
| #endif |
| route_tracker_publish_changes(tracker); |
| } |
| |
| void |
| route_tracker_interface_configuration_changed(route_tracker_t *tracker) |
| { |
| interface_t *preferred = NULL; |
| if (tracker->route_state == NULL) { |
| ERROR("tracker has no route_state"); |
| return; |
| } |
| route_state_t *route_state = tracker->route_state; |
| |
| for (interface_t *interface = route_state->interfaces; interface; interface = interface->next) { |
| if (interface->ip_configuration_service != NULL) { |
| preferred = interface; |
| break; |
| } |
| if (!interface->inactive && !interface->ineligible) { |
| if (tracker->infrastructure == interface) { |
| preferred = interface; |
| break; |
| } |
| if (preferred != NULL) { |
| FAULT("more than one infra interface: " PUB_S_SRP " and " PUB_S_SRP " (picked)", interface->name, preferred->name); |
| } else { |
| preferred = interface; |
| } |
| } |
| } |
| if (preferred == NULL) { |
| if (tracker->infrastructure != NULL) { |
| interface_release(tracker->infrastructure); |
| tracker->infrastructure = NULL; |
| INFO("no infrastructure"); |
| route_tracker_route_state_changed(tracker, NULL); |
| } |
| } else { |
| if (tracker->infrastructure != preferred) { |
| if (tracker->infrastructure != NULL) { |
| interface_release(tracker->infrastructure); |
| tracker->infrastructure = NULL; |
| } |
| INFO("preferred infrastructure interface is now " PUB_S_SRP, preferred->name); |
| #if SRP_FEATURE_NAT64 |
| nat64_pass_all_pf_rule_set(preferred->name); |
| #endif // SRP_FEATURE_NAT64 |
| tracker->infrastructure = preferred; |
| interface_retain(tracker->infrastructure); |
| route_tracker_route_state_changed(tracker, tracker->infrastructure); |
| } |
| } |
| } |
| |
| void |
| route_tracker_monitor_mesh_routes(route_tracker_t *tracker, cti_route_vec_t *routes) |
| { |
| for (size_t i = 0; i < routes->num; i++) { |
| cti_route_t *route = routes->routes[i]; |
| if (route && route->origin == offmesh_route_origin_user) { |
| route_tracker_track_prefix(tracker, &route->prefix, route->prefix_length, 100, 100, false); |
| } |
| } |
| } |
| #else // !defined(BUILD_TEST_ENTRY_POINTS) |
| |
| static void |
| route_tracker_remove_callback(void *context) |
| { |
| route_tracker_update_callback(context, 0); |
| } |
| |
| static void |
| route_tracker_test_route_update(void *context, struct in6_addr *prefix, cti_reply_t callback, bool remove) |
| { |
| route_tracker_t *tracker = context; |
| |
| for (int i = 0; i < 4; i++) { |
| if (prefix->s6_addr[i] != 0) { |
| for (int j = 0; j < 8; j++) { |
| if (prefix->s6_addr[i] == (1 << j)) { |
| int bit = i * 8 + j; |
| if (remove) { |
| tracker->remove_mask |= (1 << bit); |
| INFO("bit %d removed", bit); |
| } else { |
| tracker->add_mask |= (1 << bit); |
| INFO("bit %d added", bit); |
| } |
| tracker->callback = callback; |
| ioloop_run_async(route_tracker_remove_callback, tracker); |
| return; |
| } |
| } |
| } |
| } |
| INFO("no bit"); |
| } |
| |
| int |
| cti_add_route_(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, run_context_t NULLABLE UNUSED client_queue, |
| struct in6_addr *NONNULL prefix, int UNUSED prefix_length, int UNUSED priority, int UNUSED domain_id, bool UNUSED stable, |
| bool UNUSED nat64, const char *NONNULL UNUSED file, int UNUSED line) |
| { |
| route_tracker_test_route_update(context, prefix, callback, false); |
| return 0; |
| } |
| |
| int |
| cti_remove_route_(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, run_context_t NULLABLE UNUSED client_queue, |
| struct in6_addr *NONNULL prefix, int UNUSED prefix_length, int UNUSED priority, |
| const char *NONNULL UNUSED file, int UNUSED line) |
| { |
| route_tracker_test_route_update(context, prefix, callback, true); |
| return 0; |
| } |
| |
| static void |
| route_tracker_test_announce_prefixes_in_mask(route_tracker_t *tracker, uint32_t mask) |
| { |
| struct in6_addr prefix; |
| |
| tracker->intended_mask = mask; |
| route_tracker_reset_counts(tracker); |
| |
| for (int i = 0; i < 32; i++) { |
| if ((mask & (1 << i)) != 0) { |
| INFO("%x is in %x", 1 << i, mask); |
| int byte = i / 8; |
| uint8_t bit = (uint8_t)(1 << (i & 7)); |
| |
| in6addr_zero(&prefix); |
| prefix.s6_addr[byte] = bit; |
| |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&prefix, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " byte %d bit %x i %d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&prefix, prefix_buf), byte, bit, i); |
| |
| route_tracker_track_prefix(tracker, &prefix, 64, 100, 100, true); |
| } else { |
| INFO("%x is not in %x", 1 << i, mask); |
| } |
| } |
| route_tracker_publish_changes(tracker); |
| // If we get the same mask twice in a row, there will be no updates, so we won't get any callbacks. |
| if (tracker->update_queue == NULL) { |
| route_tracker_test_update_queue_empty(tracker); |
| } |
| } |
| |
| static void |
| route_tracker_test_iterate(route_tracker_t *tracker) |
| { |
| tracker->intended_mask = srp_random32(); |
| route_tracker_test_announce_prefixes_in_mask(tracker, tracker->intended_mask); |
| } |
| |
| route_state_t *test_route_state; |
| srp_server_t *test_srp_server; |
| |
| void |
| route_tracker_test_start(int iterations) |
| { |
| test_srp_server = calloc(1, sizeof(*test_srp_server)); |
| if (test_srp_server == NULL) { |
| ERROR("no memory for srp state"); |
| exit(1); |
| } |
| test_route_state = calloc(1, sizeof(*test_route_state)); |
| if (test_route_state == NULL) { |
| ERROR("no memory for route state"); |
| exit(1); |
| } |
| test_route_state->srp_server = test_srp_server; |
| test_route_state->route_tracker = route_tracker_create(test_route_state, "test"); |
| test_route_state->route_tracker->iterations = iterations; |
| route_tracker_start(test_route_state->route_tracker); |
| route_tracker_test_iterate(test_route_state->route_tracker); |
| } |
| |
| static void |
| route_tracker_test_update_queue_empty(route_tracker_t *tracker) |
| { |
| uint32_t result = ((tracker->current_mask & ~tracker->remove_mask)) | tracker->add_mask; |
| INFO("current: %x intended: %x result: %x add: %x remove: %x", |
| tracker->current_mask, tracker->intended_mask, result, tracker->add_mask, tracker->remove_mask); |
| if (tracker->intended_mask != result) { |
| ERROR("test failed."); |
| exit(1); |
| } |
| tracker->current_mask = tracker->intended_mask; |
| tracker->add_mask = 0; |
| tracker->remove_mask = 0; |
| tracker->intended_mask = 0; |
| if (tracker->iterations != 0) { |
| tracker->iterations--; |
| route_tracker_test_iterate(tracker); |
| } else { |
| INFO("test completed"); |
| exit(0); |
| } |
| } |
| #endif // BUILD_TEST_ENTRY_POINTS |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 120 |
| // indent-tabs-mode: nil |
| // End: |