| /* service-publisher.c |
| * |
| * Copyright (c) 2023-2024 Apple Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * This file contains code to queue and send updates for Thread services. |
| */ |
| |
| #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> |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <dns_sd.h> |
| #include <inttypes.h> |
| #include <signal.h> |
| |
| #ifdef IOLOOP_MACOS |
| #include <xpc/xpc.h> |
| |
| #include <TargetConditionals.h> |
| #include <SystemConfiguration/SystemConfiguration.h> |
| #include <SystemConfiguration/SCPrivate.h> |
| #include <SystemConfiguration/SCNetworkConfigurationPrivate.h> |
| #include <SystemConfiguration/SCNetworkSignature.h> |
| #include <network_information.h> |
| |
| #include <CoreUtils/CoreUtils.h> |
| #endif // IOLOOP_MACOS |
| |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "ioloop.h" |
| #include "srp-crypto.h" |
| |
| #include "cti-services.h" |
| #include "srp-gw.h" |
| #include "srp-proxy.h" |
| #include "srp-mdns-proxy.h" |
| #include "adv-ctl-server.h" |
| #include "dnssd-proxy.h" |
| #include "srp-proxy.h" |
| #include "route.h" |
| #include "ifpermit.h" |
| #include "srp-dnssd.h" |
| |
| #define STATE_MACHINE_IMPLEMENTATION 1 |
| typedef enum { |
| service_publisher_state_invalid, |
| service_publisher_state_startup, |
| service_publisher_state_waiting_to_publish, |
| service_publisher_state_not_publishing, |
| service_publisher_state_start_listeners, |
| service_publisher_state_publishing, |
| } state_machine_state_t; |
| #define state_machine_state_invalid service_publisher_state_invalid |
| |
| #include "state-machine.h" |
| #include "thread-service.h" |
| #include "service-tracker.h" |
| |
| #include "service-publisher.h" |
| #include "thread-tracker.h" |
| #include "node-type-tracker.h" |
| |
| // This is pretty short because our normal use case is connecting to a single device, so we don't actually anticipate |
| // normally seeing a service. But if we do, we don't suffer power events, so we don't need to desynchronize from other |
| // devices booting at the same time. So we just want to wait long enough that if there's already a service published, |
| // we see it and don't publish. |
| #define SERVICE_PUBLISHER_START_WAIT 750 |
| |
| // When a competing service is lost, we want to wait a bit longer. |
| #define SERVICE_PUBLISHER_LOST_WAIT 5000 |
| |
| // When the listener restarts, we don't actually want to wait. |
| #define SERVICE_PUBLISHER_LISTENER_RESTART_WAIT 1 |
| |
| #define ADDRESS_RECORD_TTL 4500 |
| #define OTHER_RECORD_TTL 4500 |
| |
| struct service_publisher { |
| int ref_count; |
| state_machine_header_t state_header; |
| char *id; |
| char *thread_interface_name; |
| srp_server_t *server_state; |
| wakeup_t *NULLABLE wakeup_timer; |
| wakeup_t *NULLABLE sed_timeout; |
| comm_t *srp_listener; |
| void (*reconnect_callback)(void *context); |
| thread_service_t *published_unicast_service; |
| thread_service_t *published_anycast_service; |
| thread_service_t *publication_queue; |
| cti_connection_t active_data_set_connection; |
| cti_connection_t wed_tracker_connection; |
| cti_connection_t neighbor_tracker_connection; |
| struct in6_addr thread_mesh_local_address; |
| struct in6_addr wed_ml_eid; |
| struct in6_addr neighbor_ml_eid; |
| char *NULLABLE wed_ext_address_string; |
| char *NULLABLE wed_ml_eid_string; |
| char *NULLABLE neighbor_ml_eid_string; |
| int startup_delay_range; |
| int retry_interval; // Retry interval in seconds for re-publishing service(s) |
| uint16_t srp_listener_port; |
| bool have_ml_eid; |
| bool first_time; |
| bool force_publication; |
| bool canceled; |
| bool have_srp_listener; |
| bool seen_service_list; |
| bool stopped; |
| bool have_thread_interface_name; |
| bool have_unicast_in_net_data, have_anycast_in_net_data; |
| bool cached_services_published, started_stale_service_timeout; |
| }; |
| |
| static uint64_t service_publisher_serial_number; |
| |
| static void service_publisher_queue_run(service_publisher_t *publisher); |
| |
| void |
| service_publisher_unadvertise_all(service_publisher_t *publisher) |
| { |
| srp_server_t *server_state = publisher->server_state; |
| |
| publisher->cached_services_published = false; |
| for (adv_host_t *host = server_state->hosts; host; host = host->next) { |
| // If we have an outstanding update, finish it. |
| if (host->update != NULL) { |
| srp_mdns_update_finished(host->update); |
| } |
| if (host->addresses != NULL) { |
| for (int i = 0; i < host->addresses->num; i++) { |
| adv_record_t *record = host->addresses->vec[i]; |
| if (record != NULL) { |
| if (record->rrtype == dns_rrtype_aaaa && record->rdlen == 16) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf); |
| INFO("unadvertising " PRI_S_SRP " IN AAAA " PRI_SEGMENTED_IPv6_ADDR_SRP " rec %p rref %p", |
| host->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf), |
| record, record->rref); |
| } |
| srp_mdns_shared_record_remove(server_state, record); |
| // DNSServiceRemoveRecord should clear the cache, but it doesn't. |
| DNSServiceReconfirmRecord(0, server_state->advertise_interface, host->name, record->rrtype, |
| dns_qclass_in, record->rdlen, record->rdata); |
| } |
| } |
| } |
| if (host->key_record != NULL) { |
| srp_mdns_shared_record_remove(server_state, host->key_record); |
| } |
| if (host->instances != NULL) { |
| for (int i = 0; i < host->instances->num; i++) { |
| adv_instance_t *instance = host->instances->vec[i]; |
| if (instance != NULL && instance->txn != NULL) { |
| INFO("unadvertising " PRI_S_SRP "." PRI_S_SRP " instance %p sdref %p", |
| instance->instance_name, instance->service_type, instance, instance->txn->sdref); |
| ioloop_dnssd_txn_cancel(instance->txn); |
| ioloop_dnssd_txn_release(instance->txn); |
| instance->txn = NULL; |
| } |
| } |
| } |
| } |
| } |
| |
| static void |
| service_publisher_instance_callback(DNSServiceRef UNUSED sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code, |
| const char *name, const char *regtype, const char *domain, void *context) |
| { |
| adv_instance_t *instance = context; |
| if (error_code != kDNSServiceErr_NoError) { |
| INFO("DNSServiceRegister failed: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP ": %d (instance %p sdref %p)", |
| name, regtype, domain, error_code, instance, instance->txn == NULL ? 0 : instance->txn->sdref); |
| ioloop_dnssd_txn_cancel(instance->txn); |
| ioloop_dnssd_txn_release(instance->txn); |
| instance->txn = NULL; |
| } else { |
| INFO("DNSServiceRegister succeeded: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP " (instance %p sdref %p)", |
| instance->instance_name, instance->service_type, instance->host->registered_name, |
| instance, instance->txn == NULL ? 0 : instance->txn->sdref); |
| } |
| } |
| |
| static void |
| service_publisher_re_advertise_instance(srp_server_t *server_state, adv_host_t *host, adv_instance_t *instance) |
| { |
| DNSServiceRef service_ref = server_state->shared_registration_txn->sdref; |
| |
| // Make sure we don't double-register. |
| if (instance->txn != NULL) { |
| if (instance->txn->sdref != NULL) { |
| if (instance->shared_txn == (intptr_t)server_state->shared_registration_txn) { |
| INFO("instance is already registered: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP |
| " (instance %p sdref %p)", |
| instance->instance_name, instance->service_type, host->registered_name, |
| instance, instance->txn->sdref); |
| return; |
| } |
| INFO("instance registration is stale: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP |
| " (instance %p sdref %p)", |
| instance->instance_name, instance->service_type, host->registered_name, |
| instance, instance->txn->sdref); |
| instance->txn->sdref = NULL; |
| } |
| ioloop_dnssd_txn_release(instance->txn); |
| instance->txn = NULL; |
| } |
| |
| // Get the TSR attribute for the host object. |
| char time_buf[TSR_TIMESTAMP_STRING_LEN]; |
| DNSServiceAttributeRef tsr_attribute = srp_message_tsr_attribute_generate(NULL, host->key_id, |
| time_buf, sizeof(time_buf)); |
| |
| int err = dns_service_register_wa(server_state, &service_ref, |
| (kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename | |
| kDNSServiceFlagsKnownUnique), server_state->advertise_interface, |
| instance->instance_name, instance->service_type, NULL, |
| host->registered_name, htons(instance->port), instance->txt_length, |
| instance->txt_data, tsr_attribute, service_publisher_instance_callback, instance); |
| |
| // This would happen if we pass NULL for regtype, which we don't, or if we run out of memory, or if |
| // the server isn't running; in the second two cases, we can always try again later. |
| if (err != kDNSServiceErr_NoError) { |
| INFO("DNSServiceRegister failed: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP ": %d (instance %p)", |
| instance->instance_name, instance->service_type, host->registered_name, err, instance); |
| } else { |
| INFO("DNSServiceRegister succeeded: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP " at " PUB_S_SRP |
| " (instance %p sdref %p)", |
| instance->instance_name, instance->service_type, host->registered_name, time_buf, |
| instance, service_ref); |
| instance->txn = ioloop_dnssd_txn_add_subordinate(service_ref, instance, adv_instance_context_release, NULL); |
| if (instance->txn == NULL) { |
| ERROR("no memory for instance transaction."); |
| DNSServiceRefDeallocate(service_ref); |
| return; |
| } |
| instance->shared_txn = (intptr_t)server_state->shared_registration_txn; |
| adv_instance_retain(instance); // for the callback |
| } |
| } |
| |
| static void |
| service_publisher_record_callback(DNSServiceRef UNUSED sdref, DNSRecordRef rref, |
| DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code, void *context) |
| { |
| adv_record_t *record = context; |
| const char *host_name = record->host != NULL ? record->host->name : "<null>"; |
| if (error_code != kDNSServiceErr_NoError) { |
| ERROR("re-registration for " PRI_S_SRP " (record %p rref %p) failed with code %d", |
| host_name, record, rref, error_code); |
| record->rref = NULL; |
| record->shared_txn = 0; |
| adv_record_release(record); // no more callbacks. |
| } else { |
| INFO("re-registration for " PRI_S_SRP " (record %p rref %p) succeeded.", host_name, record, rref); |
| // could get more callbacks. |
| } |
| } |
| |
| static void |
| service_publisher_re_advertise_record(srp_server_t *server_state, adv_host_t *host, adv_record_t *record) |
| { |
| const DNSServiceRef service_ref = host->server_state->shared_registration_txn->sdref; |
| |
| // Make sure we don't double register. |
| if (record->rref != NULL) { |
| if (record->shared_txn == (intptr_t)server_state->shared_registration_txn) { |
| INFO("host is already registered: " PUB_S_SRP " (record %p rref %p)", |
| host->registered_name, record, record->rref); |
| return; |
| } |
| INFO("host registration is stale: " PUB_S_SRP " (record %p rref %p)", |
| host->registered_name, record, record->rref); |
| srp_mdns_shared_record_remove(host->server_state, record); |
| } |
| |
| // Get the TSR attribute for the host object. |
| char time_buf[TSR_TIMESTAMP_STRING_LEN]; |
| DNSServiceAttributeRef tsr_attribute = srp_message_tsr_attribute_generate(NULL, host->key_id, |
| time_buf, sizeof(time_buf)); |
| |
| int err = dns_service_register_record_wa(server_state, service_ref, &record->rref, kDNSServiceFlagsKnownUnique, |
| server_state->advertise_interface, host->registered_name, |
| record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL, |
| tsr_attribute, service_publisher_record_callback, record); |
| if (err != kDNSServiceErr_NoError) { |
| INFO("DNSServiceRegisterRecord failed on host " PUB_S_SRP ": %d (record %p)", host->name, err, record); |
| } else { |
| INFO("DNSServiceRegisterRecord succeeded on host " PUB_S_SRP " at " PUB_S_SRP " (record %p rref %p)", |
| host->name, time_buf, record, record->rref); |
| record->shared_txn = (intptr_t)host->server_state->shared_registration_txn; |
| adv_record_retain(record); // for the callback |
| } |
| } |
| |
| // Re-advertise records that match the address of the WED device we're bonded to if we're bonded to a WED device, or |
| // that match the mesh-local prefix of our mesh-local address. This is a best effort--if it fails, we log it, but it |
| // just means that our cached info isn't discoverable and we have to wait for a new registration, which should come soon |
| // or else the cached data wasn't valid. |
| void |
| service_publisher_re_advertise_matching(service_publisher_t *publisher) |
| { |
| if (publisher->state_header.state == service_publisher_state_invalid) { |
| INFO("publisher is in an invalid state, so we shouldn't re-advertise anything."); |
| return; |
| } |
| |
| srp_server_t *server_state = publisher->server_state; |
| |
| // If we don't yet have a shared connection, create one. |
| if (!srp_mdns_shared_registration_txn_setup(server_state)) { |
| return; |
| } |
| |
| for (adv_host_t *host = server_state->hosts; host; host = host->next) { |
| bool matched = false; |
| |
| if (host->addresses != NULL) { |
| for (int i = 0; i < host->addresses->num; i++) { |
| adv_record_t *record = host->addresses->vec[i]; |
| // A match means that if we are in WED p2p mode, the ML-EID of the WED matches the record. If not, then |
| // it means that the record is on the mesh-local prefix. In both cases, the record has to be a valid |
| // AAAA record, of course. |
| if (record != NULL && record->rrtype == dns_rrtype_aaaa && record->rdlen == 16 && |
| (publisher->wed_ml_eid_string != NULL |
| ? !in6addr_compare(&publisher->wed_ml_eid, (struct in6_addr *)record->rdata) |
| : !in6prefix_compare(&publisher->thread_mesh_local_address, (struct in6_addr *)record->rdata, 8))) |
| { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf); |
| INFO("re-advertising " PRI_S_SRP " IN AAAA " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| host->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf)); |
| service_publisher_re_advertise_record(server_state, host, record); |
| matched = true; |
| } |
| } |
| } |
| if (matched) { |
| if (host->key_record != NULL) { |
| service_publisher_re_advertise_record(server_state, host, host->key_record); |
| } |
| if (host->instances != NULL) { |
| for (int i = 0; i < host->instances->num; i++) { |
| adv_instance_t *instance = host->instances->vec[i]; |
| if (instance != NULL && instance->txn == NULL) { |
| service_publisher_re_advertise_instance(server_state, host, instance); |
| } |
| } |
| } |
| } |
| } |
| |
| publisher->cached_services_published = true; |
| } |
| |
| bool |
| service_publisher_is_address_mesh_local(service_publisher_t *publisher, addr_t *address) |
| { |
| if (address->sa.sa_family == AF_INET) { |
| IPv4_ADDR_GEN_SRP(&address->sin.sin_addr, addr_buf); |
| if (!IN_LOOPBACK(address->sin.sin_addr.s_addr)) { |
| INFO(PRI_IPv4_ADDR_SRP "is not mesh-local", IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, addr_buf)); |
| return false; |
| } |
| INFO(PRI_IPv4_ADDR_SRP "is the IPv4 loopback address", IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, addr_buf)); |
| return true; |
| } |
| if (address->sa.sa_family != AF_INET6) { |
| INFO("address family %d can't be mesh-local", address->sa.sa_family); |
| return false; |
| } |
| |
| uint8_t *addr_ptr = (uint8_t *)&address->sin6.sin6_addr; |
| SEGMENTED_IPv6_ADDR_GEN_SRP(addr_ptr, addr_buf); |
| if (IN6_IS_ADDR_LOOPBACK(&address->sin6.sin6_addr)) { |
| INFO(PRI_SEGMENTED_IPv6_ADDR_SRP " is the IPv6 loopback address.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf)); |
| return true; |
| } |
| static const uint8_t ipv4mapped_loopback[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1 }; |
| if (!memcmp(&address->sin6.sin6_addr, ipv4mapped_loopback, sizeof(ipv4mapped_loopback))) { |
| INFO(PRI_SEGMENTED_IPv6_ADDR_SRP " is the IPv4-mapped loopback address.", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf)); |
| return true; |
| } |
| |
| if (!publisher->have_ml_eid) { |
| INFO(PRI_SEGMENTED_IPv6_ADDR_SRP "is not mesh-local", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf)); |
| return false; |
| } |
| |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&publisher->thread_mesh_local_address, mle_buf); |
| if (in6prefix_compare(&address->sin6.sin6_addr, &publisher->thread_mesh_local_address, 8)) { |
| INFO(PRI_SEGMENTED_IPv6_ADDR_SRP |
| " is not on the same prefix as mesh-local address " PRI_SEGMENTED_IPv6_ADDR_SRP ".", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&publisher->thread_mesh_local_address, mle_buf)); |
| return false; |
| } |
| INFO(PRI_SEGMENTED_IPv6_ADDR_SRP "is on the same prefix as mesh-local address " PRI_SEGMENTED_IPv6_ADDR_SRP ".", |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf), |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&publisher->thread_mesh_local_address, mle_buf)); |
| return true; |
| } |
| |
| static void |
| service_publisher_finalize(service_publisher_t *publisher) |
| { |
| thread_service_release(publisher->published_unicast_service); |
| thread_service_release(publisher->published_anycast_service); |
| thread_service_list_release(&publisher->publication_queue); |
| |
| free(publisher->state_header.name); |
| free(publisher->wed_ext_address_string); |
| free(publisher->wed_ml_eid_string); |
| free(publisher->neighbor_ml_eid_string); |
| free(publisher->id); |
| ioloop_wakeup_release(publisher->wakeup_timer); |
| free(publisher); |
| } |
| |
| RELEASE_RETAIN_FUNCS(service_publisher); |
| |
| static void |
| service_publisher_context_release(void *context) |
| { |
| service_publisher_t *publisher = context; |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| |
| static void |
| service_publisher_update_callback(void *context, cti_status_t status) |
| { |
| service_publisher_t *publisher = context; |
| thread_service_t *service = publisher->publication_queue; |
| |
| if (publisher->canceled) { |
| RELEASE_HERE(publisher, service_publisher); |
| return; |
| } |
| |
| if (service == NULL) { |
| ERROR("no pending service update"); |
| return; |
| } |
| char status_buf[256]; |
| snprintf(status_buf, sizeof(status_buf), "is in state %s, status = %d", |
| thread_service_publication_state_name_get(service->publication_state), status); |
| thread_service_note(publisher->id, service, status_buf); |
| if (status == kCTIStatus_NoError) { |
| if (service->publication_state == add_pending) { |
| service->publication_state = add_complete; |
| } else if (service->publication_state == delete_pending) { |
| service->publication_state = delete_complete; |
| } |
| } else { |
| if (service->publication_state == add_pending) { |
| service->publication_state = add_failed; |
| } else if (service->publication_state == delete_pending) { |
| service->publication_state = delete_failed; |
| } |
| } |
| publisher->publication_queue = service->next; |
| thread_service_release(service); |
| if (!publisher->canceled) { |
| service_publisher_queue_run(publisher); |
| } |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| |
| static cti_status_t |
| service_publisher_service_update(service_publisher_t *publisher, thread_service_t *service, bool add) |
| { |
| uint8_t service_data[20]; |
| uint8_t server_data[20]; |
| size_t service_data_length, server_data_length; |
| switch(service->service_type) { |
| default: |
| return kCTIStatus_Invalid; |
| case unicast_service: |
| service_data[0] = THREAD_SRP_SERVER_OPTION; |
| service_data_length = 1; |
| memcpy(server_data, &service->u.unicast.address, 16); |
| memcpy(&server_data[16], service->u.unicast.port, 2); |
| server_data_length = 18; |
| break; |
| case anycast_service: |
| service_data[0] = THREAD_SRP_SERVER_ANYCAST_OPTION; |
| service_data[1] = service->u.anycast.sequence_number; |
| service_data_length = 2; |
| server_data_length = 0; |
| break; |
| case pref_id: |
| return kCTIStatus_BadParam; // Not supported anymore. |
| } |
| |
| if (add) { |
| return cti_add_service(publisher->server_state, publisher, service_publisher_update_callback, NULL, |
| THREAD_ENTERPRISE_NUMBER, service_data, service_data_length, server_data, server_data_length); |
| } else { |
| return cti_remove_service(publisher->server_state, publisher, service_publisher_update_callback, NULL, |
| THREAD_ENTERPRISE_NUMBER, service_data, service_data_length); |
| } |
| } |
| |
| static void |
| service_publisher_queue_run(service_publisher_t *publisher) |
| { |
| thread_service_t *service = publisher->publication_queue; |
| if (service == NULL) { |
| INFO("the queue is empty."); |
| return; |
| } |
| if (service->publication_state == delete_pending || service->publication_state == add_pending) { |
| INFO("there is a pending update at the head of the queue."); |
| return; |
| } |
| if (service->publication_state == want_delete) { |
| cti_status_t status = service_publisher_service_update(publisher, service, false); |
| if (status != kCTIStatus_NoError) { |
| ERROR("cti_remove_service failed: %d", status); |
| // For removes, we'll leave it on the queue |
| } else { |
| service->publication_state = delete_pending; |
| RETAIN_HERE(publisher, service_publisher); // for the callback |
| } |
| } else if (service->publication_state == want_add) { |
| cti_status_t status = service_publisher_service_update(publisher, service, true); |
| if (status != kCTIStatus_NoError) { |
| ERROR("cti_add_service failed: %d", status); |
| publisher->publication_queue = service->next; |
| thread_service_release(service); |
| } else { |
| service->publication_state = add_pending; |
| RETAIN_HERE(publisher, service_publisher); // for the callback |
| } |
| } else { |
| char status_buf[256]; |
| snprintf(status_buf, sizeof(status_buf), "is in unexpected state %s on the publication queue", |
| thread_service_publication_state_name_get(service->publication_state)); |
| thread_service_note(publisher->id, service, status_buf); |
| publisher->publication_queue = service->next; |
| thread_service_release(service); |
| } |
| } |
| |
| static void |
| service_publisher_queue_update(service_publisher_t *publisher, thread_service_t *service, |
| thread_service_publication_state_t initial_state) |
| { |
| thread_service_t **ppref; |
| |
| thread_service_t **p_published; |
| if (service->service_type == unicast_service) { |
| p_published = &publisher->published_unicast_service; |
| } else if (service->service_type == unicast_service) { |
| p_published = &publisher->published_unicast_service; |
| } else { |
| ERROR("unsupported service type %d", service->service_type); |
| return; |
| } |
| if (thread_service_equal(*p_published, service)) { |
| FAULT("published service still present: %p", *p_published); |
| } |
| service->publication_state = initial_state; |
| if (initial_state == want_add) { |
| *p_published = service; |
| thread_service_retain(*p_published); |
| } |
| // Find the end of the queue |
| for (ppref = &publisher->publication_queue; *ppref != NULL; ppref = &(*ppref)->next) |
| ; |
| *ppref = service; |
| // Retain the service on the queue. |
| thread_service_retain(*ppref); |
| service_publisher_queue_run(publisher); |
| } |
| |
| static thread_service_t * |
| service_publisher_create_service_for_queue(thread_service_t *service) |
| { |
| switch(service->service_type) { |
| case unicast_service: |
| return thread_service_unicast_create(service->rloc16, service->u.unicast.address.s6_addr, |
| service->u.unicast.port, service->service_id); |
| case anycast_service: |
| return thread_service_anycast_create(service->rloc16, service->u.anycast.sequence_number, service->service_id); |
| case pref_id: |
| return thread_service_pref_id_create(service->rloc16, service->u.pref_id.partition_id, |
| service->u.pref_id.prefix, service->service_id); |
| default: |
| return NULL; |
| } |
| } |
| |
| static void UNUSED |
| service_publisher_service_publish(service_publisher_t *publisher, thread_service_t *service) |
| { |
| service_publisher_queue_update(publisher, service, want_add); |
| } |
| |
| static void UNUSED |
| service_publisher_service_unpublish(service_publisher_t *publisher, thread_service_type_t service_type, bool enqueue) |
| { |
| thread_service_t *service; |
| |
| if (service_type == unicast_service) { |
| service = publisher->published_unicast_service; |
| publisher->published_unicast_service = NULL; |
| } else if (service_type == anycast_service) { |
| service = publisher->published_anycast_service; |
| publisher->published_anycast_service = NULL; |
| } else { |
| ERROR("unsupported service type %d", service_type); |
| return; |
| } |
| if (service == NULL) { |
| ERROR("request to unpublished service that's not present"); |
| return; |
| } |
| |
| if (enqueue) { |
| thread_service_t *to_delete = service_publisher_create_service_for_queue(service); |
| service_publisher_queue_update(publisher, to_delete, want_delete); |
| thread_service_release(to_delete); // service_publisher_queue_update explicitly retains the references it makes. |
| thread_service_release(service); // No longer published. |
| } |
| } |
| |
| static void |
| service_publisher_unpublish_stale_service(service_publisher_t *publisher, thread_service_t *service) |
| { |
| // If there's a stale service, don't try to publish the real service until we see the stale service go away. |
| INFO("setting seen_service_list to false"); |
| publisher->seen_service_list = false; |
| |
| thread_service_t *to_delete = service_publisher_create_service_for_queue(service); |
| |
| if (to_delete == NULL) { |
| thread_service_note(publisher->id, service, "no memory for service to delete"); |
| } else { |
| service_publisher_queue_update(publisher, to_delete, want_delete); |
| thread_service_release(to_delete); // service_publisher_queue_update explicitly retains all the references it makes |
| service->ignore = true; |
| } |
| } |
| |
| static void |
| service_publisher_wait_expired(void *context) |
| { |
| service_publisher_t *publisher = context; |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_timeout, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| static void |
| service_publisher_start_wait(service_publisher_t *publisher, int32_t milliseconds) |
| { |
| ioloop_add_wake_event(publisher->wakeup_timer, publisher, service_publisher_wait_expired, |
| service_publisher_context_release, milliseconds); |
| RETAIN_HERE(publisher, service_publisher); // For wakeup |
| } |
| |
| static bool |
| service_publisher_have_competing_unicast_service(service_publisher_t *publisher, bool want_stale_service_timeout) |
| { |
| thread_service_t *NULLABLE published_service = publisher->published_unicast_service; |
| bool competing_service_present = false; |
| |
| for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker); |
| service != NULL; service = service->next) |
| { |
| if (service->ignore) { |
| continue; |
| } |
| if (service->service_type == unicast_service) { |
| if (published_service == NULL) { |
| if (service->rloc16 == publisher->server_state->rloc16 || |
| (publisher->have_ml_eid && |
| !in6addr_compare(&service->u.unicast.address, &publisher->thread_mesh_local_address))) |
| { |
| thread_service_note(publisher->id, service, |
| "is on our ml-eid or rloc16 but we aren't publishing it, so it's stale."); |
| service_publisher_unpublish_stale_service(publisher, service); |
| |
| if (want_stale_service_timeout && |
| !publisher->cached_services_published && !publisher->started_stale_service_timeout) |
| { |
| INFO("starting wakeup timer to publish cached services after stale service timeout."); |
| publisher->started_stale_service_timeout = true; |
| service_publisher_start_wait(publisher, 2000); |
| } |
| continue; |
| } |
| thread_service_note(publisher->id, service, "is not ours and we aren't publishing."); |
| competing_service_present = true; |
| // First check to see if the other service is on the mesh-local prefix. If it's not, it wins. |
| } else if (in6prefix_compare(&service->u.unicast.address, &published_service->u.unicast.address, 8)) { |
| competing_service_present = true; |
| thread_service_note(publisher->id, service, "is a competing service."); |
| } else { |
| int cmp = in6addr_compare(&service->u.unicast.address, &published_service->u.unicast.address); |
| |
| // If equal, compare ports... |
| if (!cmp) { |
| cmp = memcmp(service->u.unicast.port, published_service->u.unicast.port, |
| sizeof(service->u.unicast.port)); |
| // If the port doesn't match our published service, it's a weird stale service (this probably can't |
| // happen); |
| if (cmp) { |
| thread_service_note(publisher->id, service, |
| "is on our ml-eid but is not the one we are publishing, so it's stale."); |
| service_publisher_unpublish_stale_service(publisher, service); |
| continue; |
| } else { |
| thread_service_note(publisher->id, service, "is the one we are publishing."); |
| } |
| } else { |
| // This is a stale service published on our RLOC16 with a different ML-EID. |
| if (service->rloc16 == publisher->server_state->rloc16) { |
| thread_service_note(publisher->id, service, |
| "is a stale service published on our rloc16 with a different ml-eid."); |
| service_publisher_unpublish_stale_service(publisher, service); |
| continue; |
| } else if (cmp < 0) { |
| competing_service_present = true; |
| thread_service_note(publisher->id, service, "is not ours and wins against ours."); |
| } else { |
| thread_service_note(publisher->id, service, "is not ours and loses against ours."); |
| } |
| } |
| } |
| } |
| } |
| return competing_service_present; |
| } |
| |
| static bool |
| service_publisher_have_anycast_service(service_publisher_t *publisher) |
| { |
| bool anycast_service_present = false; |
| |
| for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker); |
| service != NULL; service = service->next) |
| { |
| if (service->ignore) { |
| continue; |
| } |
| if (service->service_type == anycast_service) { |
| thread_service_note(publisher->id, service, "is present and supersedes our unicast service"); |
| anycast_service_present = true; |
| } |
| } |
| return anycast_service_present; |
| } |
| |
| void |
| service_publisher_wanted_service_added(service_publisher_t *publisher) |
| { |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_srp_needed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| static void |
| service_publisher_sed_timeout_expired(void *context) |
| { |
| service_publisher_t *publisher = context; |
| |
| if (publisher->sed_timeout != NULL) { |
| ioloop_wakeup_release(publisher->sed_timeout); |
| publisher->sed_timeout = NULL; |
| } |
| if (publisher->neighbor_ml_eid_string == NULL) { |
| publisher->neighbor_ml_eid_string = strdup("none"); |
| memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid)); |
| } |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_neighbor_ml_eid_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| } else { |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| } |
| |
| static void |
| service_publisher_service_tracker_callback(void *context) |
| { |
| service_publisher_t *publisher = context; |
| |
| // If we get a service list update when we're associated, set the seen_service-list flag to true. This tells us we can proceed |
| // with publishing a service if we just associated to the thread network. |
| if (thread_tracker_associated_get(publisher->server_state->thread_tracker, false)) { |
| INFO("setting seen_service_list to true"); |
| publisher->seen_service_list = true; |
| } |
| |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_service_list_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| static void |
| service_publisher_thread_tracker_callback(void *context) |
| { |
| service_publisher_t *publisher = context; |
| |
| // This is a temporary hack because right now we won't get a prefix list event on associate, and we need |
| // the (presumably empty) prefix list event to trigger an immediate service advertisement. |
| // IMPORTANT: when this is fixed, make sure to start the service_tracker in thread-device.c again. |
| // To work around this issue, we stop service list events in the service tracker when we dissociate, and |
| // restart events when we re-associate. |
| // Hack is tracked in rdar://109266343 (Optimize SRP registration time) |
| if (!thread_tracker_associated_get(publisher->server_state->thread_tracker, false)) { |
| service_tracker_stop(publisher->server_state->service_tracker); |
| } else { |
| service_tracker_start(publisher->server_state->service_tracker); |
| } |
| |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_thread_network_state_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| static void |
| service_publisher_node_type_tracker_callback(void *context) |
| { |
| service_publisher_t *publisher = context; |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_thread_node_type_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| // Service publisher states |
| // |
| // <startup> |
| // on entry: start timer for a random amount of time |
| // timer event: -> <waiting to publish> |
| // other events: ignore |
| // <waiting to publish> |
| // on entry: check for service present |
| // yes: -> <not publishing> |
| // check for ML-EID present |
| // yes: -> <start listeners> |
| // ML-EID shows up: -> <start listeners> |
| // service shows up: -> <not publishing> |
| // <not publishing> |
| // on entry: do nothing |
| // last service advertisement goes away: -> <waiting to publish> |
| // <start listeners> |
| // on entry: start listeners |
| // listener ready: -> <publishing> |
| // <publishing> |
| // on entry: publish the service |
| // start wait timer |
| // on timery expiry: publish the service again |
| // our service shows up: stop the timer |
| // winning service shows up: stop advertising |
| // -> <not publishing> |
| |
| static state_machine_state_t service_publisher_action_startup(state_machine_header_t *state_header, |
| state_machine_event_t *event); |
| static state_machine_state_t service_publisher_action_waiting_to_publish(state_machine_header_t *state_header, |
| state_machine_event_t *event); |
| static state_machine_state_t service_publisher_action_not_publishing(state_machine_header_t *state_header, |
| state_machine_event_t *event); |
| static state_machine_state_t service_publisher_action_start_listeners(state_machine_header_t *state_header, |
| state_machine_event_t *event); |
| static state_machine_state_t service_publisher_action_publishing(state_machine_header_t *state_header, |
| state_machine_event_t *event); |
| |
| #define SERVICE_PUB_NAME_DECL(name) service_publisher_state_##name, #name |
| static state_machine_decl_t service_publisher_states[] = { |
| { SERVICE_PUB_NAME_DECL(invalid), NULL }, |
| { SERVICE_PUB_NAME_DECL(startup), service_publisher_action_startup }, |
| { SERVICE_PUB_NAME_DECL(waiting_to_publish), service_publisher_action_waiting_to_publish }, |
| { SERVICE_PUB_NAME_DECL(not_publishing), service_publisher_action_not_publishing }, |
| { SERVICE_PUB_NAME_DECL(start_listeners), service_publisher_action_start_listeners }, |
| { SERVICE_PUB_NAME_DECL(publishing), service_publisher_action_publishing }, |
| }; |
| #define SERVICE_PUBLISHER_NUM_STATES ((sizeof(service_publisher_states)) / (sizeof(state_machine_decl_t))) |
| |
| #define STATE_MACHINE_HEADER_TO_PUBLISHER(state_header) \ |
| if (state_header->state_machine_type != state_machine_type_service_publisher) { \ |
| ERROR("state header type isn't service_publisher: %d", state_header->state_machine_type); \ |
| return service_publisher_state_invalid; \ |
| } \ |
| service_publisher_t *publisher = state_header->state_object |
| |
| // In the startup state, we wait for a timeout to expire before doing anything. This gives other devices on the Thread mesh |
| // an opportunity to publish as well. |
| static state_machine_state_t |
| service_publisher_action_startup(state_machine_header_t *state_header, state_machine_event_t *event) |
| { |
| STATE_MACHINE_HEADER_TO_PUBLISHER(state_header); |
| BR_STATE_ANNOUNCE(publisher, event); |
| |
| if (event == NULL) { |
| // We only need a random startup delay for stub routers, which are generally powered devices that can synchronize |
| // on restart after a power failure. |
| #if STUB_ROUTER |
| if (publisher->server_state->stub_router_enabled) { |
| service_publisher_start_wait(publisher, publisher->startup_delay_range + srp_random16() % publisher->startup_delay_range); |
| RETAIN_HERE(publisher, service_publisher); // For wakeup |
| return service_publisher_state_invalid; |
| } |
| #endif |
| return service_publisher_state_waiting_to_publish; |
| } |
| |
| // The only way out of the startup state is for the timer to expire--we don't care about prefixes showing up or |
| // going away. |
| if (event->type == state_machine_event_type_timeout) { |
| INFO("startup timeout"); |
| return service_publisher_state_waiting_to_publish; |
| } |
| |
| BR_UNEXPECTED_EVENT(publisher, event); |
| } |
| |
| static bool |
| service_publisher_can_publish(service_publisher_t *publisher) |
| { |
| bool no_anycast_service = true; |
| bool no_competing_service = true; |
| bool associated = true; |
| bool router = false; |
| bool have_ml_eid = true; |
| bool have_thread_interface_name = true; |
| bool can_publish = true; |
| bool sleepy_router = false; |
| bool sleepy_end_device = false; |
| bool have_node_type = false; |
| bool have_wed_ml_eid = true; |
| bool have_neighbor_ml_eid = true; |
| |
| // Check the conditions that prevent publication. |
| if (service_publisher_have_competing_unicast_service(publisher, false)) { |
| no_competing_service = false; |
| can_publish = false; |
| } |
| if (service_publisher_have_anycast_service(publisher)) { |
| no_anycast_service = false; |
| can_publish = false; |
| } |
| srp_server_t *server_state = publisher->server_state; |
| if (!thread_tracker_associated_get(server_state->thread_tracker, false)) { |
| associated = false; |
| can_publish = false; |
| INFO("setting seen_service_list to false"); |
| publisher->seen_service_list = false; |
| } |
| |
| thread_node_type_t node_type = node_type_tracker_thread_node_type_get(server_state->node_type_tracker, false); |
| switch(node_type) { |
| case node_type_router: |
| case node_type_leader: |
| router = true; |
| have_node_type = true; |
| break; |
| case node_type_sleepy_router: |
| sleepy_router = true; |
| have_node_type = true; |
| break; |
| case node_type_unknown: |
| have_node_type = false; |
| can_publish = false; |
| break; |
| case node_type_sleepy_end_device: |
| case node_type_synchronized_sleepy_end_device: |
| sleepy_end_device = true; |
| have_node_type = true; |
| break; |
| default: |
| have_node_type = true; |
| break; |
| } |
| |
| if (!publisher->have_ml_eid) { |
| have_ml_eid = false; |
| can_publish = false; |
| } |
| if (!publisher->have_thread_interface_name) { |
| have_thread_interface_name = false; |
| can_publish = false; |
| } |
| if (!publisher->seen_service_list) { |
| can_publish = false; |
| } |
| if (publisher->stopped) { |
| can_publish = false; |
| } |
| if (publisher->wed_ml_eid_string == NULL) { |
| have_wed_ml_eid = false; |
| if (sleepy_router) { |
| can_publish = false; |
| } |
| } |
| if (publisher->neighbor_ml_eid_string == NULL) { |
| have_neighbor_ml_eid = false; |
| if (sleepy_end_device) { |
| if (publisher->sed_timeout == NULL) { |
| publisher->sed_timeout = ioloop_wakeup_create(); |
| if (publisher->sed_timeout != NULL) { |
| ioloop_add_wake_event(publisher->sed_timeout, publisher, service_publisher_sed_timeout_expired, |
| service_publisher_context_release, 500); |
| RETAIN_HERE(publisher, service_publisher); |
| } |
| } |
| can_publish = false; |
| } |
| } |
| |
| INFO(PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP, |
| can_publish ? "can publish" : "can't publish", |
| publisher->seen_service_list ? "" : " have not seen service list", |
| no_competing_service ? "" : " competing service present", |
| no_anycast_service ? "" : " anycast service present", |
| associated ? "" : " not associated ", |
| router ? "" : " not a router ", |
| sleepy_router ? "" : " not a sleepy router", |
| sleepy_end_device ? "" : " not a sleepy end device", |
| have_node_type ? "" : " don't have node type", |
| have_ml_eid ? "" : " no ml-eid ", |
| have_wed_ml_eid ? "" : " no wed ml-eid ", |
| have_neighbor_ml_eid ? "" : " no neighbor ml-eid ", |
| have_thread_interface_name ? "" : " no thread interface name ", |
| publisher->stopped ? " stopped" : ""); |
| return can_publish; |
| } |
| |
| // This function tells the caller whether the service publisher could publish a service. This will be the case either |
| // because it actually is publishing a service, or because it's in the process of finding out if it can publish a |
| // service, or preparing to publish a service. In practice, this means that the only state for which the answer is false |
| // at present is the "not_publishing" state. |
| bool |
| service_publisher_could_publish(service_publisher_t *publisher) |
| { |
| if (publisher == NULL) { |
| return false; |
| } |
| if (publisher->state_header.state == service_publisher_state_startup || |
| publisher->state_header.state == service_publisher_state_waiting_to_publish || |
| publisher->state_header.state == service_publisher_state_start_listeners || |
| publisher->state_header.state == service_publisher_state_publishing) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| service_publisher_stop_publishing(service_publisher_t *publisher) |
| { |
| publisher->stopped = true; |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_stop, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| |
| // We go to this state whenever we think we might need to publish, but are not yet publishing. So this state |
| // acts as a gatekeeper: if there is already a service published, we go straight to not_publishing. If we don't |
| // yet have an ML-EID, we have to wait until we get one to publish. If, while we are waiting for the ML-EID, |
| // we see a service show up, we go to not_publishing. Otherwise, when the ML-EID comes, we go to the listener_start |
| // page. |
| static state_machine_state_t |
| service_publisher_action_waiting_to_publish(state_machine_header_t *state_header, state_machine_event_t *event) |
| { |
| STATE_MACHINE_HEADER_TO_PUBLISHER(state_header); |
| BR_STATE_ANNOUNCE(publisher, event); |
| |
| // We do the same thing here whether we've gotten an event or just on entry, so no need to check. |
| if (service_publisher_can_publish(publisher)) { |
| ioloop_cancel_wake_event(publisher->wakeup_timer); |
| publisher->started_stale_service_timeout = false; |
| return service_publisher_state_start_listeners; |
| } |
| if (service_publisher_have_competing_unicast_service(publisher, true)) { |
| ioloop_cancel_wake_event(publisher->wakeup_timer); |
| publisher->started_stale_service_timeout = false; |
| return service_publisher_state_not_publishing; |
| } |
| // If we saw a stale service, we'll get a timeout event here. |
| if (event != NULL && event->type == state_machine_event_type_timeout && !publisher->cached_services_published) { |
| service_publisher_re_advertise_matching(publisher); |
| } |
| return service_publisher_state_invalid; |
| } |
| |
| // We get into this state when there is a competing service that wins the election against our service. |
| // We only leave the state when there is no longer a competing prefix. |
| static state_machine_state_t |
| service_publisher_action_not_publishing(state_machine_header_t *state_header, state_machine_event_t *event) |
| { |
| STATE_MACHINE_HEADER_TO_PUBLISHER(state_header); |
| BR_STATE_ANNOUNCE(publisher, event); |
| |
| if (event == NULL) { |
| if (publisher->published_unicast_service != NULL) { |
| service_publisher_service_unpublish(publisher, unicast_service, true); |
| } |
| return service_publisher_state_invalid; |
| } |
| |
| // We do the same thing here for any event we get, because we're just waiting for conditions to be right. |
| if (service_publisher_can_publish(publisher)) { |
| publisher->startup_delay_range = SERVICE_PUBLISHER_LOST_WAIT; |
| return service_publisher_state_startup; |
| } |
| return service_publisher_state_invalid; |
| } |
| |
| static void |
| service_publisher_listener_cancel_callback(comm_t *UNUSED listener, void *context) |
| { |
| srp_server_t *server_state = context; |
| service_publisher_t *publisher = server_state->service_publisher; |
| if (publisher != NULL) { |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_canceled, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| } |
| |
| static void |
| service_publisher_listener_cancel(service_publisher_t *publisher) |
| { |
| if (publisher->srp_listener != NULL) { |
| ioloop_listener_cancel(publisher->srp_listener); |
| ioloop_comm_release(publisher->srp_listener); |
| publisher->srp_listener = NULL; |
| } |
| publisher->have_srp_listener = false; |
| service_publisher_unadvertise_all(publisher); |
| } |
| |
| static void |
| service_publisher_listener_ready(void *context, uint16_t port) |
| { |
| srp_server_t *server_state = context; |
| service_publisher_t *publisher = server_state->service_publisher; |
| if (publisher != NULL) { |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_ready, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| publisher->have_srp_listener = true; |
| publisher->srp_listener_port = port; |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| } |
| } |
| |
| static void |
| service_publisher_listener_start(service_publisher_t *publisher) |
| { |
| if (publisher->srp_listener) { |
| FAULT("listener still present"); |
| service_publisher_listener_cancel(publisher); |
| } |
| publisher->srp_listener = srp_proxy_listen(NULL, 0, publisher->thread_interface_name, service_publisher_listener_ready, |
| service_publisher_listener_cancel_callback, NULL, |
| NULL, publisher->server_state); |
| if (publisher->srp_listener == NULL) { |
| ERROR("failed to setup SRP listener"); |
| } |
| service_publisher_re_advertise_matching(publisher); |
| } |
| |
| // We go to this state when we have decided to publish, but perhaps do not currently have an SRP listener |
| // running. |
| static state_machine_state_t |
| service_publisher_action_start_listeners(state_machine_header_t *state_header, state_machine_event_t *event) |
| { |
| STATE_MACHINE_HEADER_TO_PUBLISHER(state_header); |
| BR_STATE_ANNOUNCE(publisher, event); |
| |
| if (event == NULL) { |
| if (publisher->have_srp_listener) { |
| if (publisher->srp_listener != NULL) { |
| return service_publisher_state_publishing; |
| } |
| FAULT("have_srp_listener is true but there's no listener!"); |
| publisher->have_srp_listener = false; |
| } |
| service_publisher_listener_start(publisher); |
| return service_publisher_state_invalid; |
| } |
| |
| // If we get a competing service while we're waiting for the listener to start, cancel the listener. |
| // We do the same thing here for any event we get, because we're just waiting for conditions to be right. |
| if (!service_publisher_can_publish(publisher)) { |
| service_publisher_listener_cancel(publisher); |
| return service_publisher_state_not_publishing; |
| } |
| |
| // If the listener is ready, we can publish. |
| if (event->type == state_machine_event_type_listener_ready) { |
| return service_publisher_state_publishing; |
| } |
| |
| return service_publisher_state_invalid; |
| } |
| |
| static state_machine_state_t |
| service_publisher_published_services_seen(service_publisher_t *publisher) |
| { |
| return ((publisher->published_unicast_service == NULL || publisher->have_unicast_in_net_data) && |
| (publisher->published_anycast_service == NULL || publisher->have_anycast_in_net_data)); |
| } |
| |
| static bool |
| service_publisher_wanted_service_missing(service_publisher_t *publisher) |
| { |
| srp_server_t *server_state = publisher->server_state; |
| |
| // If we get here, the named service instance is not represented in the current set of services |
| // about which we have information. |
| #if STUB_ROUTER |
| if (server_state->stub_router_enabled) { |
| return true; // always publish when stub router |
| } |
| #endif |
| if (server_state->srp_service_needed) { |
| INFO("srp_service_needed == true -> true"); |
| return true; // srp service unconditionally requested |
| } |
| |
| for (wanted_service_t *service = server_state->wanted_services; service != NULL; service = service->next) { |
| for (adv_host_t *host = server_state->hosts; host != NULL; host = host->next) { |
| if (host->instances != NULL) { |
| for (int i = 0; i < host->instances->num; i++) { |
| adv_instance_t *instance = host->instances->vec[i]; |
| if (instance != NULL) { |
| if (!strcasecmp(instance->instance_name, service->name) && |
| !adv_ctl_service_types_compare(service->service_type, instance->service_type)) |
| { |
| if (host->addresses != NULL) { |
| for (int j = 0; j < host->addresses->num; j++) { |
| adv_record_t *address = host->addresses->vec[j]; |
| if (address != NULL) { |
| if (address->rdlen == 16 && |
| !in6prefix_compare((struct in6_addr *)address->rdata, |
| &publisher->thread_mesh_local_address, 8)) |
| { |
| goto instance_found; |
| } |
| } |
| } |
| INFO("srp service " PRI_S_SRP "." PRI_S_SRP " present as " PRI_S_SRP |
| " but has no address on local mesh -> true", |
| service->name, service->service_type, instance->instance_name); |
| return true; // There is no valid address for this instance in the cache. |
| } |
| INFO("srp service " PRI_S_SRP "." PRI_S_SRP " present but no addresses -> true", |
| service->name, service->service_type); |
| return true; // We didn't find the named instance in the database |
| } |
| } |
| } |
| } |
| } |
| INFO("service " PRI_S_SRP "." PRI_S_SRP " host not present -> true", service->name, service->service_type); |
| return true; |
| instance_found: |
| INFO("service " PRI_S_SRP "." PRI_S_SRP " is present", service->name, service->service_type); |
| } |
| INFO("all needed services present -> false"); |
| return false; // There weren't any named instances for which we don't have a usable registration. |
| } |
| |
| // We enter this state when we have an SRP listener and no competing unicast services. On entry, we publish our unicast service. |
| // If a competing service shows up that wins, we stop publishing and cancel the listener. Otherwise we remain in this state. |
| static state_machine_state_t |
| service_publisher_action_publishing(state_machine_header_t *state_header, state_machine_event_t *event) |
| { |
| STATE_MACHINE_HEADER_TO_PUBLISHER(state_header); |
| BR_STATE_ANNOUNCE(publisher, event); |
| |
| if (event == NULL || event->type == state_machine_event_type_timeout || |
| event->type == state_machine_event_type_srp_needed) |
| { |
| if (publisher->published_unicast_service != NULL) { |
| // We shouldn't see a published service on state entry. |
| if (event == NULL) { |
| ERROR("unicast service still published!"); |
| } |
| // Only actually enqueue a delete if we aren't retrying. |
| service_publisher_service_unpublish(publisher, unicast_service, event == NULL); |
| } |
| |
| // On non-BR devices, don't actually publish the service until we get a signal that it's needed. |
| if (!publisher->server_state->srp_on_demand || service_publisher_wanted_service_missing(publisher)) { |
| uint8_t port[] = { publisher->srp_listener_port >> 8, publisher->srp_listener_port & 255 }; |
| thread_service_t *service = thread_service_unicast_create(publisher->server_state->rloc16, |
| (uint8_t *)&publisher->thread_mesh_local_address, |
| port, 0); |
| service_publisher_service_publish(publisher, service); |
| thread_service_release(service); // service_publisher_publish retains the references it keeps. |
| |
| // Set up a retransmit timer in case the service publication fails. |
| if (event == NULL) { |
| publisher->retry_interval = 5; // First retry after five seconds |
| } else { |
| // Maybe the service tracker is wedged, so restart it |
| service_tracker_start(publisher->server_state->service_tracker); |
| |
| // Exponential backoff. |
| if (publisher->retry_interval < 3600) { |
| publisher->retry_interval *= 2; |
| } |
| } |
| service_publisher_start_wait(publisher, (publisher->retry_interval * MSEC_PER_SEC + |
| srp_random32() % (publisher->retry_interval * MSEC_PER_SEC) / 2)); |
| } |
| |
| return service_publisher_state_invalid; |
| } |
| |
| // If the listener got canceled for some reason, restart it. |
| if (event->type == state_machine_event_type_listener_canceled) { |
| service_publisher_service_unpublish(publisher, unicast_service, true); |
| publisher->startup_delay_range = SERVICE_PUBLISHER_LISTENER_RESTART_WAIT; |
| return service_publisher_state_startup; |
| } |
| |
| // Any other event triggers a re-evaluation. |
| if (event->type == state_machine_event_type_ml_eid_changed) { |
| service_publisher_listener_cancel(publisher); |
| service_publisher_service_unpublish(publisher, unicast_service, true); |
| return service_publisher_state_startup; |
| } |
| |
| if (!service_publisher_can_publish(publisher)) { |
| service_publisher_listener_cancel(publisher); |
| service_publisher_service_unpublish(publisher, unicast_service, true); |
| return service_publisher_state_not_publishing; |
| } |
| |
| // If we haven't yet seen all the services we are publishing in the network data, check to see if it showed up. |
| if (event->type == state_machine_event_type_service_list_changed && |
| !service_publisher_published_services_seen(publisher)) |
| { |
| publisher->have_unicast_in_net_data = false; |
| publisher->have_anycast_in_net_data = false; |
| for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker); |
| service != NULL; service = service->next) |
| { |
| if (service->service_type == unicast_service && service->ncp && |
| publisher->published_unicast_service != NULL && |
| !in6addr_compare(&service->u.unicast.address, &publisher->published_unicast_service->u.unicast.address) && |
| !memcmp(service->u.unicast.port, publisher->published_unicast_service->u.unicast.port, 2)) |
| { |
| publisher->have_unicast_in_net_data = true; |
| } |
| else if (service->service_type == anycast_service && service->ncp && |
| publisher->server_state->have_rloc16 && service->rloc16 == publisher->server_state->rloc16 && |
| publisher->published_anycast_service != NULL && |
| service->u.anycast.sequence_number == publisher->published_anycast_service->u.anycast.sequence_number) |
| { |
| publisher->have_anycast_in_net_data = true; |
| } |
| } |
| |
| // If all of the services we are publishing are showing up in NCP, we can cancel the timer and go to the publishing state. |
| if (service_publisher_published_services_seen(publisher)) { |
| ioloop_cancel_wake_event(publisher->wakeup_timer); |
| return service_publisher_state_invalid; |
| } |
| } |
| return service_publisher_state_invalid; |
| } |
| |
| void |
| service_publisher_cancel(service_publisher_t *publisher) |
| { |
| ioloop_cancel_wake_event(publisher->wakeup_timer); |
| service_publisher_listener_cancel(publisher); |
| service_tracker_callback_cancel(publisher->server_state->service_tracker, publisher); |
| thread_tracker_callback_cancel(publisher->server_state->thread_tracker, publisher); |
| node_type_tracker_callback_cancel(publisher->server_state->node_type_tracker, publisher); |
| if (publisher->active_data_set_connection != NULL) { |
| cti_events_discontinue(publisher->active_data_set_connection); |
| publisher->active_data_set_connection = NULL; |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| if (publisher->wed_tracker_connection != NULL) { |
| cti_events_discontinue(publisher->wed_tracker_connection); |
| publisher->wed_tracker_connection = NULL; |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| if (publisher->neighbor_tracker_connection != NULL) { |
| cti_events_discontinue(publisher->neighbor_tracker_connection); |
| publisher->neighbor_tracker_connection = NULL; |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| state_machine_cancel(&publisher->state_header); |
| } |
| |
| service_publisher_t * |
| service_publisher_create(srp_server_t *server_state) |
| { |
| service_publisher_t *ret = NULL, *publisher = calloc(1, sizeof(*publisher)); |
| if (publisher == NULL) { |
| return publisher; |
| } |
| RETAIN_HERE(publisher, service_publisher); |
| publisher->wakeup_timer = ioloop_wakeup_create(); |
| if (publisher->wakeup_timer == NULL) { |
| ERROR("wakeup timer alloc failed"); |
| goto out; |
| } |
| |
| char server_id_buf[100]; |
| snprintf(server_id_buf, sizeof(server_id_buf), "[SP%lld]", ++service_publisher_serial_number); |
| publisher->id = strdup(server_id_buf); |
| if (publisher->id == NULL) { |
| ERROR("no memory for server ID"); |
| goto out; |
| } |
| |
| if (!state_machine_header_setup(&publisher->state_header, |
| publisher, publisher->id, |
| state_machine_type_service_publisher, |
| service_publisher_states, |
| SERVICE_PUBLISHER_NUM_STATES)) { |
| ERROR("header setup failed"); |
| goto out; |
| } |
| |
| publisher->server_state = server_state; |
| if (!service_tracker_callback_add(server_state->service_tracker, service_publisher_service_tracker_callback, |
| service_publisher_context_release, publisher)) |
| { |
| goto out; |
| } |
| RETAIN_HERE(publisher, service_publisher); // for service tracker |
| |
| if (!thread_tracker_callback_add(server_state->thread_tracker, service_publisher_thread_tracker_callback, |
| service_publisher_context_release, publisher)) |
| { |
| goto out; |
| } |
| RETAIN_HERE(publisher, service_publisher); // for thread network state tracker |
| |
| if (!node_type_tracker_callback_add(server_state->node_type_tracker, service_publisher_node_type_tracker_callback, |
| service_publisher_context_release, publisher)) |
| { |
| goto out; |
| } |
| RETAIN_HERE(publisher, service_publisher); // for thread network state tracker |
| |
| // Set the first_time flag so that we'll know to remove any locally-published on-mesh prefixes. |
| publisher->first_time = true; |
| publisher->startup_delay_range = SERVICE_PUBLISHER_START_WAIT; |
| ret = publisher; |
| publisher = NULL; |
| out: |
| if (publisher != NULL) { |
| RELEASE_HERE(publisher, service_publisher); |
| } |
| return ret; |
| } |
| |
| static void |
| service_publisher_get_mesh_local_address_callback(void *context, const char *address_string, cti_status_t status) |
| { |
| service_publisher_t *publisher = context; |
| |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| if (publisher->reconnect_callback != NULL) { |
| publisher->reconnect_callback(publisher->server_state); |
| } |
| goto fail; |
| } |
| |
| INFO(PUB_S_SRP " %d", address_string != NULL ? address_string : "<null>", status); |
| if (status != kCTIStatus_NoError || address_string == NULL) { |
| goto fail; |
| } |
| |
| struct in6_addr new_mesh_local_address; |
| if (!inet_pton(AF_INET6, address_string, &new_mesh_local_address)) { |
| ERROR("address syntax incorrect: " PRI_S_SRP, address_string); |
| goto fail; |
| } |
| |
| if (publisher->have_ml_eid && !in6addr_compare(&new_mesh_local_address, &publisher->thread_mesh_local_address)) { |
| INFO("address didn't change"); |
| return; |
| } |
| publisher->thread_mesh_local_address = new_mesh_local_address; |
| publisher->have_ml_eid = true; |
| |
| for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker); |
| service != NULL; service = service->next) |
| { |
| if (service->ignore) { |
| continue; |
| } |
| if (service->service_type == unicast_service) { |
| if (publisher->published_unicast_service == NULL) { |
| if (service->rloc16 == publisher->server_state->rloc16 || |
| (publisher->have_ml_eid && |
| !in6addr_compare(&service->u.unicast.address, &publisher->thread_mesh_local_address))) |
| { |
| thread_service_note(publisher->id, service, |
| "is on our ml-eid or rloc16 but we aren't publishing it, so it's stale."); |
| service_publisher_unpublish_stale_service(publisher, service); |
| continue; |
| } |
| } |
| } |
| } |
| |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_ml_eid_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| return; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| RELEASE_HERE(publisher, service_publisher); // callback held a reference. |
| return; |
| fail: |
| RELEASE_HERE(publisher, service_publisher); // callback held a reference. |
| publisher->have_ml_eid = false; |
| return; |
| } |
| |
| static void |
| service_publisher_active_data_set_changed_callback(void *context, cti_status_t status) |
| { |
| service_publisher_t *publisher = context; |
| |
| if (status != kCTIStatus_NoError) { |
| ERROR("error %d", status); |
| RELEASE_HERE(publisher, service_publisher); // no more callbacks |
| cti_events_discontinue(publisher->active_data_set_connection); |
| publisher->active_data_set_connection = NULL; |
| return; |
| } |
| |
| status = cti_get_mesh_local_address(publisher->server_state, publisher, |
| service_publisher_get_mesh_local_address_callback, NULL); |
| if (status != kCTIStatus_NoError) { |
| ERROR("cti_get_mesh_local_address failed with status %d", status); |
| } else { |
| RETAIN_HERE(publisher, service_publisher); // for mesh-local callback |
| } |
| } |
| |
| static void |
| service_publisher_tunnel_name_callback(void *context, const char *name, cti_status_t status) |
| { |
| service_publisher_t *publisher = context; |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| goto out; |
| } |
| |
| if (status != kCTIStatus_NoError) { |
| INFO(PUB_S_SRP " %d", name != NULL ? name : "<null>", status); |
| goto out; |
| } |
| publisher->have_thread_interface_name = true; |
| |
| // Get rid of the old interface name if it's changed. |
| bool changed = true; |
| if (publisher->thread_interface_name != NULL) { |
| if (!strcmp(name, publisher->thread_interface_name)) { |
| changed = false; |
| } else { |
| free(publisher->thread_interface_name); |
| publisher->thread_interface_name = NULL; |
| } |
| } |
| |
| // Store the new interface name if it's changed. |
| if (changed) { |
| publisher->thread_interface_name = strdup(name); |
| |
| INFO("thread interface at " PUB_S_SRP, name); |
| } |
| |
| if (publisher->thread_interface_name == NULL) { |
| ERROR("No memory to save thread interface name " PUB_S_SRP, name); |
| } |
| |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_thread_interface_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| goto out; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| out: |
| RELEASE_HERE(publisher, service_publisher); // callback held a reference. |
| } |
| |
| static void |
| service_publisher_wed_callback(void *context, const char *ext_address, const char *ml_eid, bool added, int status) |
| { |
| service_publisher_t *publisher = context; |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| goto out; |
| } |
| |
| const char *none = "<none>"; |
| const char *ea = none; |
| if (ext_address != NULL) { |
| ea = ext_address; |
| } |
| const char *mle = none; |
| if (ml_eid != NULL) { |
| int ret = inet_pton(AF_INET6, ml_eid, &publisher->wed_ml_eid); |
| if (ret) { |
| mle = ml_eid; |
| } |
| } |
| |
| INFO("ext_address: " PRI_S_SRP " ml_eid: " PRI_S_SRP PUB_S_SRP " %d", ea, mle, added ? " added" : " removed", status); |
| if (status != kCTIStatus_NoError) { |
| goto out; |
| } |
| |
| if (publisher->wed_ext_address_string != NULL) { |
| free(publisher->wed_ext_address_string); |
| publisher->wed_ext_address_string = NULL; |
| } |
| |
| if (publisher->wed_ml_eid_string != NULL) { |
| free(publisher->wed_ml_eid_string); |
| publisher->wed_ml_eid_string = NULL; |
| } |
| |
| // API guarantees addresses are non-NULL if added is true. |
| if (added) { |
| if (ea != none) { |
| publisher->wed_ext_address_string = strdup(ea); |
| if (publisher->wed_ext_address_string == NULL) { |
| ERROR("no memory for wed_ext_address string!"); |
| } |
| } |
| if (mle != none) { |
| publisher->wed_ml_eid_string = strdup(mle); |
| if (publisher->wed_ml_eid_string == NULL) { |
| ERROR("no memory for wed_ml_eid string!"); |
| memset(&publisher->wed_ml_eid, 0, sizeof(publisher->wed_ml_eid)); |
| } |
| } else { |
| memset(&publisher->wed_ml_eid, 0, sizeof(publisher->wed_ml_eid)); |
| } |
| } |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_wed_ml_eid_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| goto out; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| out: |
| ; |
| } |
| |
| static void |
| service_publisher_neighbor_callback(void *context, const char *ml_eid, cti_status_t status) |
| { |
| service_publisher_t *publisher = context; |
| if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) { |
| INFO("disconnected"); |
| goto out; |
| } |
| |
| const char *none = "<none>"; |
| const char *mle = none; |
| if (ml_eid != NULL) { |
| if (!strcmp(ml_eid, "none")) { |
| mle = ml_eid; |
| memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid)); |
| } else { |
| int ret = inet_pton(AF_INET6, ml_eid, &publisher->neighbor_ml_eid); |
| if (ret) { |
| mle = ml_eid; |
| } |
| } |
| } |
| |
| INFO("ml_eid: " PRI_S_SRP ", status %d", mle, status); |
| if (status != kCTIStatus_NoError) { |
| goto out; |
| } |
| |
| |
| if (publisher->neighbor_ml_eid_string != NULL) { |
| free(publisher->neighbor_ml_eid_string); |
| publisher->neighbor_ml_eid_string = NULL; |
| } |
| |
| if (mle != none) { |
| publisher->neighbor_ml_eid_string = strdup(mle); |
| if (publisher->neighbor_ml_eid_string == NULL) { |
| ERROR("no memory for neighbor_ml_eid string!"); |
| memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid)); |
| } |
| } else { |
| memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid)); |
| } |
| state_machine_event_t *event = state_machine_event_create(state_machine_event_type_neighbor_ml_eid_changed, NULL); |
| if (event == NULL) { |
| ERROR("unable to allocate event to deliver"); |
| goto out; |
| } |
| state_machine_event_deliver(&publisher->state_header, event); |
| RELEASE_HERE(event, state_machine_event); |
| |
| if (publisher->sed_timeout != NULL) { |
| ioloop_cancel_wake_event(publisher->sed_timeout); |
| ioloop_wakeup_release(publisher->sed_timeout); |
| publisher->sed_timeout = NULL; |
| } |
| out: |
| ; |
| } |
| |
| void |
| service_publisher_start(service_publisher_t *publisher) |
| { |
| cti_status_t status = cti_track_active_data_set(publisher->server_state, &publisher->active_data_set_connection, |
| publisher, service_publisher_active_data_set_changed_callback, |
| NULL); |
| if (status != kCTIStatus_NoError) { |
| ERROR("unable to start tracking active dataset: %d", status); |
| } else { |
| RETAIN_HERE(publisher, service_publisher); // for active dataset callback |
| } |
| |
| status = cti_get_tunnel_name(publisher->server_state, publisher, service_publisher_tunnel_name_callback, NULL); |
| if (status != kCTIStatus_NoError) { |
| ERROR("unable to get tunnel name: %d", status); |
| } else { |
| RETAIN_HERE(publisher, service_publisher); // for tunnel name callback |
| } |
| |
| |
| status = cti_track_wed_status(publisher->server_state, &publisher->wed_tracker_connection, |
| publisher, service_publisher_wed_callback, NULL); |
| if (status != kCTIStatus_NoError) { |
| FAULT("can't track WED status: %d", status); |
| } else { |
| RETAIN_HERE(publisher, service_publisher); |
| } |
| |
| status = cti_track_neighbor_ml_eid(publisher->server_state, &publisher->neighbor_tracker_connection, |
| publisher, service_publisher_neighbor_callback, NULL); |
| if (status != kCTIStatus_NoError) { |
| FAULT("can't track WED status: %d", status); |
| } else { |
| RETAIN_HERE(publisher, service_publisher); |
| } |
| |
| service_publisher_active_data_set_changed_callback(publisher, kCTIStatus_NoError); // Get the initial state. |
| state_machine_next_state(&publisher->state_header, service_publisher_state_startup); |
| } |
| |
| bool |
| service_publisher_get_ml_eid(service_publisher_t *publisher, struct in6_addr *ml_eid) |
| { |
| if (publisher != NULL && publisher->have_ml_eid) { |
| in6addr_copy(ml_eid, &publisher->thread_mesh_local_address); |
| return true; |
| } |
| return false; |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 120 |
| // indent-tabs-mode: nil |
| // End: |