Home | History | Annotate | Line # | Download | only in ServiceRegistration
      1  1.1  christos /* omr-watcher.c
      2  1.1  christos  *
      3  1.1  christos  * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
      4  1.1  christos  *
      5  1.1  christos  * Licensed under the Apache License, Version 2.0 (the "License");
      6  1.1  christos  * you may not use this file except in compliance with the License.
      7  1.1  christos  * You may obtain a copy of the License at
      8  1.1  christos  *
      9  1.1  christos  *     https://www.apache.org/licenses/LICENSE-2.0
     10  1.1  christos  *
     11  1.1  christos  * Unless required by applicable law or agreed to in writing, software
     12  1.1  christos  * distributed under the License is distributed on an "AS IS" BASIS,
     13  1.1  christos  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  1.1  christos  * See the License for the specific language governing permissions and
     15  1.1  christos  * limitations under the License.
     16  1.1  christos  *
     17  1.1  christos  * This file contains the implementation of the omr_watcher_t object, which tracks off-mesh-routable prefixes on the
     18  1.1  christos  * Thread network.
     19  1.1  christos  */
     20  1.1  christos 
     21  1.1  christos #ifndef LINUX
     22  1.1  christos #include <netinet/in.h>
     23  1.1  christos #include <net/if.h>
     24  1.1  christos #include <netinet6/in6_var.h>
     25  1.1  christos #include <netinet6/nd6.h>
     26  1.1  christos #include <net/if_media.h>
     27  1.1  christos #include <sys/stat.h>
     28  1.1  christos #else
     29  1.1  christos #define _GNU_SOURCE
     30  1.1  christos #include <netinet/in.h>
     31  1.1  christos #include <fcntl.h>
     32  1.1  christos #include <bsd/stdlib.h>
     33  1.1  christos #include <net/if.h>
     34  1.1  christos #endif
     35  1.1  christos #include <sys/socket.h>
     36  1.1  christos #include <sys/ioctl.h>
     37  1.1  christos #include <net/route.h>
     38  1.1  christos #include <netinet/icmp6.h>
     39  1.1  christos #include <stdio.h>
     40  1.1  christos #include <unistd.h>
     41  1.1  christos #include <errno.h>
     42  1.1  christos #include <stdlib.h>
     43  1.1  christos #include <string.h>
     44  1.1  christos #include <ctype.h>
     45  1.1  christos #include <arpa/inet.h>
     46  1.1  christos #if !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING
     47  1.1  christos #ifndef LINUX
     48  1.1  christos #include <sys/sysctl.h>
     49  1.1  christos #endif // LINUX
     50  1.1  christos #endif // !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING
     51  1.1  christos #include <stdlib.h>
     52  1.1  christos #include <stddef.h>
     53  1.1  christos #include <dns_sd.h>
     54  1.1  christos #include <inttypes.h>
     55  1.1  christos #include <signal.h>
     56  1.1  christos 
     57  1.1  christos #ifdef IOLOOP_MACOS
     58  1.1  christos #include <xpc/xpc.h>
     59  1.1  christos 
     60  1.1  christos #include <TargetConditionals.h>
     61  1.1  christos #include <SystemConfiguration/SystemConfiguration.h>
     62  1.1  christos #include <SystemConfiguration/SCPrivate.h>
     63  1.1  christos #include <SystemConfiguration/SCNetworkConfigurationPrivate.h>
     64  1.1  christos #include <SystemConfiguration/SCNetworkSignature.h>
     65  1.1  christos #include <network_information.h>
     66  1.1  christos 
     67  1.1  christos #include <CoreUtils/CoreUtils.h>
     68  1.1  christos #endif // IOLOOP_MACOS
     69  1.1  christos 
     70  1.1  christos #include "srp.h"
     71  1.1  christos #include "dns-msg.h"
     72  1.1  christos #include "ioloop.h"
     73  1.1  christos #include "srp-crypto.h"
     74  1.1  christos 
     75  1.1  christos #include "cti-services.h"
     76  1.1  christos #include "srp-gw.h"
     77  1.1  christos #include "srp-proxy.h"
     78  1.1  christos #include "srp-mdns-proxy.h"
     79  1.1  christos #include "adv-ctl-server.h"
     80  1.1  christos #include "dnssd-proxy.h"
     81  1.1  christos #include "srp-proxy.h"
     82  1.1  christos #include "route.h"
     83  1.1  christos #include "state-machine.h"
     84  1.1  christos #include "thread-service.h"
     85  1.1  christos #include "omr-watcher.h"
     86  1.1  christos #include "nat64.h"
     87  1.1  christos #include "route-tracker.h"
     88  1.1  christos 
     89  1.1  christos struct omr_watcher_callback {
     90  1.1  christos     omr_watcher_callback_t *next;
     91  1.1  christos     omr_watcher_event_callback_t callback;
     92  1.1  christos     omr_watcher_context_release_callback_t context_release_callback;
     93  1.1  christos     void *context;
     94  1.1  christos     bool canceled;
     95  1.1  christos };
     96  1.1  christos 
     97  1.1  christos struct omr_watcher {
     98  1.1  christos     int ref_count;
     99  1.1  christos     route_state_t *route_state;
    100  1.1  christos     omr_watcher_callback_t *callbacks;
    101  1.1  christos     cti_connection_t route_connection;
    102  1.1  christos     cti_connection_t prefix_connection;
    103  1.1  christos     wakeup_t *prefix_recheck_wakeup;
    104  1.1  christos     omr_prefix_t *prefixes;
    105  1.1  christos     void (*disconnect_callback)(void *context);
    106  1.1  christos     uint16_t prefix_connections_pending;
    107  1.1  christos     bool purge_pending;
    108  1.1  christos     bool first_time;
    109  1.1  christos     bool prefix_recheck_pending;
    110  1.1  christos     bool awaiting_unpublication;
    111  1.1  christos };
    112  1.1  christos 
    113  1.1  christos static void
    114  1.1  christos omr_prefix_metadata_set(omr_prefix_t *prefix, int metric, int flags, int rloc, bool stable, bool ncp)
    115  1.1  christos {
    116  1.1  christos     prefix->metric        = metric;
    117  1.1  christos     prefix->flags         = flags;
    118  1.1  christos     prefix->rloc          = rloc;
    119  1.1  christos     prefix->stable        = stable;
    120  1.1  christos     prefix->ncp           = ncp;
    121  1.1  christos     prefix->user          = !ncp;
    122  1.1  christos     prefix->onmesh        = CTI_PREFIX_FLAGS_ON_MESH(flags);
    123  1.1  christos     prefix->slaac         = CTI_PREFIX_FLAGS_SLAAC(flags);
    124  1.1  christos     prefix->dhcp          = CTI_PREFIX_FLAGS_DHCP(flags);
    125  1.1  christos     prefix->preferred     = CTI_PREFIX_FLAGS_PREFERRED(flags);
    126  1.1  christos     int priority       = CTI_PREFIX_FLAGS_PRIORITY(flags);
    127  1.1  christos     switch(priority) {
    128  1.1  christos     case kCTIPriorityMedium:
    129  1.1  christos         prefix->priority = omr_prefix_priority_medium;
    130  1.1  christos         break;
    131  1.1  christos     case kCTIPriorityHigh:
    132  1.1  christos         prefix->priority = omr_prefix_priority_high;
    133  1.1  christos         break;
    134  1.1  christos     default:
    135  1.1  christos     case kCTIPriorityReserved:
    136  1.1  christos         prefix->priority = omr_prefix_priority_invalid;
    137  1.1  christos         break;
    138  1.1  christos     case kCTIPriorityLow:
    139  1.1  christos         prefix->priority = omr_prefix_priority_low;
    140  1.1  christos         break;
    141  1.1  christos     }
    142  1.1  christos }
    143  1.1  christos 
    144  1.1  christos omr_prefix_t *
    145  1.1  christos omr_prefix_create(struct in6_addr *prefix, int prefix_length, int metric, int flags, int rloc, bool stable, bool ncp)
    146  1.1  christos {
    147  1.1  christos     omr_prefix_t *ret = calloc(1, sizeof(*ret));
    148  1.1  christos     if (ret != NULL) {
    149  1.1  christos         RETAIN_HERE(ret, omr_prefix);
    150  1.1  christos         ret->prefix        = *prefix;
    151  1.1  christos         ret->prefix_length = prefix_length;
    152  1.1  christos         omr_prefix_metadata_set(ret, metric, flags, rloc, stable, ncp);
    153  1.1  christos     }
    154  1.1  christos     return ret;
    155  1.1  christos }
    156  1.1  christos 
    157  1.1  christos int
    158  1.1  christos omr_prefix_flags_generate(bool on_mesh, bool preferred, bool slaac, omr_prefix_priority_t priority)
    159  1.1  christos {
    160  1.1  christos     int flags = 0;
    161  1.1  christos     if (on_mesh) {
    162  1.1  christos         CTI_PREFIX_FLAGS_ON_MESH_SET(flags, 1);
    163  1.1  christos     }
    164  1.1  christos     if (preferred) {
    165  1.1  christos         CTI_PREFIX_FLAGS_PREFERRED_SET(flags, 1);
    166  1.1  christos     }
    167  1.1  christos     if (slaac) {
    168  1.1  christos         CTI_PREFIX_FLAGS_SLAAC_SET(flags, 1);
    169  1.1  christos     }
    170  1.1  christos     if (priority) {
    171  1.1  christos         CTI_PREFIX_FLAGS_PRIORITY_SET(flags, omr_prefix_priority_to_bits(priority));
    172  1.1  christos     }
    173  1.1  christos     return flags;
    174  1.1  christos }
    175  1.1  christos 
    176  1.1  christos int
    177  1.1  christos omr_prefix_priority_to_bits(omr_prefix_priority_t priority)
    178  1.1  christos {
    179  1.1  christos     switch(priority) {
    180  1.1  christos     case omr_prefix_priority_invalid:
    181  1.1  christos         return 2;
    182  1.1  christos         break;
    183  1.1  christos     case omr_prefix_priority_low:
    184  1.1  christos         return 3;
    185  1.1  christos         break;
    186  1.1  christos     case omr_prefix_priority_medium:
    187  1.1  christos         return 0;
    188  1.1  christos         break;
    189  1.1  christos     case omr_prefix_priority_high:
    190  1.1  christos         return 1;
    191  1.1  christos         break;
    192  1.1  christos     }
    193  1.1  christos }
    194  1.1  christos 
    195  1.1  christos int
    196  1.1  christos omr_prefix_priority_to_int(omr_prefix_priority_t priority)
    197  1.1  christos {
    198  1.1  christos     switch(priority) {
    199  1.1  christos     case omr_prefix_priority_invalid:
    200  1.1  christos         // We should never be asked for an invalid priority, but if we are, low is good.
    201  1.1  christos         return -1;
    202  1.1  christos         break;
    203  1.1  christos     case omr_prefix_priority_low:
    204  1.1  christos         return -1;
    205  1.1  christos         break;
    206  1.1  christos     case omr_prefix_priority_medium:
    207  1.1  christos         return 0;
    208  1.1  christos         break;
    209  1.1  christos     case omr_prefix_priority_high:
    210  1.1  christos         return 1;
    211  1.1  christos         break;
    212  1.1  christos     }
    213  1.1  christos }
    214  1.1  christos 
    215  1.1  christos static void
    216  1.1  christos omr_prefix_finalize(omr_prefix_t *prefix)
    217  1.1  christos {
    218  1.1  christos     free(prefix);
    219  1.1  christos }
    220  1.1  christos 
    221  1.1  christos static void
    222  1.1  christos omr_watcher_finalize(omr_watcher_t *omw)
    223  1.1  christos {
    224  1.1  christos     omr_prefix_t *next;
    225  1.1  christos 
    226  1.1  christos     if (omw->prefix_recheck_wakeup != NULL) {
    227  1.1  christos         ioloop_cancel_wake_event(omw->prefix_recheck_wakeup);
    228  1.1  christos         ioloop_wakeup_release(omw->prefix_recheck_wakeup);
    229  1.1  christos         omw->prefix_recheck_wakeup = NULL;
    230  1.1  christos     }
    231  1.1  christos 
    232  1.1  christos     for (omr_prefix_t *prefix = omw->prefixes; prefix != NULL; prefix = next) {
    233  1.1  christos         next = prefix->next;
    234  1.1  christos         RELEASE_HERE(prefix, omr_prefix);
    235  1.1  christos     }
    236  1.1  christos 
    237  1.1  christos     // The omr_watcher_t can have a route_connection and a prefix_connection, but each of these will retain
    238  1.1  christos     // a reference to the omr_watcher, so we can't get here while these connections are still alive. Hence,
    239  1.1  christos     // we do not need to free them here.
    240  1.1  christos     free(omw);
    241  1.1  christos }
    242  1.1  christos 
    243  1.1  christos static void
    244  1.1  christos omr_watcher_callback_finalize(omr_watcher_t *omw, omr_watcher_callback_t *callback)
    245  1.1  christos {
    246  1.1  christos     if (callback->context != 0 && callback->context_release_callback) {
    247  1.1  christos         callback->context_release_callback(omw->route_state, callback->context);
    248  1.1  christos     }
    249  1.1  christos     free(callback);
    250  1.1  christos }
    251  1.1  christos 
    252  1.1  christos static void
    253  1.1  christos omr_watcher_purge_canceled_callbacks(void *context)
    254  1.1  christos {
    255  1.1  christos     omr_watcher_callback_t *cb, **pcb;
    256  1.1  christos     omr_watcher_t *omw = context;
    257  1.1  christos 
    258  1.1  christos     for (pcb = &omw->callbacks; *pcb; ) {
    259  1.1  christos         cb = *pcb;
    260  1.1  christos         if (cb->canceled) {
    261  1.1  christos             *pcb = cb->next;
    262  1.1  christos             omr_watcher_callback_finalize(omw, cb);
    263  1.1  christos         } else {
    264  1.1  christos             pcb = &((*pcb)->next);
    265  1.1  christos         }
    266  1.1  christos     }
    267  1.1  christos     RELEASE_HERE(omw, omr_watcher);
    268  1.1  christos }
    269  1.1  christos 
    270  1.1  christos static void
    271  1.1  christos omr_watcher_send_prefix_event(omr_watcher_t *omw, omr_watcher_event_type_t event_type,
    272  1.1  christos                       omr_prefix_t *NULLABLE prefixes, omr_prefix_t *NULLABLE prefix)
    273  1.1  christos {
    274  1.1  christos     omr_watcher_callback_t *cb;
    275  1.1  christos 
    276  1.1  christos     for (cb = omw->callbacks; cb; cb = cb->next) {
    277  1.1  christos         if (!cb->canceled) {
    278  1.1  christos             cb->callback(omw->route_state, cb->context, event_type, prefixes, prefix);
    279  1.1  christos         }
    280  1.1  christos     }
    281  1.1  christos }
    282  1.1  christos 
    283  1.1  christos static void
    284  1.1  christos omr_watcher_prefix_list_callback(void *context, cti_prefix_vec_t *prefixes, cti_status_t status)
    285  1.1  christos {
    286  1.1  christos     omr_watcher_t *omw = context;
    287  1.1  christos     size_t i;
    288  1.1  christos     omr_prefix_t **ppref = &omw->prefixes, *prefix = NULL, **new = NULL;
    289  1.1  christos     bool something_changed = false;
    290  1.1  christos     bool user_prefix_seen = false;
    291  1.1  christos 
    292  1.1  christos     INFO("status: %d  prefixes: %p  count: %d", status, prefixes, prefixes == NULL ? -1 : (int)prefixes->num);
    293  1.1  christos 
    294  1.1  christos     if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
    295  1.1  christos         INFO("disconnected");
    296  1.1  christos         omw->disconnect_callback(omw->route_state);
    297  1.1  christos         goto out;
    298  1.1  christos     }
    299  1.1  christos 
    300  1.1  christos     if (status != kCTIStatus_NoError) {
    301  1.1  christos         ERROR("unhandled error %d", status);
    302  1.1  christos         goto out;
    303  1.1  christos     }
    304  1.1  christos 
    305  1.1  christos     // Delete any prefixes that are not in the list provided by Thread.
    306  1.1  christos     while (*ppref != NULL) {
    307  1.1  christos         prefix = *ppref;
    308  1.1  christos 
    309  1.1  christos         for (i = 0; i < prefixes->num; i++) {
    310  1.1  christos             cti_prefix_t *cti_prefix = prefixes->prefixes[i];
    311  1.1  christos 
    312  1.1  christos             // Is this prefix still present?
    313  1.1  christos             if (!in6prefix_compare(&prefix->prefix, &cti_prefix->prefix, 8)) {
    314  1.1  christos                 break;
    315  1.1  christos             }
    316  1.1  christos         }
    317  1.1  christos         if (i == prefixes->num) {
    318  1.1  christos             omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_withdrawn, NULL, prefix);
    319  1.1  christos             *ppref = prefix->next;
    320  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    321  1.1  christos             INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d went away" PUB_S_SRP PUB_S_SRP PUB_S_SRP,
    322  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
    323  1.1  christos                  prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : "");
    324  1.1  christos             RELEASE_HERE(prefix, omr_prefix);
    325  1.1  christos             something_changed = true;
    326  1.1  christos         } else {
    327  1.1  christos             // We'll re-initialize these flags from the prefix list when we check for duplicates.
    328  1.1  christos             prefix->previous_user = prefix->user;
    329  1.1  christos             prefix->previous_ncp = prefix->ncp;
    330  1.1  christos             prefix->previous_stable = prefix->stable;
    331  1.1  christos             prefix->user = false;
    332  1.1  christos             prefix->stable = false;
    333  1.1  christos             prefix->ncp = false;
    334  1.1  christos             ppref = &prefix->next;
    335  1.1  christos             prefix->removed = false;
    336  1.1  christos             prefix->added = false;
    337  1.1  christos             prefix->ignore = false;
    338  1.1  christos         }
    339  1.1  christos     }
    340  1.1  christos 
    341  1.1  christos     // On exit, ppref is pointing to the end-of-list pointer. If after we scan the cti prefix list a second time,
    342  1.1  christos     // we discover new prefixes, the first new prefix will be pointed to by *new.
    343  1.1  christos     new = ppref;
    344  1.1  christos 
    345  1.1  christos     // Add any prefixes that are not present.
    346  1.1  christos     for (i = 0; i < prefixes->num; i++) {
    347  1.1  christos         cti_prefix_t *cti_prefix = prefixes->prefixes[i];
    348  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(cti_prefix->prefix.s6_addr, prefix_buf);
    349  1.1  christos         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is in thread-supplied prefix list",
    350  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(cti_prefix->prefix.s6_addr, prefix_buf), cti_prefix->prefix_length);
    351  1.1  christos         for (prefix = omw->prefixes; prefix != NULL; prefix = prefix->next) {
    352  1.1  christos             if (!in6addr_compare(&prefix->prefix, &cti_prefix->prefix)) {
    353  1.1  christos                 INFO("present");
    354  1.1  christos                 break;
    355  1.1  christos             }
    356  1.1  christos         }
    357  1.1  christos         if (prefix == NULL) {
    358  1.1  christos             INFO("not present");
    359  1.1  christos             prefix = omr_prefix_create(&cti_prefix->prefix, cti_prefix->prefix_length, cti_prefix->metric,
    360  1.1  christos                                        cti_prefix->flags, cti_prefix->rloc, cti_prefix->stable, cti_prefix->ncp);
    361  1.1  christos             if (prefix == NULL) {
    362  1.1  christos                 ERROR("no memory for prefix.");
    363  1.1  christos             } else {
    364  1.1  christos                 *ppref = prefix;
    365  1.1  christos                 ppref = &prefix->next;
    366  1.1  christos             }
    367  1.1  christos         }
    368  1.1  christos         // Also, since we're combing the list, update ncp, user and stable flags.   Note that a prefix can
    369  1.1  christos         // appear more than once in the thread prefix list. Also look for a mismatch between the priority: if
    370  1.1  christos         // we see a "user" priority of low and a "ncp" priority of high, this is a bug in the
    371  1.1  christos         if (prefix != NULL) {
    372  1.1  christos             if (cti_prefix->ncp) {
    373  1.1  christos                 prefix->ncp = true;
    374  1.1  christos             } else {
    375  1.1  christos                 user_prefix_seen = true;
    376  1.1  christos                 prefix->user = true;
    377  1.1  christos             }
    378  1.1  christos             if (cti_prefix->stable) {
    379  1.1  christos                 prefix->stable = true;
    380  1.1  christos             }
    381  1.1  christos         }
    382  1.1  christos     }
    383  1.1  christos     for (prefix = omw->prefixes; prefix != NULL && prefix != *new; prefix = prefix->next) {
    384  1.1  christos         if (prefix->user != prefix->previous_user || prefix->ncp != prefix->previous_ncp ||
    385  1.1  christos             prefix->previous_stable != prefix->stable)
    386  1.1  christos         {
    387  1.1  christos             omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_flags_changed, NULL, prefix);
    388  1.1  christos             something_changed = true;
    389  1.1  christos         }
    390  1.1  christos     }
    391  1.1  christos     for (prefix = *new; prefix; prefix = prefix->next) {
    392  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    393  1.1  christos         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d showed up" PUB_S_SRP PUB_S_SRP PUB_S_SRP,
    394  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
    395  1.1  christos              prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : "");
    396  1.1  christos         omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_added, NULL, prefix);
    397  1.1  christos         something_changed = true;
    398  1.1  christos     }
    399  1.1  christos     INFO("omw->prefixes = %p", omw->prefixes);
    400  1.1  christos     for (prefix = omw->prefixes; prefix != NULL; prefix = prefix->next) {
    401  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    402  1.1  christos         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is currently in the list " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
    403  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
    404  1.1  christos              prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : "");
    405  1.1  christos     }
    406  1.1  christos     if (something_changed || omw->first_time) {
    407  1.1  christos         omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_update_finished, omw->prefixes, NULL);
    408  1.1  christos         omw->first_time = false;
    409  1.1  christos     }
    410  1.1  christos     if (!user_prefix_seen && omw->route_state->srp_server->awaiting_prefix_removal) {
    411  1.1  christos         omw->route_state->srp_server->awaiting_prefix_removal = false;
    412  1.1  christos         adv_ctl_thread_shutdown_status_check(omw->route_state->srp_server);
    413  1.1  christos     }
    414  1.1  christos out:
    415  1.1  christos     // Discontinue events (currently we'll only get one callback: this just dereferences the object so it can be freed.)
    416  1.1  christos     INFO("prefix_connections_pending = %d", omw->prefix_connections_pending);
    417  1.1  christos     omw->prefix_connections_pending--;
    418  1.1  christos     if (omw->prefix_connection != NULL) {
    419  1.1  christos         cti_events_discontinue(omw->prefix_connection);
    420  1.1  christos         omw->prefix_connection = NULL;
    421  1.1  christos         RELEASE_HERE(omw, omr_watcher); // We aren't going to get another callback.
    422  1.1  christos     }
    423  1.1  christos }
    424  1.1  christos 
    425  1.1  christos omr_watcher_callback_t *
    426  1.1  christos omr_watcher_callback_add_(omr_watcher_t *omw, omr_watcher_event_callback_t callback,
    427  1.1  christos                           omr_watcher_context_release_callback_t context_release, void *context,
    428  1.1  christos                           const char *UNUSED file, int UNUSED line)
    429  1.1  christos {
    430  1.1  christos     omr_watcher_callback_t *ret = calloc(1, sizeof (*ret));
    431  1.1  christos     if (ret != NULL) {
    432  1.1  christos         omr_watcher_callback_t **cpp = &omw->callbacks;
    433  1.1  christos         ret->callback = callback;
    434  1.1  christos         ret->context = context;
    435  1.1  christos         ret->context_release_callback = context_release;
    436  1.1  christos         while (*cpp) {
    437  1.1  christos             cpp = &((*cpp)->next);
    438  1.1  christos         }
    439  1.1  christos         *cpp = ret;
    440  1.1  christos     }
    441  1.1  christos     return ret;
    442  1.1  christos }
    443  1.1  christos 
    444  1.1  christos void
    445  1.1  christos omr_watcher_callback_cancel(omr_watcher_t *omw, omr_watcher_callback_t *callback)
    446  1.1  christos {
    447  1.1  christos     if (omw->prefix_recheck_wakeup != NULL) {
    448  1.1  christos         ioloop_cancel_wake_event(omw->prefix_recheck_wakeup);
    449  1.1  christos         ioloop_wakeup_release(omw->prefix_recheck_wakeup);
    450  1.1  christos         omw->prefix_recheck_wakeup = NULL;
    451  1.1  christos     }
    452  1.1  christos     for (omr_watcher_callback_t *cb = omw->callbacks; cb != NULL; cb = cb->next) {
    453  1.1  christos         if (cb == callback) {
    454  1.1  christos             // Because a callback might be removed during a callback, and we don't want to have to worry about the callback
    455  1.1  christos             // list being modified while we're traversing it, we just mark the callback canceled so it won't be called again
    456  1.1  christos             // and schedule omr_watcher_purge_canceled_callbacks to run after we return to the event loop, where it will free any
    457  1.1  christos             // callbacks marked canceled. We retain omw here in case one of the callbacks releases the last reference to it.
    458  1.1  christos             cb->canceled = true;
    459  1.1  christos             if (!omw->purge_pending) {
    460  1.1  christos                 omw->purge_pending = true;
    461  1.1  christos                 RETAIN_HERE(omw, omr_watcher);
    462  1.1  christos                 ioloop_run_async(omr_watcher_purge_canceled_callbacks, omw);
    463  1.1  christos             }
    464  1.1  christos         }
    465  1.1  christos     }
    466  1.1  christos }
    467  1.1  christos 
    468  1.1  christos omr_watcher_t *
    469  1.1  christos omr_watcher_create_(route_state_t *route_state, void (*disconnect_callback)(void *), const char *UNUSED file, int UNUSED line)
    470  1.1  christos {
    471  1.1  christos     omr_watcher_t *omw = calloc(1, sizeof (*omw));
    472  1.1  christos     RETAIN_HERE(omw, omr_watcher);
    473  1.1  christos     omw->route_state = route_state;
    474  1.1  christos     omw->disconnect_callback = disconnect_callback;
    475  1.1  christos     omw->first_time = true;
    476  1.1  christos     return omw;
    477  1.1  christos }
    478  1.1  christos 
    479  1.1  christos static void
    480  1.1  christos omr_watcher_offmesh_route_list_callback(void *context, cti_route_vec_t *vec, cti_status_t status)
    481  1.1  christos {
    482  1.1  christos     omr_watcher_t *omw = context;
    483  1.1  christos #if SRP_FEATURE_NAT64
    484  1.1  christos     if (omw->route_state->nat64 != NULL) {
    485  1.1  christos         nat64_offmesh_route_list_callback(omw->route_state, vec, status);
    486  1.1  christos     }
    487  1.1  christos #endif
    488  1.1  christos     if (status == kCTIStatus_NoError) {
    489  1.1  christos         if (omw->route_state->route_tracker != NULL) {
    490  1.1  christos             route_tracker_monitor_mesh_routes(omw->route_state->route_tracker, vec);
    491  1.1  christos         }
    492  1.1  christos     }
    493  1.1  christos     // Release the context if it hasn't already been released.
    494  1.1  christos     if (omw->route_state->thread_route_context) {
    495  1.1  christos         cti_events_discontinue(omw->route_state->thread_route_context);
    496  1.1  christos         omw->route_state->thread_route_context = NULL;
    497  1.1  christos         RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection.
    498  1.1  christos     }
    499  1.1  christos }
    500  1.1  christos 
    501  1.1  christos static void
    502  1.1  christos omr_watcher_wakeup_release(void *context)
    503  1.1  christos {
    504  1.1  christos     omr_watcher_t *watcher = context;
    505  1.1  christos     RELEASE_HERE(watcher, omr_watcher);
    506  1.1  christos }
    507  1.1  christos 
    508  1.1  christos static void omr_watcher_prefix_list_fetch(omr_watcher_t *watcher);
    509  1.1  christos 
    510  1.1  christos static void
    511  1.1  christos omr_watcher_prefix_recheck_wakeup(void *context)
    512  1.1  christos {
    513  1.1  christos     omr_watcher_t *watcher = context;
    514  1.1  christos     watcher->prefix_recheck_pending = false;
    515  1.1  christos     bool need_recheck = false;
    516  1.1  christos 
    517  1.1  christos     // See if there are any prefixes on the list that should have been updated but haven't been
    518  1.1  christos     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
    519  1.1  christos         if (prefix->added || prefix->removed) {
    520  1.1  christos             need_recheck = true;
    521  1.1  christos         }
    522  1.1  christos     }
    523  1.1  christos     if (need_recheck) {
    524  1.1  christos         INFO("prefixes expected to be refreshed were not.");
    525  1.1  christos         omr_watcher_prefix_list_fetch(watcher);
    526  1.1  christos     }
    527  1.1  christos }
    528  1.1  christos 
    529  1.1  christos static void
    530  1.1  christos omr_watcher_prefix_list_fetch(omr_watcher_t *watcher)
    531  1.1  christos {
    532  1.1  christos     // Postpone any recheck, since we're checking now.
    533  1.1  christos     if (watcher->prefix_recheck_pending && watcher->prefix_recheck_wakeup != NULL) {
    534  1.1  christos         ioloop_add_wake_event(watcher->prefix_recheck_wakeup, watcher, omr_watcher_prefix_recheck_wakeup,
    535  1.1  christos                               omr_watcher_wakeup_release, 15 * MSEC_PER_SEC);
    536  1.1  christos         RETAIN_HERE(watcher, omr_watcher); // for wake event
    537  1.1  christos         watcher->prefix_recheck_pending = true;
    538  1.1  christos     }
    539  1.1  christos 
    540  1.1  christos     int rv = cti_get_onmesh_prefix_list(watcher->route_state->srp_server, &watcher->prefix_connection,
    541  1.1  christos                                         watcher, omr_watcher_prefix_list_callback, NULL);
    542  1.1  christos     if (rv != kCTIStatus_NoError) {
    543  1.1  christos         ERROR("can't get onmesh prefix list: %d", rv);
    544  1.1  christos         return;
    545  1.1  christos     }
    546  1.1  christos     INFO("prefix_connections_pending = %d", watcher->prefix_connections_pending);
    547  1.1  christos     watcher->prefix_connections_pending++;
    548  1.1  christos     RETAIN_HERE(watcher, omr_watcher); // For the callback
    549  1.1  christos }
    550  1.1  christos 
    551  1.1  christos // For now, the onmesh prefix property doesn't support change events, so we track the IPv6:Routes property, which does.
    552  1.1  christos static void
    553  1.1  christos omr_watcher_route_update_callback(void *context, cti_prefix_vec_t *UNUSED prefixes, cti_status_t status)
    554  1.1  christos {
    555  1.1  christos     omr_watcher_t *omw = context;
    556  1.1  christos     if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
    557  1.1  christos         INFO("disconnected");
    558  1.1  christos         // Note: this will cancel and release route_state->omr_watcher, which will result in omw->route_connection being NULL
    559  1.1  christos         // when we exit.
    560  1.1  christos         omw->disconnect_callback(omw->route_state);
    561  1.1  christos         goto fail;
    562  1.1  christos     }
    563  1.1  christos 
    564  1.1  christos     if (status != kCTIStatus_NoError) {
    565  1.1  christos         ERROR("unhandled error %d", status);
    566  1.1  christos         goto fail;
    567  1.1  christos     }
    568  1.1  christos 
    569  1.1  christos     // Release the context if there's one ongoing.
    570  1.1  christos     if (omw->prefix_connection != NULL) {
    571  1.1  christos         omw->prefix_connections_pending--;
    572  1.1  christos         cti_events_discontinue(omw->prefix_connection);
    573  1.1  christos         omw->prefix_connection = NULL;
    574  1.1  christos         RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection.
    575  1.1  christos     }
    576  1.1  christos 
    577  1.1  christos     omr_watcher_prefix_list_fetch(omw);
    578  1.1  christos 
    579  1.1  christos     // check offmesh routes
    580  1.1  christos     INFO("prefix_list finished, start to get offmesh route list");
    581  1.1  christos     // Release the context if there's one ongoing.
    582  1.1  christos     if (omw->route_state->thread_route_context) {
    583  1.1  christos         cti_events_discontinue(omw->route_state->thread_route_context);
    584  1.1  christos         omw->route_state->thread_route_context = NULL;
    585  1.1  christos         RELEASE_HERE(omw, omr_watcher); // we won't get any more callbacks on this connection.
    586  1.1  christos     }
    587  1.1  christos     int rv = cti_get_offmesh_route_list(omw->route_state->srp_server, &omw->route_state->thread_route_context,
    588  1.1  christos                                         omw, omr_watcher_offmesh_route_list_callback, NULL);
    589  1.1  christos     if (rv != kCTIStatus_NoError) {
    590  1.1  christos         ERROR("can't get offmesh route: %d", status);
    591  1.1  christos         return;
    592  1.1  christos     }
    593  1.1  christos     RETAIN_HERE(omw, omr_watcher); // For the callback
    594  1.1  christos     // We can expect further events.
    595  1.1  christos     return;
    596  1.1  christos 
    597  1.1  christos fail:
    598  1.1  christos     // We don't want any more events.
    599  1.1  christos     if (omw->route_connection) {
    600  1.1  christos         cti_events_discontinue(omw->route_connection);
    601  1.1  christos         omw->route_connection = NULL;
    602  1.1  christos         RELEASE_HERE(omw, omr_watcher); // We won't get any more callbacks, so release the omr_watcher_t.
    603  1.1  christos     }
    604  1.1  christos }
    605  1.1  christos 
    606  1.1  christos bool
    607  1.1  christos omr_watcher_start(omr_watcher_t *omw)
    608  1.1  christos {
    609  1.1  christos     int status = cti_get_prefix_list(omw->route_state->srp_server, &omw->route_connection,
    610  1.1  christos                                      omw, omr_watcher_route_update_callback, NULL);
    611  1.1  christos     if (status == kCTIStatus_NoError) {
    612  1.1  christos         RETAIN_HERE(omw, omr_watcher); // for the callback
    613  1.1  christos         return true;
    614  1.1  christos     }
    615  1.1  christos     return false;
    616  1.1  christos }
    617  1.1  christos 
    618  1.1  christos void
    619  1.1  christos omr_watcher_cancel(omr_watcher_t *omw)
    620  1.1  christos {
    621  1.1  christos     // In case the only remaining reference(s) are held by the callbacks (which should never be the case).
    622  1.1  christos     RETAIN_HERE(omw, omr_watcher);
    623  1.1  christos 
    624  1.1  christos     INFO("prefix_connections_pending = %d", omw->prefix_connections_pending);
    625  1.1  christos     if (omw->prefix_connection != NULL) {
    626  1.1  christos         omw->prefix_connections_pending--;
    627  1.1  christos         cti_events_discontinue(omw->prefix_connection);
    628  1.1  christos         omw->prefix_connection = NULL;
    629  1.1  christos         RELEASE_HERE(omw, omr_watcher);
    630  1.1  christos     }
    631  1.1  christos     if (omw->route_connection != NULL) {
    632  1.1  christos         cti_events_discontinue(omw->route_connection);
    633  1.1  christos         omw->route_connection = NULL;
    634  1.1  christos         RELEASE_HERE(omw, omr_watcher);
    635  1.1  christos     }
    636  1.1  christos 
    637  1.1  christos     RELEASE_HERE(omw, omr_watcher);
    638  1.1  christos }
    639  1.1  christos 
    640  1.1  christos bool
    641  1.1  christos omr_watcher_prefix_present(omr_watcher_t *watcher, omr_prefix_priority_t priority,
    642  1.1  christos                            struct in6_addr *ignore_prefix, int ignore_prefix_length)
    643  1.1  christos {
    644  1.1  christos     static struct in6_addr in6addr_zero;
    645  1.1  christos     SEGMENTED_IPv6_ADDR_GEN_SRP(ignore_prefix, ignore_buf);
    646  1.1  christos     if (in6addr_compare(ignore_prefix, &in6addr_zero)) {
    647  1.1  christos         INFO("prefix to ignore: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    648  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(ignore_prefix->s6_addr, ignore_buf), ignore_prefix_length);
    649  1.1  christos     }
    650  1.1  christos     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
    651  1.1  christos         if (prefix->prefix_length == ignore_prefix_length && !in6addr_compare(&prefix->prefix, ignore_prefix)) {
    652  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    653  1.1  christos             INFO("ignoring prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    654  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    655  1.1  christos             continue;
    656  1.1  christos         }
    657  1.1  christos         if (prefix->priority == priority) {
    658  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    659  1.1  christos             INFO("matched prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    660  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    661  1.1  christos             return true;
    662  1.1  christos         }
    663  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    664  1.1  christos         INFO("didn't match prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    665  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    666  1.1  christos     }
    667  1.1  christos     INFO("returning false");
    668  1.1  christos     return false;
    669  1.1  christos }
    670  1.1  christos 
    671  1.1  christos bool
    672  1.1  christos omr_watcher_non_ula_prefix_present(omr_watcher_t *watcher)
    673  1.1  christos {
    674  1.1  christos     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
    675  1.1  christos         if (omr_watcher_prefix_is_non_ula_prefix(prefix)) {
    676  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    677  1.1  christos             INFO("matched prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    678  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    679  1.1  christos             return true;
    680  1.1  christos         }
    681  1.1  christos     }
    682  1.1  christos     INFO("returning false");
    683  1.1  christos     return false;
    684  1.1  christos }
    685  1.1  christos 
    686  1.1  christos bool
    687  1.1  christos omr_watcher_prefix_exists(omr_watcher_t *watcher, const struct in6_addr *address, int prefix_length)
    688  1.1  christos {
    689  1.1  christos     SEGMENTED_IPv6_ADDR_GEN_SRP(address, target_buf);
    690  1.1  christos     INFO("address: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    691  1.1  christos          SEGMENTED_IPv6_ADDR_PARAM_SRP(address->s6_addr, target_buf), prefix_length);
    692  1.1  christos     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
    693  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    694  1.1  christos         if (prefix->prefix_length == prefix_length &&
    695  1.1  christos             !in6prefix_compare(&prefix->prefix, address, (prefix_length + 7) /8))
    696  1.1  christos         {
    697  1.1  christos             INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d matches!",
    698  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    699  1.1  christos             return true;
    700  1.1  christos         }
    701  1.1  christos         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d doesn't match!",
    702  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    703  1.1  christos     }
    704  1.1  christos     return false;
    705  1.1  christos }
    706  1.1  christos 
    707  1.1  christos bool
    708  1.1  christos omr_watcher_prefix_wins(omr_watcher_t *watcher, omr_prefix_priority_t priority,
    709  1.1  christos                         struct in6_addr *my_prefix, int my_prefix_length)
    710  1.1  christos {
    711  1.1  christos     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
    712  1.1  christos         if (prefix->priority != priority) {
    713  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    714  1.1  christos             INFO("ignoring prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    715  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    716  1.1  christos             continue;
    717  1.1  christos         }
    718  1.1  christos         if (prefix->prefix_length == my_prefix_length && in6addr_compare(&prefix->prefix, my_prefix) > 0) {
    719  1.1  christos             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    720  1.1  christos             INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d won",
    721  1.1  christos                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    722  1.1  christos             return true;
    723  1.1  christos         }
    724  1.1  christos         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    725  1.1  christos         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d didn't win",
    726  1.1  christos              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    727  1.1  christos     }
    728  1.1  christos     INFO("returning false");
    729  1.1  christos     return false;
    730  1.1  christos }
    731  1.1  christos 
    732  1.1  christos omr_prefix_t *
    733  1.1  christos omr_watcher_prefixes_get(omr_watcher_t *watcher)
    734  1.1  christos {
    735  1.1  christos     return watcher->prefixes;
    736  1.1  christos }
    737  1.1  christos 
    738  1.1  christos static bool
    739  1.1  christos omr_watcher_prefix_add_remove(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length,
    740  1.1  christos                               omr_prefix_priority_t priority, bool remove)
    741  1.1  christos {
    742  1.1  christos     int flags = omr_prefix_priority_to_bits(priority) << kCTIPriorityShift;
    743  1.1  christos     omr_prefix_t **opl, *prefix = NULL;
    744  1.1  christos 
    745  1.1  christos     // If we already have this prefix, update the metadata.
    746  1.1  christos     for (opl = &watcher->prefixes; *opl != NULL; opl = &(*opl)->next) {
    747  1.1  christos         prefix = *opl;
    748  1.1  christos 
    749  1.1  christos         if (prefix->prefix_length == prefix_length &&
    750  1.1  christos             !in6prefix_compare(&prefix->prefix, prefix_bits, (prefix_length + 7) / 8))
    751  1.1  christos         {
    752  1.1  christos             omr_prefix_metadata_set(prefix, 0, flags, 0, true, true);
    753  1.1  christos             goto out;
    754  1.1  christos         }
    755  1.1  christos     }
    756  1.1  christos 
    757  1.1  christos     // Otherwise allocate a new one.
    758  1.1  christos     prefix = omr_prefix_create((struct in6_addr *)prefix_bits, prefix_length, 0, flags, 0, true, true);
    759  1.1  christos     if (prefix == NULL) {
    760  1.1  christos         goto out;
    761  1.1  christos     }
    762  1.1  christos     *opl = prefix;
    763  1.1  christos out:
    764  1.1  christos     if (prefix != NULL) {
    765  1.1  christos         if (remove) {
    766  1.1  christos             prefix->added = false;
    767  1.1  christos             prefix->removed = true;
    768  1.1  christos             prefix->ignore = true;
    769  1.1  christos         } else {
    770  1.1  christos             prefix->added = true;
    771  1.1  christos             prefix->removed = false;
    772  1.1  christos             prefix->ignore = false;
    773  1.1  christos         }
    774  1.1  christos         if (watcher->prefix_recheck_wakeup == NULL) {
    775  1.1  christos             watcher->prefix_recheck_wakeup = ioloop_wakeup_create();
    776  1.1  christos         }
    777  1.1  christos         if (watcher->prefix_recheck_wakeup != NULL) {
    778  1.1  christos             ioloop_add_wake_event(watcher->prefix_recheck_wakeup, watcher, omr_watcher_prefix_recheck_wakeup,
    779  1.1  christos                                   omr_watcher_wakeup_release, 15 * MSEC_PER_SEC);
    780  1.1  christos             RETAIN_HERE(watcher, omr_watcher); // for wake event
    781  1.1  christos             watcher->prefix_recheck_pending = true;
    782  1.1  christos         }
    783  1.1  christos     }
    784  1.1  christos     return prefix != NULL;
    785  1.1  christos }
    786  1.1  christos 
    787  1.1  christos bool
    788  1.1  christos omr_watcher_prefix_add(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length, omr_prefix_priority_t priority)
    789  1.1  christos {
    790  1.1  christos     return omr_watcher_prefix_add_remove(watcher, prefix_bits, prefix_length, priority, false);
    791  1.1  christos }
    792  1.1  christos 
    793  1.1  christos bool
    794  1.1  christos omr_watcher_prefix_remove(omr_watcher_t *watcher, const void *prefix_bits, int prefix_length)
    795  1.1  christos {
    796  1.1  christos     return omr_watcher_prefix_add_remove(watcher, prefix_bits, prefix_length, omr_prefix_priority_invalid, true);
    797  1.1  christos }
    798  1.1  christos 
    799  1.1  christos RELEASE_RETAIN_FUNCS(omr_watcher);
    800  1.1  christos RELEASE_RETAIN_FUNCS(omr_prefix);
    801  1.1  christos 
    802  1.1  christos // Local Variables:
    803  1.1  christos // mode: C
    804  1.1  christos // tab-width: 4
    805  1.1  christos // c-file-style: "bsd"
    806  1.1  christos // c-basic-offset: 4
    807  1.1  christos // fill-column: 120
    808  1.1  christos // indent-tabs-mode: nil
    809  1.1  christos // End:
    810