| /* omr-watcher.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 of the omr_watcher_t object, which tracks off-mesh-routable prefixes on the |
| * Thread network. |
| */ |
| |
| #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" |
| |
| #include "cti-services.h" |
| #include "srp-gw.h" |
| #include "srp-proxy.h" |
| #include "srp-mdns-proxy.h" |
| #include "dnssd-proxy.h" |
| #include "srp-proxy.h" |
| #include "route.h" |
| #include "state-machine.h" |
| #include "thread-service.h" |
| #include "omr-watcher.h" |
| #include "nat64.h" |
| #include "route-tracker.h" |
| |
| struct omr_watcher_callback { |
| omr_watcher_callback_t *next; |
| omr_watcher_event_callback_t callback; |
| omr_watcher_context_release_callback_t context_release_callback; |
| void *context; |
| bool canceled; |
| }; |
| |
| struct omr_watcher { |
| int ref_count; |
| route_state_t *route_state; |
| omr_watcher_callback_t *callbacks; |
| cti_connection_t route_connection; |
| cti_connection_t prefix_connection; |
| wakeup_t *prefix_recheck_wakeup; |
| omr_prefix_t *prefixes; |
| void (*disconnect_callback)(void *context); |
| uint16_t prefix_connections_pending; |
| bool purge_pending; |
| bool first_time; |
| bool prefix_recheck_pending; |
| }; |
| |
| static void |
| omr_prefix_metadata_set(omr_prefix_t *prefix, int metric, int flags, int rloc, bool stable, bool ncp) |
| { |
| prefix->metric = metric; |
| prefix->flags = flags; |
| prefix->rloc = rloc; |
| prefix->stable = stable; |
| prefix->ncp = ncp; |
| prefix->user = !ncp; |
| prefix->onmesh = CTI_PREFIX_FLAGS_ON_MESH(flags); |
| prefix->slaac = CTI_PREFIX_FLAGS_SLAAC(flags); |
| prefix->dhcp = CTI_PREFIX_FLAGS_DHCP(flags); |
| prefix->preferred = CTI_PREFIX_FLAGS_PREFERRED(flags); |
| int priority = CTI_PREFIX_FLAGS_PRIORITY(flags); |
| switch(priority) { |
| case kCTIPriorityMedium: |
| prefix->priority = omr_prefix_priority_medium; |
| break; |
| case kCTIPriorityHigh: |
| prefix->priority = omr_prefix_priority_high; |
| break; |
| default: |
| case kCTIPriorityReserved: |
| prefix->priority = omr_prefix_priority_invalid; |
| break; |
| case kCTIPriorityLow: |
| prefix->priority = omr_prefix_priority_low; |
| break; |
| } |
| } |
| |
| omr_prefix_t * |
| omr_prefix_create(struct in6_addr *prefix, int prefix_length, int metric, int flags, int rloc, bool stable, bool ncp) |
| { |
| omr_prefix_t *ret = calloc(1, sizeof(*ret)); |
| if (ret != NULL) { |
| RETAIN_HERE(ret, omr_prefix); |
| ret->prefix = *prefix; |
| ret->prefix_length = prefix_length; |
| omr_prefix_metadata_set(ret, metric, flags, rloc, stable, ncp); |
| } |
| return ret; |
| } |
| |
| int |
| omr_prefix_flags_generate(bool on_mesh, bool preferred, bool slaac, omr_prefix_priority_t priority) |
| { |
| int flags = 0; |
| if (on_mesh) { |
| CTI_PREFIX_FLAGS_ON_MESH_SET(flags, 1); |
| } |
| if (preferred) { |
| CTI_PREFIX_FLAGS_PREFERRED_SET(flags, 1); |
| } |
| if (slaac) { |
| CTI_PREFIX_FLAGS_SLAAC_SET(flags, 1); |
| } |
| if (priority) { |
| CTI_PREFIX_FLAGS_PRIORITY_SET(flags, omr_prefix_priority_to_bits(priority)); |
| } |
| return flags; |
| } |
| |
| int |
| omr_prefix_priority_to_bits(omr_prefix_priority_t priority) |
| { |
| switch(priority) { |
| case omr_prefix_priority_invalid: |
| return 2; |
| break; |
| case omr_prefix_priority_low: |
| return 3; |
| break; |
| case omr_prefix_priority_medium: |
| return 0; |
| break; |
| case omr_prefix_priority_high: |
| return 1; |
| break; |
| } |
| } |
| |
| int |
| omr_prefix_priority_to_int(omr_prefix_priority_t priority) |
| { |
| switch(priority) { |
| case omr_prefix_priority_invalid: |
| // We should never be asked for an invalid priority, but if we are, low is good. |
| return -1; |
| break; |
| case omr_prefix_priority_low: |
| return -1; |
| break; |
| case omr_prefix_priority_medium: |
| return 0; |
| break; |
| case omr_prefix_priority_high: |
| return 1; |
| break; |
| } |
| } |
| |
| static void |
| omr_prefix_finalize(omr_prefix_t *prefix) |
| { |
| free(prefix); |
| } |
| |
| static void |
| omr_watcher_finalize(omr_watcher_t *omw) |
| { |
| // The omr_watcher_t can have a route_connection and a prefix_connection, but each of these will retain |
| // a reference to the omr_watcher, so we can't get here while these connections are still alive. Hence, |
| // we do not need to free them here. |
| free(omw); |
| } |
| |
| static void |
| omr_watcher_callback_finalize(omr_watcher_t *omw, omr_watcher_callback_t *callback) |
| { |
| if (callback->context != 0 && callback->context_release_callback) { |
| callback->context_release_callback(omw->route_state, callback->context); |
| } |
| free(callback); |
| } |
| |
| static void |
| omr_watcher_purge_canceled_callbacks(void *context) |
| { |
| omr_watcher_callback_t *cb, **pcb; |
| omr_watcher_t *omw = context; |
| |
| for (pcb = &omw->callbacks; *pcb; ) { |
| cb = *pcb; |
| if (cb->canceled) { |
| *pcb = cb->next; |
| omr_watcher_callback_finalize(omw, cb); |
| } else { |
| pcb = &((*pcb)->next); |
| } |
| } |
| } |
| |
| static void |
| omr_watcher_send_prefix_event(omr_watcher_t *omw, omr_watcher_event_type_t event_type, |
| omr_prefix_t *NULLABLE prefixes, omr_prefix_t *NULLABLE prefix) |
| { |
| omr_watcher_callback_t *cb; |
| |
| for (cb = omw->callbacks; cb; cb = cb->next) { |
| if (!cb->canceled) { |
| cb->callback(omw->route_state, cb->context, event_type, prefixes, prefix); |
| } |
| } |
| } |
| |
| static void |
| omr_watcher_prefix_list_callback(void *context, cti_prefix_vec_t *prefixes, cti_status_t status) |
| { |
| omr_watcher_t *omw = context; |
| size_t i; |
| omr_prefix_t **ppref = &omw->prefixes, *prefix = NULL, **new = NULL; |
| bool something_changed = false; |
| |
| INFO("status: %d prefixes: %p count: %d", status, prefixes, prefixes == NULL ? -1 : (int)prefixes->num); |
| |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| omw->disconnect_callback(omw->route_state); |
| goto out; |
| } |
| |
| if (status != kCTIStatus_NoError) { |
| ERROR("unhandled error %d", status); |
| goto out; |
| } |
| |
| // 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 (!in6prefix_compare(&prefix->prefix, &cti_prefix->prefix, 8)) { |
| break; |
| } |
| } |
| if (i == prefixes->num) { |
| omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_withdrawn, NULL, prefix); |
| *ppref = prefix->next; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d went away" PUB_S_SRP PUB_S_SRP PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length, |
| prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : ""); |
| RELEASE_HERE(prefix, omr_prefix); |
| something_changed = true; |
| } else { |
| // We'll re-initialize these flags from the prefix list when we check for duplicates. |
| prefix->previous_user = prefix->user; |
| prefix->previous_ncp = prefix->ncp; |
| prefix->previous_stable = prefix->stable; |
| prefix->user = false; |
| prefix->stable = false; |
| prefix->ncp = false; |
| ppref = &prefix->next; |
| prefix->removed = false; |
| prefix->added = false; |
| prefix->ignore = false; |
| } |
| } |
| |
| // On exit, ppref is pointing to the end-of-list pointer. If after we scan the cti prefix list a second time, |
| // we discover new prefixes, the first new prefix will be pointed to by *new. |
| new = ppref; |
| |
| // Add any prefixes that are not present. |
| for (i = 0; i < prefixes->num; i++) { |
| cti_prefix_t *cti_prefix = prefixes->prefixes[i]; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(cti_prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is in thread-supplied prefix list", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(cti_prefix->prefix.s6_addr, prefix_buf), cti_prefix->prefix_length); |
| for (prefix = omw->prefixes; prefix != NULL; prefix = prefix->next) { |
| if (!in6addr_compare(&prefix->prefix, &cti_prefix->prefix)) { |
| INFO("present"); |
| break; |
| } |
| } |
| if (prefix == NULL) { |
| INFO("not present"); |
| prefix = omr_prefix_create(&cti_prefix->prefix, cti_prefix->prefix_length, cti_prefix->metric, |
| cti_prefix->flags, cti_prefix->rloc, cti_prefix->stable, cti_prefix->ncp); |
| if (prefix == NULL) { |
| ERROR("no memory for prefix."); |
| } else { |
| *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. Also look for a mismatch between the priority: if |
| // we see a "user" priority of low and a "ncp" priority of high, this is a bug in the |
| if (prefix != NULL) { |
| if (cti_prefix->ncp) { |
| prefix->ncp = true; |
| } else { |
| prefix->user = true; |
| } |
| if (cti_prefix->stable) { |
| prefix->stable = true; |
| } |
| } |
| } |
| for (prefix = omw->prefixes; prefix != NULL && prefix != *new; prefix = prefix->next) { |
| if (prefix->user != prefix->previous_user || prefix->ncp != prefix->previous_ncp || |
| prefix->previous_stable != prefix->stable) |
| { |
| omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_flags_changed, NULL, prefix); |
| something_changed = true; |
| } |
| } |
| for (prefix = *new; prefix; prefix = prefix->next) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d showed up" PUB_S_SRP PUB_S_SRP PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length, |
| prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : ""); |
| omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_added, NULL, prefix); |
| something_changed = true; |
| } |
| INFO("omw->prefixes = %p", omw->prefixes); |
| for (prefix = omw->prefixes; prefix != NULL; prefix = prefix->next) { |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is currently in the list " PUB_S_SRP PUB_S_SRP PUB_S_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length, |
| prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : ""); |
| } |
| if (something_changed || omw->first_time) { |
| omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_update_finished, omw->prefixes, NULL); |
| omw->first_time = false; |
| } |
| out: |
| // Discontinue events (currently we'll only get one callback: this just dereferences the object so it can be freed.) |
| INFO("prefix_connections_pending = %d", omw->prefix_connections_pending); |
| omw->prefix_connections_pending--; |
| if (omw->prefix_connection != NULL) { |
| cti_events_discontinue(omw->prefix_connection); |
| omw->prefix_connection = NULL; |
| RELEASE_HERE(omw, omr_watcher); // We aren't going to get another callback. |
| } |
| } |
| |
| omr_watcher_callback_t * |
| omr_watcher_callback_add_(omr_watcher_t *omw, omr_watcher_event_callback_t callback, |
| omr_watcher_context_release_callback_t context_release, void *context, |
| const char *UNUSED file, int UNUSED line) |
| { |
| omr_watcher_callback_t *ret = calloc(1, sizeof (*ret)); |
| if (ret != NULL) { |
| omr_watcher_callback_t **cpp = &omw->callbacks; |
| ret->callback = callback; |
| ret->context = context; |
| ret->context_release_callback = context_release; |
| while (*cpp) { |
| cpp = &((*cpp)->next); |
| } |
| *cpp = ret; |
| } |
| return ret; |
| } |
| |
| void |
| omr_watcher_callback_cancel(omr_watcher_t *omw, omr_watcher_callback_t *callback) |
| { |
| if (omw->prefix_recheck_wakeup != NULL) { |
| ioloop_cancel_wake_event(omw->prefix_recheck_wakeup); |
| ioloop_wakeup_release(omw->prefix_recheck_wakeup); |
| omw->prefix_recheck_wakeup = NULL; |
| } |
| for (omr_watcher_callback_t *cb = omw->callbacks; cb != NULL; cb = cb->next) { |
| if (cb == callback) { |
| // Because a callback might be removed during a callback, and we don't want to have to worry about the callback |
| // list being modified while we're traversing it, we just mark the callback canceled so it won't be called again |
| // and schedule omr_watcher_purge_canceled_callbacks to run after we return to the event loop, where it will free any |
| // callbacks marked canceled. We retain omw here in case one of the callbacks releases the last reference to it. |
| cb->canceled = true; |
| if (!omw->purge_pending) { |
| omw->purge_pending = true; |
| RETAIN_HERE(omw, omr_watcher); |
| ioloop_run_async(omr_watcher_purge_canceled_callbacks, omw); |
| } |
| } |
| } |
| } |
| |
| omr_watcher_t * |
| omr_watcher_create_(route_state_t *route_state, void (*disconnect_callback)(void *), const char *UNUSED file, int UNUSED line) |
| { |
| omr_watcher_t *omw = calloc(1, sizeof (*omw)); |
| RETAIN_HERE(omw, omr_watcher); |
| omw->route_state = route_state; |
| omw->disconnect_callback = disconnect_callback; |
| omw->first_time = true; |
| return omw; |
| } |
| |
| static void |
| omr_watcher_offmesh_route_list_callback(void *context, cti_route_vec_t *vec, cti_status_t status) |
| { |
| omr_watcher_t *omw = context; |
| #if SRP_FEATURE_NAT64 |
| if (omw->route_state->nat64 != NULL) { |
| nat64_offmesh_route_list_callback(omw->route_state, vec, status); |
| } |
| #endif |
| if (status == kCTIStatus_NoError) { |
| if (omw->route_state->route_tracker != NULL) { |
| route_tracker_monitor_mesh_routes(omw->route_state->route_tracker, vec); |
| } |
| } |
| // Release the context if it hasn't already been released. |
| if (omw->route_state->thread_route_context) { |
| cti_events_discontinue(omw->route_state->thread_route_context); |
| omw->route_state->thread_route_context = NULL; |
| RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection. |
| } |
| } |
| |
| static void |
| omr_watcher_wakeup_release(void *context) |
| { |
| omr_watcher_t *watcher = context; |
| RELEASE_HERE(watcher, omr_watcher); |
| } |
| |
| static void omr_watcher_prefix_list_fetch(omr_watcher_t *watcher); |
| |
| static void |
| omr_watcher_prefix_recheck_wakeup(void *context) |
| { |
| omr_watcher_t *watcher = context; |
| watcher->prefix_recheck_pending = false; |
| bool need_recheck = false; |
| |
| // See if there are any prefixes on the list that should have been updated but haven't been |
| for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) { |
| if (prefix->added || prefix->removed) { |
| need_recheck = true; |
| } |
| } |
| if (need_recheck) { |
| INFO("prefixes expected to be refreshed were not."); |
| omr_watcher_prefix_list_fetch(watcher); |
| } |
| } |
| |
| static void |
| omr_watcher_prefix_list_fetch(omr_watcher_t *watcher) |
| { |
| // Postpone any recheck, since we're checking now. |
| if (watcher->prefix_recheck_pending && watcher->prefix_recheck_wakeup != NULL) { |
| ioloop_add_wake_event(watcher->prefix_recheck_wakeup, watcher, omr_watcher_prefix_recheck_wakeup, |
| omr_watcher_wakeup_release, 15 * MSEC_PER_SEC); |
| RETAIN_HERE(watcher, omr_watcher); // for wake event |
| watcher->prefix_recheck_pending = true; |
| } |
| |
| int rv = cti_get_onmesh_prefix_list(watcher->route_state->srp_server, &watcher->prefix_connection, watcher, omr_watcher_prefix_list_callback, NULL); |
| if (rv != kCTIStatus_NoError) { |
| ERROR("can't get onmesh prefix list: %d", rv); |
| return; |
| } |
| INFO("prefix_connections_pending = %d", watcher->prefix_connections_pending); |
| watcher->prefix_connections_pending++; |
| RETAIN_HERE(watcher, omr_watcher); // For the callback |
| } |
| |
| // For now, the onmesh prefix property doesn't support change events, so we track the IPv6:Routes property, which does. |
| static void |
| omr_watcher_route_update_callback(void *context, cti_prefix_vec_t *UNUSED prefixes, cti_status_t status) |
| { |
| omr_watcher_t *omw = context; |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| // Note: this will cancel and release route_state->omr_watcher, which will result in omw->route_connection being NULL |
| // when we exit. |
| omw->disconnect_callback(omw->route_state); |
| goto fail; |
| } |
| |
| if (status != kCTIStatus_NoError) { |
| ERROR("unhandled error %d", status); |
| goto fail; |
| } |
| |
| // Release the context if there's one ongoing. |
| if (omw->prefix_connection != NULL) { |
| omw->prefix_connections_pending--; |
| cti_events_discontinue(omw->prefix_connection); |
| omw->prefix_connection = NULL; |
| RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection. |
| } |
| |
| omr_watcher_prefix_list_fetch(omw); |
| |
| // check offmesh routes |
| INFO("prefix_list finished, start to get offmesh route list"); |
| // Release the context if there's one ongoing. |
| if (omw->route_state->thread_route_context) { |
| cti_events_discontinue(omw->route_state->thread_route_context); |
| omw->route_state->thread_route_context = NULL; |
| RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection. |
| } |
| int rv = cti_get_offmesh_route_list(omw->route_state->srp_server, &omw->route_state->thread_route_context, |
| omw, omr_watcher_offmesh_route_list_callback, NULL); |
| if (rv != kCTIStatus_NoError) { |
| ERROR("can't get offmesh route: %d", status); |
| return; |
| } |
| RETAIN_HERE(omw, omr_watcher); // For the callback |
| // We can expect further events. |
| return; |
| |
| fail: |
| // We don't want any more events. |
| if (omw->route_connection) { |
| cti_events_discontinue(omw->route_connection); |
| omw->route_connection = NULL; |
| RELEASE_HERE(omw, omr_watcher); // We won't get any more callbacks, so release the omr_watcher_t. |
| } |
| } |
| |
| bool |
| omr_watcher_start(omr_watcher_t *omw) |
| { |
| int status = cti_get_prefix_list(omw->route_state->srp_server, &omw->route_connection, |
| omw, omr_watcher_route_update_callback, NULL); |
| if (status == kCTIStatus_NoError) { |
| RETAIN_HERE(omw, omr_watcher); // for the callback |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| omr_watcher_cancel(omr_watcher_t *omw) |
| { |
| // In case the only remaining reference(s) are held by the callbacks (which should never be the case). |
| RETAIN_HERE(omw, omr_watcher); |
| |
| INFO("prefix_connections_pending = %d", omw->prefix_connections_pending); |
| if (omw->prefix_connection != NULL) { |
| omw->prefix_connections_pending--; |
| cti_events_discontinue(omw->prefix_connection); |
| omw->prefix_connection = NULL; |
| RELEASE_HERE(omw, omr_watcher); |
| } |
| if (omw->route_connection != NULL) { |
| cti_events_discontinue(omw->route_connection); |
| omw->route_connection = NULL; |
| RELEASE_HERE(omw, omr_watcher); |
| } |
| |
| RELEASE_HERE(omw, omr_watcher); |
| } |
| |
| bool |
| omr_watcher_prefix_present(omr_watcher_t *watcher, omr_prefix_priority_t priority, |
| struct in6_addr *ignore_prefix, int ignore_prefix_length) |
| { |
| static struct in6_addr in6addr_zero; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(ignore_prefix->prefix.s6_addr, prefix_buf); |
| if (in6addr_compare(ignore_prefix, &in6addr_zero)) { |
| INFO("prefix to ignore: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(ignore_prefix->s6_addr, prefix_buf), ignore_prefix_length); |
| } |
| for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) { |
| if (prefix->prefix_length == ignore_prefix_length && !in6addr_compare(&prefix->prefix, ignore_prefix)) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("ignoring prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| continue; |
| } |
| if (prefix->priority == priority) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("matched prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| return true; |
| } |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("didn't match prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| } |
| INFO("returning false"); |
| return false; |
| } |
| |
| bool |
| omr_watcher_prefix_exists(omr_watcher_t *watcher, const struct in6_addr *address, int prefix_length) |
| { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(address->prefix.s6_addr, target_buf); |
| INFO("address: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(address->s6_addr, target_buf), prefix_length); |
| for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| if (prefix->prefix_length == prefix_length && |
| !in6prefix_compare(&prefix->prefix, address, (prefix_length + 7) /8)) |
| { |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d matches!", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| return true; |
| } |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d doesn't match!", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| } |
| return false; |
| } |
| |
| bool |
| omr_watcher_prefix_wins(omr_watcher_t *watcher, omr_prefix_priority_t priority, |
| struct in6_addr *my_prefix, int my_prefix_length) |
| { |
| for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) { |
| if (prefix->priority != priority) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("ignoring prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| continue; |
| } |
| if (prefix->prefix_length == my_prefix_length && in6addr_compare(&prefix->prefix, my_prefix) > 0) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d won", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| return true; |
| } |
| SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf); |
| INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d didn't win", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length); |
| } |
| INFO("returning false"); |
| return false; |
| } |
| |
| omr_prefix_t * |
| omr_watcher_prefixes_get(omr_watcher_t *watcher) |
| { |
| return watcher->prefixes; |
| } |
| |
| static bool |
| omr_watcher_prefix_add_remove(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length, |
| omr_prefix_priority_t priority, bool remove) |
| { |
| int flags = omr_prefix_priority_to_bits(priority) << kCTIPriorityShift; |
| omr_prefix_t **opl, *prefix = NULL; |
| |
| // If we already have this prefix, update the metadata. |
| for (opl = &watcher->prefixes; *opl != NULL; opl = &(*opl)->next) { |
| prefix = *opl; |
| |
| if (prefix->prefix_length == prefix_length && |
| !in6prefix_compare(&prefix->prefix, prefix_bits, (prefix_length + 7) / 8)) |
| { |
| omr_prefix_metadata_set(prefix, 0, flags, 0, true, true); |
| goto out; |
| } |
| } |
| |
| // Otherwise allocate a new one. |
| prefix = omr_prefix_create((struct in6_addr *)prefix_bits, prefix_length, 0, flags, 0, true, true); |
| if (prefix == NULL) { |
| goto out; |
| } |
| *opl = prefix; |
| out: |
| if (prefix != NULL) { |
| if (remove) { |
| prefix->added = false; |
| prefix->removed = true; |
| prefix->ignore = true; |
| } else { |
| prefix->added = true; |
| prefix->removed = false; |
| prefix->ignore = false; |
| } |
| if (watcher->prefix_recheck_wakeup == NULL) { |
| watcher->prefix_recheck_wakeup = ioloop_wakeup_create(); |
| } |
| if (watcher->prefix_recheck_wakeup != NULL) { |
| ioloop_add_wake_event(watcher->prefix_recheck_wakeup, watcher, omr_watcher_prefix_recheck_wakeup, |
| omr_watcher_wakeup_release, 15 * MSEC_PER_SEC); |
| RETAIN_HERE(watcher, omr_watcher); // for wake event |
| watcher->prefix_recheck_pending = true; |
| } |
| } |
| return prefix != NULL; |
| } |
| |
| bool |
| omr_watcher_prefix_add(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length, omr_prefix_priority_t priority) |
| { |
| return omr_watcher_prefix_add_remove(watcher, prefix_bits, prefix_length, priority, false); |
| } |
| |
| bool |
| omr_watcher_prefix_remove(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length) |
| { |
| return omr_watcher_prefix_add_remove(watcher, prefix_bits, prefix_length, omr_prefix_priority_invalid, true); |
| } |
| |
| RELEASE_RETAIN_FUNCS(omr_watcher); |
| RELEASE_RETAIN_FUNCS(omr_prefix); |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 120 |
| // indent-tabs-mode: nil |
| // End: |