| /* srp-replication.c |
| * |
| * Copyright (c) 2020-2023 Apple Inc. All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * This file contains an implementation of SRP Replication, which allows two or more |
| * SRP servers to cooperatively maintain an SRP registration dataset. |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <pwd.h> |
| #include <errno.h> |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <time.h> |
| #include <dns_sd.h> |
| #include <net/if.h> |
| #include <inttypes.h> |
| #include <sys/resource.h> |
| #include <math.h> |
| #include <CoreUtils/CoreUtils.h> |
| |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "srp-crypto.h" |
| #include "ioloop.h" |
| #include "srp-gw.h" |
| #include "srp-proxy.h" |
| #include "srp-mdns-proxy.h" |
| #include "dnssd-proxy.h" |
| #include "config-parse.h" |
| #include "cti-services.h" |
| #include "route.h" |
| #define DNSMessageHeader dns_wire_t |
| #include "dso.h" |
| #include "dso-utils.h" |
| |
| #if SRP_FEATURE_REPLICATION |
| #include "srp-replication.h" |
| |
| #define SRPL_CONNECTION_IS_CONNECTED(connection) ((connection)->state > srpl_state_connecting) |
| |
| static srpl_instance_t *unmatched_instances; |
| |
| #define srpl_event_content_type_set(event, content_type) \ |
| srpl_event_content_type_set_(event, content_type, __FILE__, __LINE__) |
| static bool srpl_event_content_type_set_(srpl_event_t *event, |
| srpl_event_content_type_t content_type, const char *file, int line); |
| static srpl_state_t srpl_connection_drop_state_delay(srpl_instance_t *instance, |
| srpl_connection_t *srpl_connection, int delay); |
| static srpl_state_t srpl_connection_drop_state(srpl_instance_t *instance, srpl_connection_t *srpl_connection); |
| static void srpl_disconnect(srpl_connection_t *srpl_connection); |
| static void srpl_connection_discontinue(srpl_connection_t *srpl_connection); |
| static void srpl_connection_next_state(srpl_connection_t *srpl_connection, srpl_state_t state); |
| static void srpl_event_initialize(srpl_event_t *event, srpl_event_type_t event_type); |
| static void srpl_event_deliver(srpl_connection_t *srpl_connection, srpl_event_t *event); |
| static void srpl_domain_advertise(srpl_domain_t *domain); |
| static void srpl_connection_finalize(srpl_connection_t *srpl_connection); |
| static void srpl_instance_address_query_reset(srpl_instance_t *instance); |
| static void srpl_instance_reconnect(srpl_instance_t *instance); |
| static void srpl_instance_reconnect_callback(void *context); |
| static bool srpl_domain_browse_start(srpl_domain_t *domain); |
| static const char *srpl_state_name(srpl_state_t state); |
| static bool srpl_can_transition_to_routine_state(const srpl_domain_t *domain); |
| static void srpl_transition_to_routine_state(srpl_domain_t *domain); |
| static void srpl_message_sent(srpl_connection_t *srpl_connection); |
| static srpl_state_t srpl_connection_schedule_reconnect_event(srpl_connection_t *srpl_connection, uint32_t when); |
| static void srpl_partner_discovery_timeout(void *context); |
| static void srpl_instance_services_discontinue(srpl_instance_t *instance); |
| static void srpl_instance_service_discontinue_timeout(void *context); |
| static void srpl_maybe_sync_or_transition(srpl_domain_t *domain); |
| |
| #define EQUI_DISTANCE64 (int64_t)0x8000000000000000 |
| |
| #ifdef DEBUG |
| #define STATE_DEBUGGING_ABORT() abort(); |
| #else |
| #define STATE_DEBUGGING_ABORT() |
| #endif |
| |
| // Send reconfirm records for all queries relating to this connection. |
| static void |
| srpl_reconfirm(srpl_connection_t *connection) |
| { |
| // If there's no instance, that's why we got here, so no need for reconfirms. |
| if (connection->instance == NULL) { |
| INFO("no instance"); |
| return; |
| } |
| srpl_instance_t *instance = connection->instance; |
| |
| for (srpl_instance_service_t *service = instance->services; service != NULL; service = service->next) { |
| if (!service->got_new_info) { |
| INFO("we haven't had any new information since the last time we did a reconfirm, so no point doing it again."); |
| continue; |
| } |
| service->got_new_info = false; |
| |
| if (service->full_service_name != NULL && service->ptr_rdata != NULL) { |
| // The service name is the service instance name minus the first label. |
| char *service_type = strchr(service->full_service_name, '.'); |
| if (service_type != NULL) { |
| service_type++; // Skip the '.' |
| // Send a reconfirm for the PTR record |
| DNSServiceReconfirmRecord(0, service->ifindex, service_type, dns_rrtype_ptr, dns_qclass_in, |
| service->ptr_length, service->ptr_rdata); |
| } |
| if (service->srv_rdata != NULL) { |
| DNSServiceReconfirmRecord(0, service->ifindex, service->full_service_name, dns_rrtype_srv, dns_qclass_in, |
| service->srv_length, service->srv_rdata); |
| } |
| if (service->txt_rdata != NULL) { |
| DNSServiceReconfirmRecord(0, service->ifindex, service->full_service_name, dns_rrtype_txt, dns_qclass_in, |
| service->txt_length, service->txt_rdata); |
| } |
| if (service->address_query != NULL) { |
| address_query_t *query = service->address_query; |
| for (int i = 0; i > query->num_addresses; i++) { |
| if (query->addresses[i].sa.sa_family == AF_INET) { |
| DNSServiceReconfirmRecord(0, query->address_interface[i], query->hostname, dns_rrtype_a, |
| dns_qclass_in, 4, &query->addresses[i].sin.sin_addr); |
| } else if (query->addresses[i].sa.sa_family == AF_INET6) { |
| DNSServiceReconfirmRecord(0, query->address_interface[i], query->hostname, dns_rrtype_aaaa, |
| dns_qclass_in, 16, &query->addresses[i].sin6.sin6_addr); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // |
| // 1. Enumerate all SRP servers that are participating in synchronization on infrastructure: This is done by looking up |
| // NS records for <thread-network-name>.thread.home.arpa with the ForceMulticast flag set so that we use mDNS to |
| // discover them. |
| |
| // 2. For each identified server that is not this server, look up A and AAAA records for the server's hostname. |
| |
| // 3. Maintain a state object with the list of IP addresses and an index to the current server being tried. |
| |
| // 4. Try to connect to the first address on the list -> connection management state machine: |
| |
| // * When we have established an outgoing connection to a server, generate a random 64-bit unsigned number and send a |
| // SRPLSession DSO message using that number as the server ID. |
| |
| // * When we receive an SRPLSession DSO message, see if we have an outgoing connection from the same server |
| // for which we've sent a server ID. If so, and if the server id we received is less than the one we sent, |
| // terminate the outgoing connection. If the server ids are equal, generate a new server id for the outgoing |
| // connection and send another session establishment message. |
| // |
| // * When we receive an acknowledgement to our SRPLSession DSO message, see if we have an incoming |
| // connection from the same server from which we've received a server id. If the server id we received is less |
| // than the one we sent, terminate the outgoing connection. If the server ids are equal, generate a new random |
| // number for the outgoing connection and send another session establishment message. |
| // |
| // * When a connection from a server is terminated, see if we have an established outgoing connection with that |
| // server. If not, attempt to connect to the next address we have for that server. |
| // |
| // * When our connection to a server is terminated or fails, and there is no established incoming connection from that |
| // server, attempt to connect to the next address we have for that server. |
| // |
| // * When the NS record for a server goes away, drop any outgoing connection to that server and discontinue trying to |
| // connect to it. |
| // |
| // 5. When we have established a session, meaning that we either got an acknowledgment to a SRPLSession DSO |
| // message that we sent and _didn't_ drop the connection, or we got an SRPLSession DSO message on an |
| // incoming connection with a lower server id than the outgoing connection, we begin the synchronization process. |
| // |
| // * Immediately following session establishment, we generate a list of candidate hosts to send to the other server |
| // from our internal list of SRP hosts (clients). Every non-expired host entry goes into the candidate list. |
| // |
| // * Then, if we are the originator, we sent an SRPLSendCandidates message. |
| // |
| // * If we are the recipient, we wait for an SRPLSendCandidates message. |
| // |
| // * When we receive an SRPLSendCandidates message, we iterate across the candidates list, for each |
| // candidate sending an SRPLCandidate message containing the host key, current time, and last message |
| // received times, in seconds since the epoch. When we come to the end of the candidates list, we send an |
| // acknowledgement to the SRPLSendCandidates message and discard the candidates list. |
| // |
| // * When we receive an SRPLCandidate message, we look in our own candidate list, if there is one, to see |
| // if the host key is present in the candidates list. If it is not present, or if it is present and the received |
| // time from the SRPLCandidate message is later than the time we have recorded in our own candidate |
| // list, we send an SRPLCandidateRequest message with the host key from the SRPLCandidate |
| // message. |
| // |
| // * When we receive an SRPLCandidateRequest message, we send an SRPLHost message which |
| // encapsulates the SRP update for the host and includes the timestamp when we received the SRP update from that |
| // host, which may have changed since we sent the SRPLCandidate message. |
| // |
| // * When we receive an SRPLHost message, we look in our list of hosts (SRP clients) for a matching |
| // host. If no such host exists, or if it exists and the timestamp is less than the timestamp in the |
| // SRPLHost message, we process the SRP update from the SRPLHost message and mark the host as |
| // "not received locally." In other words, this message was not received directly from an SRP client, but rather |
| // indirectly through our SRP replication partner. Note that this message cannot be assumed to be syntactically |
| // correct and must be treated like any other data received from the network. If we are sent an invalid message, |
| // this is an indication that our partner is broken in some way, since it should have validated the message before |
| // accepting it. |
| // |
| // * Whenever the SRP engine applies an SRP update from a host, it also delivers that update to each replication |
| // server state engine. |
| // |
| // * That replication server state engine first checks to see if it is connected to its partner; if not, no action |
| // is taken. |
| // |
| // * It then checks to see if there is a candidate list. |
| // * If so, it checks to see if the host implicated in the update is already on the candidate list. |
| // * If so, it updates that candidate's update time. |
| // * If not, it adds the host to the end of the candidate list. |
| // * If not, it sends an SRPLCandidate message to the other replication server, with the host |
| // key and new timestamp. |
| // |
| // 6. When there is more than one SRP server participating in replication, only one server should advertise using |
| // mDNS. All other servers should only advertise using DNS and DNS Push (SRP scalability feature). The SRP server |
| // with the lowest numbered server ID is the one that acts as an advertising proxy for SRP. In practice this means |
| // that if we have the lowest server ID of all the SRP servers we are connected to, we advertise mDNS. If two servers |
| // on the same link can't connect to each other, they probably can't see each others' multicasts, so this is the |
| // right outcome. |
| |
| static bool |
| ip_addresses_equal(const addr_t *a, const addr_t *b) |
| { |
| return (a->sa.sa_family == b->sa.sa_family && |
| ((a->sa.sa_family == AF_INET && !memcmp(&a->sin.sin_addr, &b->sin.sin_addr, 4)) || |
| (a->sa.sa_family == AF_INET6 && !memcmp(&a->sin6.sin6_addr, &b->sin6.sin6_addr, 16)))); |
| } |
| |
| #define ADDR_NAME_LOGGER(log_type, address, preamble, conjunction, number, fullname, interfaceIndex) \ |
| if ((address)->sa.sa_family == AF_INET6) { \ |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&(address)->sin6.sin6_addr, rdata_buf); \ |
| log_type(PUB_S_SRP PRI_SEGMENTED_IPv6_ADDR_SRP PUB_S_SRP PRI_S_SRP PUB_S_SRP "%d", preamble, \ |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&(address)->sin6.sin6_addr, rdata_buf), \ |
| conjunction, fullname, number, interfaceIndex); \ |
| } else { \ |
| IPv4_ADDR_GEN_SRP(&(address)->sin.sin_addr, rdata_buf); \ |
| log_type(PUB_S_SRP PRI_IPv4_ADDR_SRP PUB_S_SRP PRI_S_SRP PUB_S_SRP "%d", preamble, \ |
| IPv4_ADDR_PARAM_SRP(&(address)->sin.sin_addr, rdata_buf), \ |
| conjunction, fullname, number, interfaceIndex); \ |
| } |
| |
| static void |
| address_query_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, |
| uint16_t rdlen, const void *rdata, uint32_t UNUSED ttl, void *context) |
| { |
| address_query_t *address = context; |
| addr_t addr; |
| int i, j; |
| |
| if (errorCode != kDNSServiceErr_NoError) { |
| ERROR("address resolution for " PRI_S_SRP " failed with %d", fullname, errorCode); |
| address->change_callback(address->context, NULL, false, errorCode); |
| return; |
| } |
| if (rrclass != dns_qclass_in || ((rrtype != dns_rrtype_a || rdlen != 4) && |
| (rrtype != dns_rrtype_aaaa || rdlen != 16))) { |
| ERROR("Invalid response record type (%d) or class (%d) provided for " PRI_S_SRP, rrtype, rrclass, fullname); |
| return; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| if (rrtype == dns_rrtype_a) { |
| #ifndef NOT_HAVE_SA_LEN |
| addr.sa.sa_len = sizeof(struct sockaddr_in); |
| #endif |
| addr.sa.sa_family = AF_INET; |
| memcpy(&addr.sin.sin_addr, rdata, rdlen); |
| if (IN_LINKLOCAL(addr.sin.sin_addr.s_addr)) { |
| ADDR_NAME_LOGGER(INFO, &addr, "Skipping link-local address ", " received for instance ", " index ", |
| fullname, interfaceIndex); |
| return; |
| } |
| } else { |
| #ifndef NOT_HAVE_SA_LEN |
| addr.sa.sa_len = sizeof(struct sockaddr_in6); |
| #endif |
| addr.sa.sa_family = AF_INET6; |
| memcpy(&addr.sin6.sin6_addr, rdata, rdlen); |
| if (IN6_IS_ADDR_LINKLOCAL(&addr.sin6.sin6_addr)) { |
| ADDR_NAME_LOGGER(INFO, &addr, "Skipping link-local address ", " received for instance ", " index ", |
| fullname, interfaceIndex); |
| return; |
| } |
| } |
| |
| for (i = 0, j = 0; i < address->num_addresses; i++) { |
| // Already in the list? |
| if (address->address_interface[i] == interfaceIndex && !memcmp(&address->addresses[i], &addr, sizeof(addr))) { |
| if (flags & kDNSServiceFlagsAdd) { |
| ADDR_NAME_LOGGER(INFO, &addr, "Duplicate address ", " received for instance ", " index ", |
| fullname, interfaceIndex); |
| return; |
| } else { |
| ADDR_NAME_LOGGER(INFO, &addr, "Removing address ", " from instance ", " index ", |
| fullname, interfaceIndex); |
| |
| // If we're removing an address, we keep going through the array copying down. |
| if (address->cur_address >= i) { |
| address->cur_address--; |
| } |
| } |
| } else { |
| // Copy down. |
| if (i != j) { |
| address->addresses[j] = address->addresses[i]; |
| address->address_interface[j] = address->address_interface[i]; |
| } |
| j++; |
| } |
| } |
| if (flags & kDNSServiceFlagsAdd) { |
| if (i == ADDRESS_QUERY_MAX_ADDRESSES) { |
| ADDR_NAME_LOGGER(ERROR, &addr, "No room for address ", " received for ", " index ", |
| fullname, interfaceIndex); |
| return; |
| } |
| |
| ADDR_NAME_LOGGER(INFO, &addr, "Adding address ", " to ", " index ", fullname, interfaceIndex); |
| |
| address->addresses[i] = addr; |
| address->address_interface[i] = interfaceIndex; |
| address->num_addresses++; |
| address->change_callback(address->context, &address->addresses[i], true, kDNSServiceErr_NoError); |
| } else { |
| if (i == j) { |
| ADDR_NAME_LOGGER(ERROR, &addr, "Remove for unknown address ", " received for ", " index ", |
| fullname, interfaceIndex); |
| return; |
| } else { |
| address->num_addresses--; |
| address->change_callback(address->context, &addr, false, kDNSServiceErr_NoError); |
| } |
| } |
| } |
| |
| static void |
| address_query_finalize(void *context) |
| { |
| address_query_t *address = context; |
| free(address->hostname); |
| free(address); |
| } |
| |
| static void |
| address_query_cancel(address_query_t *address) |
| { |
| if (address->a_query != NULL) { |
| ioloop_dnssd_txn_cancel(address->a_query); |
| ioloop_dnssd_txn_release(address->a_query); |
| address->a_query = NULL; |
| } |
| if (address->aaaa_query != NULL) { |
| ioloop_dnssd_txn_cancel(address->aaaa_query); |
| ioloop_dnssd_txn_release(address->aaaa_query); |
| address->aaaa_query = NULL; |
| } |
| |
| // Have whatever holds a reference to the address query let go of it. |
| if (address->cancel_callback != NULL && address->context != NULL) { |
| address->cancel_callback(address->context); |
| address->context = NULL; |
| address->cancel_callback = NULL; |
| } |
| } |
| |
| static void |
| address_query_txn_fail(void *context, int err) |
| { |
| address_query_t *address = context; |
| ERROR("address query " PRI_S_SRP " i/o failure: %d", address->hostname, err); |
| address_query_cancel(address); |
| } |
| |
| static void |
| address_query_context_release(void *context) |
| { |
| address_query_t *address = context; |
| RELEASE_HERE(address, address_query); |
| } |
| |
| static address_query_t * |
| address_query_create(const char *hostname, void *context, address_change_callback_t change_callback, |
| address_query_cancel_callback_t cancel_callback) |
| { |
| address_query_t *address = calloc(1, sizeof(*address)); |
| DNSServiceRef sdref; |
| dnssd_txn_t **txn; |
| |
| require_action_quiet(address != NULL, exit_no_free, ERROR("No memory for address query.")); |
| RETAIN_HERE(address, address_query); // We return a retained object, or free it. |
| address->hostname = strdup(hostname); |
| require_action_quiet(address->hostname != NULL, exit, ERROR("No memory for address query hostname.")); |
| |
| for (int i = 0; i < 2; i++) { |
| int ret = DNSServiceQueryRecord(&sdref, kDNSServiceFlagsForceMulticast | kDNSServiceFlagsLongLivedQuery, |
| kDNSServiceInterfaceIndexAny, hostname, (i |
| ? kDNSServiceType_A |
| : kDNSServiceType_AAAA), |
| kDNSServiceClass_IN, address_query_callback, address); |
| require_action_quiet(ret == kDNSServiceErr_NoError, exit, |
| ERROR("Unable to resolve instance hostname " PRI_S_SRP " addresses: %d", |
| hostname, ret)); |
| |
| txn = i ? &address->a_query : &address->aaaa_query; |
| *txn = ioloop_dnssd_txn_add(sdref, address, address_query_context_release, address_query_txn_fail); |
| require_action_quiet(*txn != NULL, exit, |
| ERROR("Unable to set up ioloop transaction for " PRI_S_SRP " query on " THREAD_BROWSING_DOMAIN, |
| hostname); |
| DNSServiceRefDeallocate(sdref)); |
| RETAIN_HERE(address, address_query); // For the QueryRecord context |
| } |
| address->change_callback = change_callback; |
| address->cancel_callback = cancel_callback; |
| address->context = context; |
| address->cur_address = -1; |
| return address; |
| |
| exit: |
| if (address->a_query != NULL) { |
| ioloop_dnssd_txn_cancel(address->a_query); |
| ioloop_dnssd_txn_release(address->a_query); |
| address->a_query = NULL; |
| } |
| if (address->aaaa_query != NULL) { // Un-possible right now, but better safe than sorry in case of future change |
| ioloop_dnssd_txn_cancel(address->aaaa_query); |
| ioloop_dnssd_txn_release(address->aaaa_query); |
| address->aaaa_query = NULL; |
| } |
| RELEASE_HERE(address, address_query); |
| address = NULL; |
| |
| exit_no_free: |
| return address; |
| } |
| |
| static void |
| srpl_domain_finalize(srpl_domain_t *domain) |
| { |
| srpl_instance_t *instance, *next; |
| srpl_instance_service_t *service, *next_service; |
| |
| free(domain->name); |
| if (domain->query != NULL) { |
| ioloop_dnssd_txn_cancel(domain->query); |
| ioloop_dnssd_txn_release(domain->query); |
| } |
| |
| for (instance = domain->instances; instance != NULL; instance = next) { |
| next = instance->next; |
| srpl_instance_services_discontinue(instance); |
| } |
| for (service = domain->unresolved_services; service != NULL; service = next_service) { |
| next_service = service->next; |
| srpl_instance_service_discontinue_timeout(service); |
| } |
| if (domain->query != NULL) { |
| ioloop_dnssd_txn_cancel(domain->query); |
| ioloop_dnssd_txn_release(domain->query); |
| domain->query = NULL; |
| } |
| if (domain->srpl_advertise_txn != NULL) { |
| ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn); |
| ioloop_dnssd_txn_release(domain->srpl_advertise_txn); |
| domain->srpl_advertise_txn = NULL; |
| } |
| if (domain->srpl_register_wakeup != NULL) { |
| ioloop_cancel_wake_event(domain->srpl_register_wakeup); |
| ioloop_wakeup_release(domain->srpl_register_wakeup); |
| domain->srpl_register_wakeup = NULL; |
| } |
| if (domain->partner_discovery_timeout != NULL) { |
| ioloop_cancel_wake_event(domain->partner_discovery_timeout); |
| ioloop_wakeup_release(domain->partner_discovery_timeout); |
| domain->partner_discovery_timeout = NULL; |
| } |
| |
| free(domain); |
| } |
| |
| static void srpl_instance_finalize(srpl_instance_t *instance); |
| |
| static void |
| srpl_instance_service_finalize(srpl_instance_service_t *service) |
| { |
| if (service->domain != NULL) { |
| RELEASE_HERE(service->domain, srpl_domain); |
| } |
| if (service->txt_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->txt_txn); |
| ioloop_dnssd_txn_release(service->txt_txn); |
| service->txt_txn = NULL; |
| } |
| if (service->srv_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->srv_txn); |
| ioloop_dnssd_txn_release(service->srv_txn); |
| service->srv_txn = NULL; |
| } |
| free(service->full_service_name); |
| free(service->host_name); |
| free(service->txt_rdata); |
| free(service->srv_rdata); |
| free(service->ptr_rdata); |
| if (service->address_query != NULL) { |
| address_query_cancel(service->address_query); |
| RELEASE_HERE(service->address_query, address_query); |
| service->address_query = NULL; |
| } |
| if (service->discontinue_timeout != NULL) { |
| ioloop_cancel_wake_event(service->discontinue_timeout); |
| ioloop_wakeup_release(service->discontinue_timeout); |
| service->discontinue_timeout = NULL; |
| } |
| if (service->resolve_wakeup != NULL) { |
| ioloop_cancel_wake_event(service->resolve_wakeup); |
| ioloop_wakeup_release(service->resolve_wakeup); |
| service->resolve_wakeup = NULL; |
| } |
| if (service->instance != NULL) { |
| RELEASE_HERE(service->instance, srpl_instance); |
| service->instance = NULL; |
| } |
| free(service); |
| } |
| |
| static void |
| srpl_instance_finalize(srpl_instance_t *instance) |
| { |
| if (instance->domain != NULL) { |
| RELEASE_HERE(instance->domain, srpl_domain); |
| instance->domain = NULL; |
| } |
| free(instance->instance_name); |
| if (instance->connection != NULL) { |
| srpl_connection_discontinue(instance->connection); |
| RELEASE_HERE(instance->connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| if (instance->reconnect_timeout != NULL) { |
| ioloop_cancel_wake_event(instance->reconnect_timeout); |
| ioloop_wakeup_release(instance->reconnect_timeout); |
| instance->reconnect_timeout = NULL; |
| } |
| |
| srpl_instance_service_t *service = instance->services, *next; |
| while (service != NULL) { |
| next = service->next; |
| RELEASE_HERE(service, srpl_instance_service); |
| service = next; |
| } |
| instance->services = NULL; |
| free(instance); |
| } |
| |
| #define srpl_connection_message_set(srpl_connection, message) \ |
| srpl_connection_message_set_(srpl_connection, message, __FILE__, __LINE__) |
| static void |
| srpl_connection_message_set_(srpl_connection_t *srpl_connection, message_t *message, const char *file, int line) |
| { |
| if (srpl_connection->message != NULL) { |
| ioloop_message_release_(srpl_connection->message, file, line); |
| srpl_connection->message = NULL; |
| } |
| if (message != NULL) { |
| srpl_connection->message = message; |
| ioloop_message_retain_(srpl_connection->message, file, line); |
| } |
| } |
| |
| static message_t * |
| srpl_connection_message_get(srpl_connection_t *srpl_connection) |
| { |
| return srpl_connection->message; |
| } |
| |
| #define srpl_candidate_free(candidate) srpl_candidate_free_(candidate, __FILE__, __LINE__) |
| static void |
| srpl_candidate_free_(srpl_candidate_t *candidate, const char *file, int line) |
| { |
| if (candidate != NULL) { |
| if (candidate->name != NULL) { |
| dns_name_free(candidate->name); |
| candidate->name = NULL; |
| } |
| if (candidate->message != NULL) { |
| ioloop_message_release_(candidate->message, file, line); |
| candidate->message = NULL; |
| } |
| if (candidate->host != NULL) { |
| srp_adv_host_release_(candidate->host, file, line); |
| candidate->host = NULL; |
| } |
| free(candidate); |
| } |
| } |
| |
| static void |
| srpl_connection_candidates_free(srpl_connection_t *srpl_connection) |
| { |
| if (srpl_connection->candidates == NULL) { |
| goto out; |
| } |
| for (int i = 0; i < srpl_connection->num_candidates; i++) { |
| if (srpl_connection->candidates[i] != NULL) { |
| srp_adv_host_release(srpl_connection->candidates[i]); |
| } |
| } |
| free(srpl_connection->candidates); |
| srpl_connection->candidates = NULL; |
| out: |
| srpl_connection->num_candidates = srpl_connection->current_candidate = 0; |
| return; |
| } |
| |
| static void |
| srpl_srp_client_update_queue_free(srpl_connection_t *srpl_connection) |
| { |
| srpl_srp_client_queue_entry_t **cp = &srpl_connection->client_update_queue; |
| while (*cp) { |
| srpl_srp_client_queue_entry_t *entry = *cp; |
| srp_adv_host_release(entry->host); |
| *cp = entry->next; |
| free(entry); |
| } |
| } |
| |
| static void |
| srpl_connection_candidate_set(srpl_connection_t *srpl_connection, srpl_candidate_t *candidate) |
| { |
| if (srpl_connection->candidate != NULL) { |
| srpl_candidate_free(srpl_connection->candidate); |
| } |
| srpl_connection->candidate = candidate; |
| } |
| |
| static void |
| srpl_host_update_parts_free(srpl_host_update_t *update) |
| { |
| if (update->messages != NULL) { |
| for (int i = 0; i < update->num_messages; i++) { |
| ioloop_message_release(update->messages[i]); |
| } |
| free(update->messages); |
| update->messages = NULL; |
| update->num_messages = update->max_messages = update->messages_processed = 0; |
| } |
| if (update->hostname != NULL) { |
| dns_name_free(update->hostname); |
| update->hostname = NULL; |
| } |
| } |
| |
| // Free up any temporarily retained or allocated objects on the connection (i.e., not the name). |
| static void |
| srpl_connection_reset(srpl_connection_t *srpl_connection) |
| { |
| srpl_connection->candidates_not_generated = true; |
| srpl_connection->database_synchronized = false; |
| srpl_host_update_parts_free(&srpl_connection->stashed_host); |
| srpl_connection_message_set(srpl_connection, NULL); |
| if (srpl_connection->candidate != NULL) { |
| srpl_candidate_free(srpl_connection->candidate); |
| srpl_connection->candidate = NULL; |
| } |
| |
| // Cancel keepalive timers |
| if (srpl_connection->keepalive_send_wakeup) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_send_wakeup); |
| } |
| if (srpl_connection->keepalive_receive_wakeup) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_receive_wakeup); |
| } |
| |
| srpl_connection_candidates_free(srpl_connection); |
| srpl_srp_client_update_queue_free(srpl_connection); |
| } |
| |
| static void |
| srpl_connection_finalize(srpl_connection_t *srpl_connection) |
| { |
| if (srpl_connection->instance) { |
| RELEASE_HERE(srpl_connection->instance, srpl_instance); |
| srpl_connection->instance = NULL; |
| } |
| if (srpl_connection->connection != NULL) { |
| ioloop_comm_release(srpl_connection->connection); |
| srpl_connection->connection = NULL; |
| } |
| if (srpl_connection->reconnect_wakeup != NULL) { |
| ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup); |
| ioloop_wakeup_release(srpl_connection->reconnect_wakeup); |
| srpl_connection->reconnect_wakeup = NULL; |
| } |
| if (srpl_connection->keepalive_send_wakeup != NULL) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_send_wakeup); |
| ioloop_wakeup_release(srpl_connection->keepalive_send_wakeup); |
| srpl_connection->keepalive_send_wakeup = NULL; |
| } |
| if (srpl_connection->keepalive_receive_wakeup != NULL) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_receive_wakeup); |
| ioloop_wakeup_release(srpl_connection->keepalive_receive_wakeup); |
| srpl_connection->keepalive_receive_wakeup = NULL; |
| } |
| free(srpl_connection->name); |
| srpl_connection_reset(srpl_connection); |
| free(srpl_connection); |
| } |
| |
| void |
| srpl_connection_release_(srpl_connection_t *srpl_connection, const char *file, int line) |
| { |
| RELEASE(srpl_connection, srpl_connection); |
| } |
| |
| void |
| srpl_connection_retain_(srpl_connection_t *srpl_connection, const char *file, int line) |
| { |
| RETAIN(srpl_connection, srpl_connection); |
| } |
| |
| static srpl_connection_t * |
| srpl_connection_create(srpl_instance_t *instance, bool outgoing) |
| { |
| srpl_connection_t *srpl_connection = calloc(1, sizeof (*srpl_connection)), *ret = NULL; |
| size_t srpl_connection_name_length; |
| if (srpl_connection == NULL) { |
| goto out; |
| } |
| RETAIN_HERE(srpl_connection, srpl_connection); |
| if (outgoing) { |
| srpl_connection_name_length = strlen(instance->instance_name) + 2; |
| } else { |
| srpl_connection_name_length = strlen(instance->instance_name) + 2; |
| } |
| srpl_connection->name = malloc(srpl_connection_name_length); |
| if (srpl_connection->name == NULL) { |
| goto out; |
| } |
| srpl_connection->keepalive_send_wakeup = ioloop_wakeup_create(); |
| if (srpl_connection->keepalive_send_wakeup == NULL) { |
| goto out; |
| } |
| srpl_connection->keepalive_receive_wakeup = ioloop_wakeup_create(); |
| if (srpl_connection->keepalive_receive_wakeup == NULL) { |
| goto out; |
| } |
| srpl_connection->keepalive_interval = DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2; |
| snprintf(srpl_connection->name, srpl_connection_name_length, "%s%s", outgoing ? ">" : "<", instance->instance_name); |
| srpl_connection->is_server = !outgoing; |
| srpl_connection->instance = instance; |
| RETAIN_HERE(instance, srpl_instance); |
| ret = srpl_connection; |
| srpl_connection = NULL; |
| out: |
| if (srpl_connection != NULL) { |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| } |
| return ret; |
| } |
| |
| static void |
| srpl_connection_context_release(void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| } |
| |
| static void |
| srpl_instance_service_context_release(void *context) |
| { |
| srpl_instance_service_t *service = context; |
| |
| RELEASE_HERE(service, srpl_instance_service); |
| } |
| |
| static void |
| srpl_instance_context_release(void *context) |
| { |
| srpl_instance_t *instance = context; |
| |
| RELEASE_HERE(instance, srpl_instance); |
| } |
| |
| static void |
| srpl_instance_discontinue_timeout(void *context) |
| { |
| srpl_instance_t **sp = NULL, *instance = context; |
| srpl_domain_t *domain = instance->domain; |
| |
| INFO("discontinuing instance " PRI_S_SRP " with partner id %" PRIx64, instance->instance_name, instance->partner_id); |
| for (sp = &domain->instances; *sp; sp = &(*sp)->next) { |
| if (*sp == instance) { |
| *sp = instance->next; |
| break; |
| } |
| } |
| |
| srpl_connection_t *srpl_connection = instance->connection; |
| if (srpl_connection != NULL) { |
| RELEASE_HERE(srpl_connection->instance, srpl_instance); |
| srpl_connection->instance = NULL; |
| srpl_connection_discontinue(srpl_connection); |
| // The instance no longer has a reference to the srpl_connection object. |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| RELEASE_HERE(instance, srpl_instance); |
| |
| // Check to see if we are eligible to move into the routine state if we haven't done so. |
| // If the partner we failed to sync with goes away, we could enter the routine state if |
| // we have succcessfully sync-ed with all other partners discovered in startup. |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) { |
| srpl_maybe_sync_or_transition(domain); |
| } |
| } |
| |
| static void |
| srpl_instance_service_discontinue_timeout(void *context) |
| { |
| srpl_instance_service_t **hp = NULL, *service = context; |
| srpl_domain_t *domain = service->domain; |
| srpl_instance_t *instance = service->instance; |
| |
| // Retain for duration of function, since otherwise we might finalize it below. |
| |
| // This retain shouldn't be necessary if we are actually being called by the timeout, because the timeout holds a |
| // reference to service that can't be released below. However, this function can be called directly, outside of a |
| // timeout, and in that case we do need to retain service for the function. |
| |
| RETAIN_HERE(service, srpl_instance_service); |
| |
| // Remove the service from either the unresolved_services list or resolved instance list |
| if (instance == NULL) { |
| hp = &domain->unresolved_services; |
| } else { |
| RETAIN_HERE(instance, srpl_instance); // Retain instance for life of function in case we decrement its refcnt below. |
| hp = &instance->services; |
| } |
| for (; *hp; hp = &(*hp)->next) { |
| if (*hp == service) { |
| *hp = service->next; |
| RELEASE_HERE(service, srpl_instance_service); // Release service list's reference to instance_service. |
| break; |
| } |
| } |
| |
| if (service->discontinue_timeout != NULL) { |
| ioloop_cancel_wake_event(service->discontinue_timeout); |
| ioloop_wakeup_release(service->discontinue_timeout); |
| service->discontinue_timeout = NULL; |
| } |
| if (service->resolve_wakeup != NULL) { |
| ioloop_cancel_wake_event(service->resolve_wakeup); |
| ioloop_wakeup_release(service->resolve_wakeup); |
| service->resolve_wakeup = NULL; |
| } |
| if (service->address_query != NULL) { |
| address_query_cancel(service->address_query); |
| RELEASE_HERE(service->address_query, address_query); |
| service->address_query = NULL; |
| } |
| if (service->txt_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->txt_txn); |
| ioloop_dnssd_txn_release(service->txt_txn); |
| service->txt_txn = NULL; |
| service->resolve_started = false; |
| } |
| if (service->srv_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->srv_txn); |
| ioloop_dnssd_txn_release(service->srv_txn); |
| service->srv_txn = NULL; |
| service->resolve_started = false; |
| } |
| if (service->instance != NULL) { |
| RELEASE_HERE(service->instance, srpl_instance); |
| service->instance = NULL; |
| } |
| if (instance != NULL) { |
| if (instance->services == NULL) { |
| srpl_instance_discontinue_timeout(instance); |
| } |
| RELEASE_HERE(instance, srpl_instance); // Release this function's reference to instance |
| } |
| RELEASE_HERE(service, srpl_instance_service); // Release this functions reference to instance_service. |
| } |
| |
| static void |
| srpl_instance_services_discontinue(srpl_instance_t *instance) |
| { |
| srpl_instance_service_t *service; |
| for (service = instance->services; service != NULL; ) { |
| // The service is retained on the list, but... |
| srpl_instance_service_t *next = service->next; |
| // This is going to release it... |
| srpl_instance_service_discontinue_timeout(service); |
| // So next is still valid here, but service isn't. |
| service = next; |
| } |
| } |
| |
| static void |
| srpl_instance_service_discontinue(srpl_instance_service_t *service) |
| { |
| // Already discontinuing. |
| if (service->discontinuing) { |
| INFO("Replication service " PRI_S_SRP " went away, already discontinuing", service->full_service_name); |
| return; |
| } |
| if (service->num_copies > 0) { |
| INFO("Replication service " PRI_S_SRP " went away, %d still left", service->host_name, service->num_copies); |
| return; |
| } |
| INFO("Replication service " PRI_S_SRP " went away, none left, discontinuing", service->full_service_name); |
| service->discontinuing = true; |
| |
| // DNSServiceResolve doesn't give us the kDNSServiceFlagAdd flag--apparently it's assumed that we know the |
| // service was removed because we get a remove on the browse. So we need to restart the resolve if the |
| // instance comes back, rather than continuing to use the old resolve transaction. |
| if (service->txt_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->txt_txn); |
| ioloop_dnssd_txn_release(service->txt_txn); |
| service->txt_txn = NULL; |
| } |
| if (service->srv_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->srv_txn); |
| ioloop_dnssd_txn_release(service->srv_txn); |
| service->srv_txn = NULL; |
| } |
| service->resolve_started = false; |
| // It's not uncommon for a name to drop and then come back immediately. Wait 30s before |
| // discontinuing the instance host. |
| if (service->discontinue_timeout == NULL) { |
| service->discontinue_timeout = ioloop_wakeup_create(); |
| // Oh well. |
| if (service->discontinue_timeout == NULL) { |
| srpl_instance_service_discontinue_timeout(service); |
| return; |
| } |
| } |
| |
| RETAIN_HERE(service, srpl_instance_service); |
| ioloop_add_wake_event(service->discontinue_timeout, service, srpl_instance_service_discontinue_timeout, |
| srpl_instance_service_context_release, 30 * 1000); |
| } |
| |
| |
| static void |
| srpl_instance_discontinue(srpl_instance_t *instance) |
| { |
| srpl_instance_service_t *service; |
| instance->discontinuing = true; |
| for (service = instance->services; service != NULL; service = service->next) { |
| srpl_instance_service_discontinue(service); |
| } |
| } |
| |
| void |
| srpl_shutdown(srp_server_t *server_state) |
| { |
| srpl_instance_t *instance, *next; |
| srpl_instance_service_t *service, *next_service; |
| |
| if (server_state->current_thread_domain_name == NULL) { |
| INFO("no current domain"); |
| return; |
| } |
| for (srpl_domain_t **dp = &server_state->srpl_domains; *dp != NULL; ) { |
| srpl_domain_t *domain = *dp; |
| if (!strcmp(domain->name, server_state->current_thread_domain_name)) { |
| for (instance = domain->instances; instance != NULL; instance = next) { |
| next = instance->next; |
| srpl_instance_services_discontinue(instance); |
| } |
| for (service = domain->unresolved_services; service != NULL; service = next_service) { |
| next_service = service->next; |
| srpl_instance_service_discontinue_timeout(service); |
| } |
| if (domain->query != NULL) { |
| ioloop_dnssd_txn_cancel(domain->query); |
| ioloop_dnssd_txn_release(domain->query); |
| domain->query = NULL; |
| } |
| if (domain->srpl_advertise_txn != NULL) { |
| ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn); |
| ioloop_dnssd_txn_release(domain->srpl_advertise_txn); |
| domain->srpl_advertise_txn = NULL; |
| } |
| if (domain->partner_discovery_timeout != NULL) { |
| ioloop_cancel_wake_event(domain->partner_discovery_timeout); |
| ioloop_wakeup_release(domain->partner_discovery_timeout); |
| domain->partner_discovery_timeout = NULL; |
| } |
| *dp = domain->next; |
| RELEASE_HERE(domain, srpl_domain); |
| free(server_state->current_thread_domain_name); |
| server_state->current_thread_domain_name = NULL; |
| } else { |
| dp = &(*dp)->next; |
| } |
| } |
| } |
| |
| void |
| srpl_disable(srp_server_t *server_state) |
| { |
| srpl_shutdown(server_state); |
| server_state->srp_replication_enabled = false; |
| } |
| |
| void |
| srpl_drop_srpl_connection(srp_server_t *NONNULL server_state) |
| { |
| for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL && instance->connection->state > srpl_state_disconnect_wait) { |
| srpl_connection_discontinue(instance->connection); |
| } |
| } |
| } |
| } |
| |
| void |
| srpl_undrop_srpl_connection(srp_server_t *NONNULL server_state) |
| { |
| for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| srpl_instance_reconnect(instance); |
| } |
| } |
| } |
| |
| // Stop service advertisement in the given domain. |
| static void |
| srpl_stop_srpl_domain_advertisement(srpl_domain_t *NONNULL domain) |
| { |
| if (domain->srpl_advertise_txn != NULL) { |
| ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn); |
| ioloop_dnssd_txn_release(domain->srpl_advertise_txn); |
| domain->srpl_advertise_txn = NULL; |
| } |
| } |
| |
| // Stop service advertisement in all the domains |
| void |
| srpl_drop_srpl_advertisement(srp_server_t *NONNULL server_state) |
| { |
| srpl_domain_t *domain; |
| for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| if (domain->srpl_advertise_txn != NULL) { |
| ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn); |
| ioloop_dnssd_txn_release(domain->srpl_advertise_txn); |
| domain->srpl_advertise_txn = NULL; |
| } |
| } |
| } |
| |
| void |
| srpl_undrop_srpl_advertisement(srp_server_t *NONNULL server_state) |
| { |
| srpl_domain_t *domain; |
| for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| srpl_domain_advertise(domain); |
| } |
| } |
| |
| |
| // Copy from into to, and then NULL out the host pointer in from, which is not refcounted, so that we don't get a |
| // double free later. Add a reference to the message, since it is refcounted. |
| static void |
| srpl_host_update_steal_parts(srpl_host_update_t *to, srpl_host_update_t *from) |
| { |
| *to = *from; |
| from->hostname = NULL; |
| from->messages = NULL; |
| from->num_messages = from->max_messages = from->messages_processed = 0; |
| } |
| |
| static bool |
| srpl_event_content_type_set_(srpl_event_t *event, srpl_event_content_type_t content_type, const char *file, int line) |
| { |
| switch(event->content_type) { |
| case srpl_event_content_type_none: |
| case srpl_event_content_type_address: |
| case srpl_event_content_type_session: |
| case srpl_event_content_type_candidate_disposition: |
| case srpl_event_content_type_rcode: |
| case srpl_event_content_type_client_result: // pointers owned by caller |
| case srpl_event_content_type_advertise_finished_result: |
| break; |
| |
| case srpl_event_content_type_candidate: |
| if (event->content.candidate != NULL) { |
| srpl_candidate_free_(event->content.candidate, file, line); |
| event->content.candidate = NULL; |
| } |
| break; |
| case srpl_event_content_type_host_update: |
| srpl_host_update_parts_free(&event->content.host_update); |
| break; |
| } |
| memset(&event->content, 0, sizeof(event->content)); |
| if (content_type == srpl_event_content_type_candidate) { |
| event->content.candidate = calloc(1, sizeof(srpl_candidate_t)); |
| if (event->content.candidate == NULL) { |
| return false; |
| } |
| } |
| event->content_type = content_type; |
| return true; |
| } |
| |
| static void |
| srpl_disconnected_callback(comm_t *comm, void *context, int UNUSED error) |
| { |
| srpl_connection_t *srpl_connection = context; |
| srpl_domain_t *domain; |
| |
| // No matter what state we are in, if we are disconnected, we can't continue with the existing connection. |
| // Either we need to make a new connection, or go idle. |
| |
| srpl_instance_t *instance = srpl_connection->instance; |
| |
| // The connection would still be holding a reference; hold a reference to the connection to avoid it being released |
| // prematurely. |
| RETAIN_HERE(srpl_connection, srpl_connection); |
| |
| // Get rid of the comm_t connection object if it's still around |
| if (srpl_connection->connection != NULL && srpl_connection->connection == comm) { |
| comm_t *connection = srpl_connection->connection; |
| srpl_connection->connection = NULL; |
| ioloop_comm_release(connection); |
| |
| if (srpl_connection->dso != NULL) { |
| dso_state_cancel(srpl_connection->dso); |
| srpl_connection->dso = NULL; |
| } |
| } |
| |
| // If there's no instance, this connection just needs to go away (and presumably has). |
| if (instance == NULL) { |
| INFO("the instance is NULL."); |
| goto out; |
| } |
| |
| // Because instance is still holding a reference to srpl_connection, it's safe to keep using srpl_connection. |
| |
| // Clear old data from connection. |
| srpl_connection_reset(srpl_connection); |
| |
| // If the connection is in the disconnect_wait state, deliver an event. |
| if (srpl_connection->state == srpl_state_disconnect_wait) { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_disconnected); |
| srpl_event_deliver(srpl_connection, &event); |
| goto out; |
| } |
| |
| domain = instance->domain; |
| if (domain == NULL) { |
| // If domain is NULL, instance has been discontinued. |
| INFO(PRI_S_SRP "instance was discontinued, not reconnecting.", instance->instance_name); |
| } else { |
| // If we are in the startup state, we should reinitiate the connection to the peer. |
| // Otherwise, we should reconnect only if our partner id is greater than the peer's. |
| // If there's no partner id on the instance, the instance should be a temporary one |
| // and that means we haven't discovered the peer yet, so we can just drop the connection |
| // and wait to discover it or for it to reconnect. |
| if (domain->srpl_opstate == SRPL_OPSTATE_STARTUP || |
| (instance->have_partner_id && domain->server_state != NULL && |
| domain->partner_id > instance->partner_id)) |
| { |
| INFO(PRI_S_SRP ": disconnect received, reconnecting.", srpl_connection->name); |
| srpl_connection_next_state(srpl_connection, srpl_state_next_address_get); |
| goto out; |
| } |
| } |
| |
| // If it's not our job to reconnect, we no longer need this connection. Release the reference |
| // held by the instance (which'd cause the connection to be finalized). |
| srpl_connection_next_state(srpl_connection, srpl_state_idle); |
| if (instance->connection == srpl_connection) { |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| |
| out: |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| } |
| |
| static bool |
| srpl_dso_message_setup(dso_state_t *dso, dso_message_t *state, dns_towire_state_t *towire, uint8_t *buffer, |
| size_t buffer_size, message_t *message, bool unidirectional, bool response, int rcode, |
| uint16_t xid, void *context) |
| { |
| uint16_t send_xid = 0; |
| |
| if (buffer_size < DNS_HEADER_SIZE) { |
| ERROR("internal: invalid buffer size %zd", buffer_size); |
| return false; |
| } |
| |
| if (response) { |
| if (message != NULL) { |
| send_xid = message->wire.id; |
| } else { |
| send_xid = xid; |
| } |
| } |
| dso_make_message(state, buffer, buffer_size, dso, unidirectional, response, |
| send_xid, rcode, context); |
| memset(towire, 0, sizeof(*towire)); |
| towire->p = &buffer[DNS_HEADER_SIZE]; |
| towire->lim = towire->p + (buffer_size - DNS_HEADER_SIZE); |
| towire->message = (dns_wire_t *)buffer; |
| return true; |
| } |
| |
| static srpl_domain_t * |
| srpl_connection_domain(srpl_connection_t *srpl_connection) |
| { |
| if (srpl_connection->instance == NULL) { |
| INFO("connection has no instance %p.", srpl_connection); |
| return NULL; |
| } |
| return srpl_connection->instance->domain; |
| } |
| |
| static bool |
| srpl_keepalive_send(srpl_connection_t *srpl_connection, bool response, uint16_t xid) |
| { |
| uint8_t dsobuf[SRPL_KEEPALIVE_MESSAGE_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| NULL, srpl_connection->dso->is_server, response, |
| dns_rcode_noerror, xid, srpl_connection)) { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_Keepalive); |
| dns_rdlength_begin(&towire); |
| dns_u32_to_wire(&towire, DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2); // Idle timeout (we are never idle) |
| dns_u32_to_wire(&towire, DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2); // Keepalive timeout |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO("sent %zd byte " PUB_S_SRP " Keepalive, xid %02x%02x (was %04x), to " PRI_S_SRP, iov.iov_len, |
| (response ? "response" : (srpl_connection->is_server |
| ? "unidirectional" |
| : "query")), dsobuf[0], dsobuf[1], xid, srpl_connection->name); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| // If we ever get a wakeup, it means that a wakeup send interval has passed since the last time we sent any message on |
| // this connection, so we should send a keepalive message. |
| static void |
| srpl_connection_keepalive_send_wakeup(void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| |
| // In case we lost our connection but still have keepalive timers going, now's a good time to |
| // cancel them. |
| if (srpl_connection->connection == NULL) { |
| // Cancel keepalive timers |
| if (srpl_connection->keepalive_send_wakeup) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_send_wakeup); |
| } |
| if (srpl_connection->keepalive_receive_wakeup) { |
| ioloop_cancel_wake_event(srpl_connection->keepalive_receive_wakeup); |
| } |
| return; |
| } |
| srpl_keepalive_send(srpl_connection, false, 0); |
| srpl_message_sent(srpl_connection); |
| } |
| |
| static void |
| srpl_message_sent(srpl_connection_t *srpl_connection) |
| { |
| if (!srpl_connection->is_server) { |
| srpl_connection->last_message_sent = srp_time(); |
| ioloop_add_wake_event(srpl_connection->keepalive_send_wakeup, |
| srpl_connection, srpl_connection_keepalive_send_wakeup, srpl_connection_context_release, |
| srpl_connection->keepalive_interval); |
| RETAIN_HERE(srpl_connection, srpl_connection); // for the callback |
| } |
| } |
| |
| static bool |
| srpl_session_message_send(srpl_connection_t *srpl_connection, bool response) |
| { |
| uint8_t dsobuf[SRPL_SESSION_MESSAGE_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| if (domain == NULL) { |
| return false; |
| } |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| srpl_connection_message_get(srpl_connection), false, response, 0, 0, srpl_connection)) { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLSession); |
| dns_rdlength_begin(&towire); |
| dns_u64_to_wire(&towire, domain->partner_id); |
| dns_rdlength_end(&towire); |
| |
| // version TLV |
| dns_u16_to_wire(&towire, kDSOType_SRPLVersion); |
| dns_rdlength_begin(&towire); |
| dns_u16_to_wire(&towire, SRPL_CURRENT_VERSION); |
| dns_rdlength_end(&towire); |
| |
| // domain name tlv |
| dns_u16_to_wire(&towire, kDSOType_SRPLDomainName); |
| dns_rdlength_begin(&towire); |
| INFO("include domain " PRI_S_SRP, domain->name); |
| dns_full_name_to_wire(NULL, &towire, domain->name); |
| dns_rdlength_end(&towire); |
| if (domain->srpl_opstate == SRPL_OPSTATE_STARTUP) { |
| // new partner TLV |
| dns_u16_to_wire(&towire, kDSOType_SRPLNewPartner); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| } |
| |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent SRPLSession " PUB_S_SRP ", id %" PRIx64, srpl_connection->name, |
| response ? "response" : "message", domain->partner_id); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| static bool |
| srpl_send_candidates_message_send(srpl_connection_t *srpl_connection, bool response) |
| { |
| uint8_t dsobuf[SRPL_SEND_CANDIDATES_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| srpl_connection_message_get(srpl_connection), false, response, 0, 0, srpl_connection)) { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLSendCandidates); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent SRPLSendCandidates " PUB_S_SRP, srpl_connection->name, response ? "response" : "query"); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| static bool |
| srpl_candidate_message_send(srpl_connection_t *srpl_connection, adv_host_t *host) |
| { |
| uint8_t dsobuf[SRPL_CANDIDATE_MESSAGE_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| time_t update_time = host->update_time; |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, |
| dsobuf, sizeof(dsobuf), NULL, false, false, 0, 0, srpl_connection)) { |
| return false; |
| } |
| |
| // For testing, make the update time really wrong so that signature validation fails. This will not actually |
| // cause a failure unless the SRP requestor sends a time range, so really only useful for testing with the |
| // mDNSResponder srp-client, not with e.g. a Thread client. |
| if (host->server_state != NULL && host->server_state->break_srpl_time) { |
| INFO("breaking time: %lu -> %lu", (unsigned long)update_time, (unsigned long)(update_time - 1800)); |
| update_time -= 1800; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLCandidate); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| dns_u16_to_wire(&towire, kDSOType_SRPLHostname); |
| dns_rdlength_begin(&towire); |
| dns_full_name_to_wire(NULL, &towire, host->name); |
| dns_rdlength_end(&towire); |
| dns_u16_to_wire(&towire, kDSOType_SRPLTimeOffset); |
| dns_rdlength_begin(&towire); |
| dns_u32_to_wire(&towire, (uint32_t)(srp_time() - update_time)); |
| dns_rdlength_end(&towire); |
| dns_u16_to_wire(&towire, kDSOType_SRPLKeyID); |
| dns_rdlength_begin(&towire); |
| dns_u32_to_wire(&towire, host->key_id); |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent SRPLCandidate message on connection.", srpl_connection->name); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| static bool |
| srpl_candidate_response_send(srpl_connection_t *srpl_connection, dso_message_types_t response_type) |
| { |
| uint8_t dsobuf[SRPL_CANDIDATE_RESPONSE_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| srpl_connection_message_get(srpl_connection), false, true, 0, 0, srpl_connection)) { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLCandidate); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| dns_u16_to_wire(&towire, response_type); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent SRPLCandidate response on connection.", srpl_connection->name); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| // Qsort comparison function for message receipt times. |
| static int |
| srpl_message_compare(const void *v1, const void *v2) |
| { |
| const message_t *m1 = v1, *m2 = v2; |
| if (m1->received_time - m2->received_time < 0) { |
| return -1; |
| } else if(m1->received_time - m2->received_time > 0) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static bool |
| srpl_host_message_send(srpl_connection_t *srpl_connection, adv_host_t *host) |
| { |
| uint8_t *dsobuf = NULL; |
| size_t dsobuf_length = SRPL_HOST_MESSAGE_LENGTH; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec *iov = NULL; |
| int num_messages; // Number of SRP updates we need to send |
| int iovec_count = 1, iov_cur = 0; |
| message_t **messages = NULL; |
| bool rv = false; |
| |
| if (host->message == NULL) { |
| FAULT("no host message to send for " PRI_S_SRP " on " PRI_S_SRP ".", host->name, srpl_connection->name); |
| goto out; |
| } |
| iovec_count++; |
| num_messages = 1; |
| if (SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) { |
| for (int i = 0; i < host->instances->num; i++) { |
| adv_instance_t *instance = host->instances->vec[i]; |
| if (instance != NULL) { |
| if (instance->message != NULL && instance->message != host->message && instance->message != NULL) { |
| num_messages++; |
| iovec_count += 2; |
| // Account for additional HostMessage TLV. |
| dsobuf_length += DSO_TLV_HEADER_SIZE + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t); |
| } |
| } |
| } |
| messages = calloc(1, sizeof (*messages)); |
| if (messages == NULL) { |
| INFO("no memory for message vector"); |
| goto out; |
| } |
| messages[0] = host->message; |
| num_messages = 1; |
| for (int i = 0; i < host->instances->num; i++) { |
| adv_instance_t *instance = host->instances->vec[i]; |
| if (instance != NULL) { |
| if (instance->message != NULL && instance->message != host->message && instance->message != NULL) { |
| messages[num_messages] = instance->message; |
| } |
| } |
| } |
| qsort(messages, num_messages, sizeof(*messages), srpl_message_compare); |
| } |
| iov = calloc(iovec_count, sizeof(*iov)); |
| if (iov == NULL) { |
| ERROR("no memory for iovec."); |
| goto out; |
| } |
| dsobuf = malloc(dsobuf_length); |
| if (dsobuf == NULL) { |
| ERROR("no memory for dso buffer"); |
| goto out; |
| } |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, |
| dsobuf, dsobuf_length, NULL, false, false, 0, 0, srpl_connection)) { |
| goto out; |
| } |
| |
| time_t srpl_now = srp_time(); |
| |
| // For testing, make the update time really wrong so that signature validation fails. This will not actually |
| // cause a failure unless the SRP requestor sends a time range, so really only useful for testing with the |
| // mDNSResponder srp-client, not with e.g. a Thread client. |
| if (host->server_state != NULL && host->server_state->break_srpl_time) { |
| INFO("breaking time: %lu -> %lu", (unsigned long)srpl_now, (unsigned long)(srpl_now + 1800)); |
| srpl_now += 1800; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLHost); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| dns_u16_to_wire(&towire, kDSOType_SRPLHostname); |
| dns_rdlength_begin(&towire); |
| dns_full_name_to_wire(NULL, &towire, host->name); |
| dns_rdlength_end(&towire); |
| // v0 of the protocol only includes one host message option, and timeoffset is sent |
| // as its own secondary TLV. |
| if (!SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) { |
| dns_u16_to_wire(&towire, kDSOType_SRPLTimeOffset); |
| dns_rdlength_begin(&towire); |
| dns_u32_to_wire(&towire, (uint32_t)(srpl_now - host->message->received_time)); |
| dns_rdlength_end(&towire); |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLServerStableID); |
| dns_rdlength_begin(&towire); |
| dns_u64_to_wire(&towire, host->server_stable_id); |
| dns_rdlength_end(&towire); |
| if (!SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) { |
| dns_u16_to_wire(&towire, kDSOType_SRPLHostMessage); |
| dns_u16_to_wire(&towire, host->message->length); |
| iov[iov_cur].iov_len = towire.p - dsobuf; |
| iov[iov_cur].iov_base = dsobuf; |
| iov_cur++; |
| iov[iov_cur].iov_len = host->message->length; |
| iov[iov_cur].iov_base = &host->message->wire; |
| iov_cur++; |
| } else { |
| for (int i = 0; i < num_messages; i++) { |
| uint8_t *start = towire.p; |
| dns_u16_to_wire(&towire, kDSOType_SRPLHostMessage); |
| dns_u16_to_wire(&towire, 12 + messages[i]->length); |
| dns_u32_to_wire(&towire, messages[i]->lease); |
| dns_u32_to_wire(&towire, messages[i]->key_lease); |
| dns_u32_to_wire(&towire, (uint32_t)(srpl_now - messages[i]->received_time)); |
| iov[iov_cur].iov_len = towire.p - start; |
| iov[iov_cur].iov_base = start; |
| iov_cur++; |
| iov[iov_cur].iov_len = messages[i]->length; |
| iov[iov_cur].iov_base = &messages[i]->wire; |
| iov_cur++; |
| } |
| } |
| |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| goto out; |
| } |
| |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), iov, iov_cur)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent SRPLHost message %02x%02x " PRI_S_SRP " stable ID %" PRIx64 ", Host Message Count %d", |
| srpl_connection->name, message.buf[0], message.buf[1], host->name, host->server_stable_id, num_messages); |
| rv = true; |
| srpl_message_sent(srpl_connection); |
| out: |
| if (messages != NULL) { |
| free(messages); |
| } |
| if (iov != NULL) { |
| free(iov); |
| } |
| if (dsobuf != NULL) { |
| free(dsobuf); |
| } |
| return rv; |
| } |
| |
| |
| static bool |
| srpl_host_response_send(srpl_connection_t *srpl_connection, int rcode) |
| { |
| uint8_t dsobuf[SRPL_HOST_RESPONSE_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| srpl_connection_message_get(srpl_connection), false, true, rcode, 0, srpl_connection)) { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_SRPLHost); |
| dns_rdlength_begin(&towire); |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| INFO(PRI_S_SRP " sent SRPLHost response %02x%02x rcode %d on connection.", |
| srpl_connection->name, message.buf[0], message.buf[1], rcode); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| |
| static bool |
| srpl_retry_delay_send(srpl_connection_t *srpl_connection, uint32_t delay) |
| { |
| uint8_t dsobuf[SRPL_RETRY_DELAY_LENGTH]; |
| dns_towire_state_t towire; |
| dso_message_t message; |
| struct iovec iov; |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| if (domain == NULL) { |
| ERROR("domain is NULL."); |
| return false; |
| } |
| |
| // If this isn't a server, there's no benefit to sending retry delay. |
| if (!srpl_connection->is_server) { |
| return true; |
| } |
| |
| if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf), |
| srpl_connection_message_get(srpl_connection), false, true, dns_rcode_noerror, 0, |
| srpl_connection)) |
| { |
| return false; |
| } |
| dns_u16_to_wire(&towire, kDSOType_RetryDelay); |
| dns_rdlength_begin(&towire); |
| dns_u32_to_wire(&towire, delay); // One hour. |
| dns_rdlength_end(&towire); |
| if (towire.error) { |
| ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line); |
| return false; |
| } |
| memset(&iov, 0, sizeof(iov)); |
| iov.iov_len = towire.p - dsobuf; |
| iov.iov_base = dsobuf; |
| if (!ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1)) { |
| INFO("send failed"); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| |
| INFO(PRI_S_SRP " sent Retry Delay, id %" PRIx64, srpl_connection->name, domain->partner_id); |
| srpl_message_sent(srpl_connection); |
| return true; |
| } |
| static bool |
| srpl_find_dso_additionals(srpl_connection_t *srpl_connection, dso_state_t *dso, const dso_message_types_t *additionals, |
| bool *required, bool *multiple, const char **names, int *indices, int num, |
| int min_additls, int max_additls, const char *message_name, void *context, |
| bool (*iterator)(int index, const uint8_t *buf, unsigned *offp, uint16_t len, void *context)) |
| { |
| int ret = true; |
| int count = 0; |
| |
| for (int j = 0; j < num; j++) { |
| indices[j] = -1; |
| } |
| for (unsigned i = 0; i < dso->num_additls; i++) { |
| bool found = false; |
| for (int j = 0; j < num; j++) { |
| if (dso->additl[i].opcode == additionals[j]) { |
| if (indices[j] != -1 && (multiple == NULL || multiple[j] == false)) { |
| ERROR(PRI_S_SRP ": duplicate " PUB_S_SRP " for " PUB_S_SRP ".", |
| srpl_connection->name, names[j], message_name); |
| ret = false; |
| continue; |
| } |
| indices[j] = i; |
| unsigned offp = 0; |
| if (!iterator(j, dso->additl[i].payload, &offp, dso->additl[i].length, context) || |
| offp != dso->additl[i].length) |
| { |
| ERROR(PRI_S_SRP ": invalid " PUB_S_SRP " for " PUB_S_SRP ".", |
| srpl_connection->name, names[j], message_name); |
| found = true; // So we don't complain later. |
| count++; |
| ret = false; |
| } else { |
| found = true; |
| count++; |
| } |
| } |
| } |
| if (!found) { |
| ERROR(PRI_S_SRP ": unexpected opcode %x for " PUB_S_SRP ".", |
| srpl_connection->name, dso->additl[i].opcode, message_name); |
| } |
| } |
| for (int j = 0; j < num; j++) { |
| if (required[j] && indices[j] == -1) { |
| ERROR(PRI_S_SRP ": missing " PUB_S_SRP " for " PUB_S_SRP ".", |
| srpl_connection->name, names[j], message_name); |
| ret = false; |
| } |
| } |
| if (count < min_additls) { |
| ERROR(PRI_S_SRP ": not enough additional TLVs (%d) for " PUB_S_SRP ".", |
| srpl_connection->name, count, message_name); |
| ret = false; |
| } else if (count > max_additls) { |
| ERROR(PRI_S_SRP ": too many additional TLVs (%d) for " PUB_S_SRP ".", |
| srpl_connection->name, count, message_name); |
| ret = false; |
| } |
| return ret; |
| } |
| |
| static void |
| srpl_connection_discontinue(srpl_connection_t *srpl_connection) |
| { |
| srpl_connection->candidates_not_generated = true; |
| // Cancel any outstanding reconnect wakeup event, so that we don't accidentally restart the connection we decided to |
| // discontinue. |
| if (srpl_connection->reconnect_wakeup != NULL) { |
| ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup); |
| // We have to get rid of the wakeup here because it's holding a reference to the connection, which we may want to |
| // have go away. |
| ioloop_wakeup_release(srpl_connection->reconnect_wakeup); |
| srpl_connection->reconnect_wakeup = NULL; |
| } |
| srpl_connection_reset(srpl_connection); |
| srpl_connection_next_state(srpl_connection, srpl_state_disconnect); |
| } |
| |
| static bool |
| srpl_session_message_parse_in(int index, const uint8_t *buffer, unsigned *offp, uint16_t length, void *context) |
| { |
| srpl_session_t *session = context; |
| |
| switch(index) { |
| case 0: |
| session->new_partner = true; |
| return true; |
| case 1: |
| return dns_name_parse(&session->domain_name, buffer, length, offp, length); |
| case 2: |
| return dns_u16_parse(buffer, length, offp, &session->remote_version); |
| } |
| return false; |
| } |
| |
| static bool |
| srpl_session_message_parse(srpl_connection_t *srpl_connection, |
| srpl_event_t *event, dso_state_t *dso, const char *message_name) |
| { |
| const char *names[3] = { "New Partner", "Domain Name", "Protocol Version" }; |
| dso_message_types_t additionals[3] = { kDSOType_SRPLNewPartner, kDSOType_SRPLDomainName, kDSOType_SRPLVersion }; |
| bool required[3] = { false, false, false }; |
| bool multiple[3] = { false, false, false }; |
| int indices[3]; |
| |
| if (dso->primary.length != 8) { |
| ERROR(PRI_S_SRP ": invalid DSO Primary length %d for " PUB_S_SRP ".", |
| srpl_connection->name, dso->primary.length, message_name); |
| return false; |
| } |
| |
| unsigned offp = 0; |
| srpl_event_content_type_set(event, srpl_event_content_type_session); |
| if (!dns_u64_parse(dso->primary.payload, 8, &offp, &event->content.session.partner_id)) { |
| // This should be un-possible. |
| ERROR(PRI_S_SRP ": invalid DSO Primary server id in " PRI_S_SRP ".", |
| srpl_connection->name, message_name); |
| return false; |
| } |
| |
| event->content.session.new_partner = false; |
| if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, required, multiple, names, indices, 3, 0, 3, |
| "SRPLSession message", &(event->content.session), srpl_session_message_parse_in)) { |
| return false; |
| } |
| |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| if (domain == NULL) { |
| ERROR("connection has no domain."); |
| return false; |
| } |
| // If this is an unidentified connection that is associated with a temporary instance and |
| // a temporary domain, we need to retrieve the domain name from the session message and |
| // find the real domain for this connection. |
| // A connection can not be identified due to either |
| // the sending partner is still in the startup state and has not advertised yet; or |
| // the sending partner is in the routine state and has advertised the domain, but |
| // the receiving partner has not discovered it yet. |
| DNS_NAME_GEN_SRP(event->content.session.domain_name, dname_buf); |
| if (domain->name == NULL) { |
| srp_server_t *server_state = domain->server_state; |
| if (server_state == NULL) { |
| ERROR("server state is NULL."); |
| return false; |
| } |
| if (event->content.session.domain_name == NULL) { |
| ERROR(PUB_S_SRP " does not include domain name", message_name); |
| return false; |
| } |
| srpl_domain_t **dp, *match_domain = NULL; |
| // Find the domain. |
| for (dp = &server_state->srpl_domains; *dp; dp = &(*dp)->next) { |
| match_domain = *dp; |
| if (!strcasecmp(match_domain->name, dname_buf)) { |
| break; |
| } |
| } |
| if (match_domain == NULL) { |
| ERROR("domain name in " PUB_S_SRP " does not match any domain", message_name); |
| return false; |
| } |
| RELEASE_HERE(srpl_connection->instance->domain, srpl_domain); |
| srpl_connection->instance->domain = match_domain; |
| RETAIN_HERE(match_domain, srpl_domain); |
| } |
| |
| |
| if (event->content.session.remote_version >= SRPL_VERSION_MULTI_HOST_MESSAGE) { |
| srpl_connection->variation_mask |= SRPL_VARIATION_MULTI_HOST_MESSAGE; |
| } |
| |
| INFO(PRI_S_SRP " received " PUB_S_SRP ", id %" PRIx64 ", startup " PUB_S_SRP |
| ", domain " PRI_S_SRP ", version %d", srpl_connection->name, message_name, |
| event->content.session.partner_id, event->content.session.new_partner? "yes" : "no", |
| dname_buf, event->content.session.remote_version); |
| return true; |
| } |
| |
| static void |
| srpl_session_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso) |
| { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_session_message_received); |
| |
| srpl_connection_message_set(srpl_connection, message); |
| if (!srpl_session_message_parse(srpl_connection, &event, dso, "SRPLSession message")) { |
| srpl_disconnect(srpl_connection); |
| return; |
| } |
| srpl_event_deliver(srpl_connection, &event); |
| dns_name_free(event.content.session.domain_name); |
| } |
| |
| static void |
| srpl_session_response(srpl_connection_t *srpl_connection, dso_state_t *dso) |
| { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_session_response_received); |
| if (!srpl_session_message_parse(srpl_connection, &event, dso, "SRPLSession response")) { |
| srpl_disconnect(srpl_connection); |
| return; |
| } |
| srpl_event_deliver(srpl_connection, &event); |
| dns_name_free(event.content.session.domain_name); |
| } |
| |
| static bool |
| srpl_send_candidates_message_parse(srpl_connection_t *srpl_connection, dso_state_t *dso, const char *message_name) |
| { |
| if (dso->primary.length != 0) { |
| ERROR(PRI_S_SRP ": invalid DSO Primary length %d for " PUB_S_SRP ".", |
| srpl_connection->name, dso->primary.length, message_name); |
| srpl_disconnect(srpl_connection); |
| return false; |
| } |
| return true; |
| } |
| |
| static void |
| srpl_send_candidates_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso) |
| { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_send_candidates_message_received); |
| |
| srpl_connection_message_set(srpl_connection, message); |
| if (srpl_send_candidates_message_parse(srpl_connection, dso, "SRPLSendCandidates message")) { |
| INFO(PRI_S_SRP " received SRPLSendCandidates query", srpl_connection->name); |
| |
| srpl_event_deliver(srpl_connection, &event); |
| return; |
| } |
| srpl_disconnect(srpl_connection); |
| } |
| |
| static void |
| srpl_send_candidates_response(srpl_connection_t *srpl_connection, dso_state_t *dso) |
| { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_send_candidates_response_received); |
| |
| if (srpl_send_candidates_message_parse(srpl_connection, dso, "SRPLSendCandidates message")) { |
| INFO(PRI_S_SRP " received SRPLSendCandidates response", srpl_connection->name); |
| srpl_event_deliver(srpl_connection, &event); |
| return; |
| } |
| } |
| |
| static bool |
| srpl_candidate_message_parse_in(int index, const uint8_t *buffer, unsigned *offp, uint16_t length, void *context) |
| { |
| srpl_candidate_t *candidate = context; |
| |
| switch(index) { |
| case 0: |
| return dns_name_parse(&candidate->name, buffer, length, offp, length); |
| case 1: |
| return dns_u32_parse(buffer, length, offp, &candidate->update_offset); |
| case 2: |
| return dns_u32_parse(buffer, length, offp, &candidate->key_id); |
| } |
| return false; |
| } |
| |
| static void |
| srpl_candidate_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso) |
| { |
| const char *names[3] = { "Candidate Name", "Time Offset", "Key ID" }; |
| dso_message_types_t additionals[3] = { kDSOType_SRPLHostname, kDSOType_SRPLTimeOffset, kDSOType_SRPLKeyID }; |
| bool required[3] = { true, true, true }; |
| int indices[3]; |
| |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_candidate_received); |
| srpl_connection_message_set(srpl_connection, message); |
| if (!srpl_event_content_type_set(&event, srpl_event_content_type_candidate) || |
| !srpl_find_dso_additionals(srpl_connection, dso, additionals, |
| required, NULL, names, indices, 3, 3, 3, "SRPLCandidate message", |
| event.content.candidate, srpl_candidate_message_parse_in)) { |
| goto fail; |
| } |
| |
| event.content.candidate->update_time = srp_time() - event.content.candidate->update_offset; |
| srpl_event_deliver(srpl_connection, &event); |
| srpl_event_content_type_set(&event, srpl_event_content_type_none); |
| return; |
| |
| fail: |
| srpl_disconnect(srpl_connection); |
| } |
| |
| static bool |
| srpl_candidate_response_parse_in(int index, |
| const uint8_t *UNUSED buffer, unsigned *offp, uint16_t length, void *context) |
| { |
| srpl_candidate_disposition_t *candidate_disposition = context; |
| |
| if (length != 0) { |
| return false; |
| } |
| |
| switch(index) { |
| case 0: |
| *candidate_disposition = srpl_candidate_yes; |
| break; |
| case 1: |
| *candidate_disposition = srpl_candidate_no; |
| break; |
| case 2: |
| *candidate_disposition = srpl_candidate_conflict; |
| break; |
| } |
| *offp = 0; |
| return true; |
| } |
| |
| static void |
| srpl_candidate_response(srpl_connection_t *srpl_connection, dso_state_t *dso) |
| { |
| const char *names[3] = { "Candidate Yes", "Candidate No", "Conflict" }; |
| dso_message_types_t additionals[3] = { kDSOType_SRPLCandidateYes, kDSOType_SRPLCandidateNo, kDSOType_SRPLConflict }; |
| bool required[3] = { false, false, false }; |
| int indices[3]; |
| srpl_event_t event; |
| |
| srpl_event_initialize(&event, srpl_event_candidate_response_received); |
| srpl_event_content_type_set(&event, srpl_event_content_type_candidate_disposition); |
| if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, |
| required, NULL, names, indices, 3, 1, 1, "SRPLCandidate reply", |
| &event.content.disposition, srpl_candidate_response_parse_in)) { |
| goto fail; |
| } |
| srpl_event_deliver(srpl_connection, &event); |
| return; |
| |
| fail: |
| srpl_disconnect(srpl_connection); |
| } |
| |
| static bool |
| srpl_host_message_parse_in(int index, const uint8_t *buffer, unsigned *offp, uint16_t length, void *context) |
| { |
| srpl_host_update_t *update = context; |
| |
| switch(index) { |
| case 0: // Host Name |
| if (update->hostname == NULL) { |
| unsigned offp_orig = *offp; |
| bool ret = dns_name_parse(&update->hostname, buffer, length, offp, length); |
| update->num_bytes = *offp - offp_orig; |
| update->orig_buffer = (intptr_t)buffer; |
| return ret; |
| } else { |
| if ((intptr_t)buffer == update->orig_buffer) { |
| (*offp) += update->num_bytes; |
| } |
| return true; |
| } |
| case 1: // Host Message |
| if (update->messages != NULL) { |
| const uint8_t *message_buffer; |
| size_t message_length; |
| if (update->rcode) { |
| message_buffer = buffer + 24; // lease, key-lease, time offset |
| message_length = length - 24; |
| } else { |
| message_buffer = buffer; |
| message_length = length; |
| } |
| message_t *message = ioloop_message_create(message_length); |
| if (message == NULL) { |
| return false; |
| } |
| if (update->rcode) { |
| uint32_t time_offset = 0; |
| if (!(dns_u32_parse(buffer, length, offp, &message->lease) && |
| dns_u32_parse(buffer, length, offp, &message->key_lease) && |
| dns_u32_parse(buffer, length, offp, &time_offset))) |
| { |
| return false; |
| } |
| message->received_time = srp_time() - time_offset; |
| } |
| memcpy(&message->wire, message_buffer, message_length); |
| |
| // We are parsing across the same message, so we can't exceed max_messages here. |
| update->messages[update->num_messages++] = message; |
| } else { |
| update->max_messages++; |
| } |
| *offp = length; |
| return true; |
| case 2: // Server Stable ID |
| return dns_u64_parse(buffer, length, offp, &update->server_stable_id); |
| case 3: // Time Offset |
| return dns_u32_parse(buffer, length, offp, &update->update_offset); |
| } |
| return false; |
| } |
| |
| static void |
| srpl_host_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso) |
| { |
| srpl_event_t event; |
| memset(&event, 0, sizeof(event)); |
| srpl_connection_message_set(srpl_connection, message); |
| if (dso->primary.length != 0) { |
| ERROR(PRI_S_SRP ": invalid DSO Primary length %d for SRPLHost message.", |
| srpl_connection->name, dso->primary.length); |
| goto fail; |
| } else { |
| const char *names[4] = { "Host Name", "Host Message", "Server Stable ID", "Time Offset" }; |
| dso_message_types_t additionals[4] = { kDSOType_SRPLHostname, kDSOType_SRPLHostMessage, |
| kDSOType_SRPLServerStableID, kDSOType_SRPLTimeOffset }; |
| bool required[4] = { true, true, false, true }; |
| bool multiple[4] = { false, true, false, false }; |
| int indices[4]; |
| int num_additls = 4; |
| |
| // Parse host message |
| srpl_event_initialize(&event, srpl_event_host_message_received); |
| srpl_event_content_type_set(&event, srpl_event_content_type_host_update); |
| if (SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) { |
| num_additls--; |
| event.content.host_update.rcode = 1; // temporarily use rcode for flag |
| } |
| |
| if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, required, multiple, names, indices, |
| num_additls, num_additls - 1, num_additls, "SRPLHost message", |
| &event.content.host_update, srpl_host_message_parse_in)) { |
| goto fail; |
| } |
| // update->max_messages can't be zero here, or we would have gotten a false return from |
| // srpl_find_dso_additionals and not gotten here. |
| event.content.host_update.messages = calloc(event.content.host_update.max_messages, |
| sizeof (*event.content.host_update.messages)); |
| if (event.content.host_update.messages == NULL) { |
| goto fail; |
| } |
| // Now that we know how many messages, we can copy them out. |
| if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, required, multiple, names, indices, |
| num_additls, num_additls - 1, num_additls, "SRPLHost message", |
| &event.content.host_update, srpl_host_message_parse_in)) { |
| goto fail; |
| } |
| DNS_NAME_GEN_SRP(event.content.host_update.hostname, hostname_buf); |
| INFO(PRI_S_SRP " received SRPLHost message %x for " PRI_DNS_NAME_SRP " server stable ID %" PRIx64, |
| srpl_connection->name, ntohs(message->wire.id), |
| DNS_NAME_PARAM_SRP(event.content.host_update.hostname, hostname_buf), |
| event.content.host_update.server_stable_id); |
| if (!SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) { |
| time_t update_time = srp_time() - event.content.host_update.update_offset; |
| event.content.host_update.messages[0]->received_time = update_time; |
| } else { |
| // Make sure times are sequential. |
| time_t last_received_time = event.content.host_update.messages[0]->received_time; |
| for (int i = 0; i < event.content.host_update.num_messages; i++) { |
| time_t cur_received_time = event.content.host_update.messages[i]->received_time; |
| if (cur_received_time - last_received_time <= 0) { |
| INFO(PRI_S_SRP |
| " received invalid SRPLHost message %x with message %d time %lld <= message %d time %lld", |
| srpl_connection->name, ntohs(event.content.host_update.messages[i]->wire.id), |
| i, (long long)cur_received_time, i - 1, (long long)last_received_time); |
| goto fail_no_message; |
| } |
| last_received_time = cur_received_time; |
| } |
| } |
| event.content.host_update.rcode = 0; |
| srpl_event_deliver(srpl_connection, &event); |
| srpl_event_content_type_set(&event, srpl_event_content_type_none); |
| } |
| return; |
| fail: |
| INFO(PRI_S_SRP " received invalid SRPLHost message %x", srpl_connection->name, ntohs(message->wire.id)); |
| fail_no_message: |
| if (event.content_type == srpl_event_content_type_host_update) { |
| srpl_event_content_type_set(&event, srpl_event_content_type_none); |
| } |
| srpl_disconnect(srpl_connection); |
| return; |
| } |
| |
| static void |
| srpl_host_response(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso) |
| { |
| if (dso->primary.length != 0) { |
| ERROR(PRI_S_SRP ": invalid DSO Primary length %d for SRPLHost response.", |
| srpl_connection->name, dso->primary.length); |
| srpl_disconnect(srpl_connection); |
| return; |
| } else { |
| srpl_event_t event; |
| INFO(PRI_S_SRP " received SRPLHost response %x", srpl_connection->name, ntohs(message->wire.id)); |
| srpl_event_initialize(&event, srpl_event_host_response_received); |
| srpl_event_content_type_set(&event, srpl_event_content_type_rcode); |
| event.content.rcode = dns_rcode_get(&message->wire); |
| srpl_event_deliver(srpl_connection, &event); |
| srpl_event_content_type_set(&event, srpl_event_content_type_none); |
| return; |
| } |
| } |
| |
| static void |
| srpl_keepalive_receive(srpl_connection_t *srpl_connection, int keepalive_interval, uint16_t xid) |
| { |
| if (srpl_connection->is_server) { |
| int num_standby = 0; |
| for (srpl_instance_t *unmatched = unmatched_instances; unmatched != NULL; unmatched = unmatched->next) { |
| srpl_connection_t *unidentified = unmatched->connection; |
| if (unidentified != NULL && unidentified->state > srpl_state_session_evaluate) { |
| num_standby++; |
| } |
| } |
| int new_interval = num_standby * DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2; |
| if (new_interval > 0 && srpl_connection->keepalive_interval != new_interval) { |
| srpl_connection->keepalive_interval = new_interval; |
| INFO("suggest keepalive %d for connection " PRI_S_SRP, new_interval, srpl_connection->name); |
| } |
| srpl_keepalive_send(srpl_connection, true, xid); |
| } else { |
| if (srpl_connection->state <= srpl_state_sync_wait) { |
| INFO("keepalive for connection " PRI_S_SRP " - old %d, new %d.", srpl_connection->name, |
| srpl_connection->keepalive_interval, keepalive_interval); |
| srpl_connection->keepalive_interval = keepalive_interval; |
| } |
| } |
| } |
| |
| static void |
| srpl_dso_retry_delay(srpl_connection_t *srpl_connection, int reconnect_delay) |
| { |
| if (srpl_connection->instance == NULL) { |
| // If there's no instance, we're already disconnecting. |
| INFO(PRI_S_SRP ": no instance", srpl_connection->name); |
| return; |
| } |
| srpl_instance_t *instance = srpl_connection->instance; |
| RETAIN_HERE(srpl_connection, srpl_connection); // In case there's only one reference left. |
| if (instance->unmatched) { |
| INFO(PRI_S_SRP ": not sending retry delay for %d seconds because unidentified", srpl_connection->name, reconnect_delay); |
| if (instance->connection == srpl_connection) { |
| RELEASE_HERE(instance->connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| } else { |
| INFO(PRI_S_SRP ": sending retry delay for %d seconds", srpl_connection->name, reconnect_delay); |
| |
| // Set things up to reconnect later. |
| srpl_connection_drop_state_delay(instance, srpl_connection, reconnect_delay); |
| } |
| |
| // Drop the connection |
| srpl_connection_discontinue(srpl_connection); |
| RELEASE_HERE(srpl_connection, srpl_connection); // For the function. |
| } |
| |
| static void |
| srpl_dso_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso, bool response) |
| { |
| const char *name = "<null>"; |
| if (srpl_connection != NULL) { |
| if (srpl_connection->instance != NULL) { |
| name = srpl_connection->instance->instance_name; |
| } else { |
| name = srpl_connection->name; |
| } |
| } |
| |
| switch(dso->primary.opcode) { |
| case kDSOType_SRPLSession: |
| if (response) { |
| srpl_session_response(srpl_connection, dso); |
| } else { |
| srpl_session_message(srpl_connection, message, dso); |
| } |
| break; |
| |
| case kDSOType_SRPLSendCandidates: |
| if (response) { |
| srpl_send_candidates_response(srpl_connection, dso); |
| } else { |
| srpl_send_candidates_message(srpl_connection, message, dso); |
| } |
| break; |
| |
| case kDSOType_SRPLCandidate: |
| if (response) { |
| srpl_candidate_response(srpl_connection, dso); |
| } else { |
| srpl_candidate_message(srpl_connection, message, dso); |
| } |
| break; |
| |
| case kDSOType_SRPLHost: |
| if (response) { |
| srpl_host_response(srpl_connection, message, dso); |
| } else { |
| srpl_host_message(srpl_connection, message, dso); |
| } |
| break; |
| |
| case kDSOType_Keepalive: |
| if (response) { |
| INFO(PRI_S_SRP ": keepalive response, xid %04x", name, message->wire.id); |
| } else if (message->wire.id) { |
| INFO(PRI_S_SRP ": keepalive query, xid %04x", name, message->wire.id); |
| } else { |
| INFO(PRI_S_SRP ": keepalive unidirectional, xid %04x (should be zero)", name, message->wire.id); |
| } |
| break; |
| |
| default: |
| INFO(PRI_S_SRP ": unexpected primary TLV %d", name, dso->primary.opcode); |
| dso_simple_response(srpl_connection->connection, NULL, &message->wire, dns_rcode_dsotypeni); |
| break; |
| } |
| |
| } |
| |
| // We should never get here. If we do, it means that we haven't gotten a keepalive in the required interval. |
| static void |
| srpl_keepalive_receive_wakeup(void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| |
| INFO(PUB_S_SRP ": nothing heard from partner across keepalive interval--disconnecting", srpl_connection->name); |
| srpl_connection_discontinue(srpl_connection); // Drop the connection, don't send a retry_delay. |
| } |
| |
| static void |
| srpl_instance_dso_event_callback(void *context, void *event_context, dso_state_t *dso, dso_event_type_t eventType) |
| { |
| message_t *message; |
| dso_query_receive_context_t *response_context; |
| dso_disconnect_context_t *disconnect_context; |
| dso_keepalive_context_t *keepalive_context; |
| srpl_connection_t *srpl_connection = context; |
| const char *name = "<null>"; |
| if (srpl_connection != NULL) { |
| if (srpl_connection->instance != NULL) { |
| name = srpl_connection->instance->instance_name; |
| } else { |
| name = srpl_connection->name; |
| } |
| } |
| |
| switch(eventType) |
| { |
| case kDSOEventType_DNSMessage: |
| // We shouldn't get here because we already handled any DNS messages |
| message = event_context; |
| INFO(PRI_S_SRP ": DNS Message (opcode=%d) received from " PRI_S_SRP, |
| name, dns_opcode_get(&message->wire), dso->remote_name); |
| break; |
| case kDSOEventType_DNSResponse: |
| // We shouldn't get here because we already handled any DNS messages |
| message = event_context; |
| INFO(PRI_S_SRP ": DNS Response (opcode=%d) received from " PRI_S_SRP, |
| name, dns_opcode_get(&message->wire), dso->remote_name); |
| break; |
| case kDSOEventType_DSOMessage: |
| INFO(PRI_S_SRP ": DSO Message (Primary TLV=%d) received from " PRI_S_SRP, |
| name, dso->primary.opcode, dso->remote_name); |
| srpl_connection->last_message_received = srp_time(); |
| ioloop_add_wake_event(srpl_connection->keepalive_receive_wakeup, |
| srpl_connection, srpl_keepalive_receive_wakeup, srpl_connection_context_release, |
| srpl_connection->keepalive_interval * 2); |
| RETAIN_HERE(srpl_connection, srpl_connection); // for the callback |
| message = event_context; |
| srpl_dso_message(srpl_connection, message, dso, false); |
| break; |
| case kDSOEventType_DSOResponse: |
| INFO(PRI_S_SRP ": DSO Response (Primary TLV=%d) received from " PRI_S_SRP, |
| name, dso->primary.opcode, dso->remote_name); |
| srpl_connection->last_message_received = srp_time(); |
| ioloop_add_wake_event(srpl_connection->keepalive_receive_wakeup, |
| srpl_connection, srpl_keepalive_receive_wakeup, srpl_connection_context_release, |
| srpl_connection->keepalive_interval * 2); |
| RETAIN_HERE(srpl_connection, srpl_connection); // for the callback |
| response_context = event_context; |
| message = response_context->message_context; |
| srpl_dso_message(srpl_connection, message, dso, true); |
| break; |
| |
| case kDSOEventType_Finalize: |
| INFO("Finalize"); |
| break; |
| |
| case kDSOEventType_Connected: |
| INFO("Connected to " PRI_S_SRP, dso->remote_name); |
| break; |
| |
| case kDSOEventType_ConnectFailed: |
| INFO("Connection to " PRI_S_SRP " failed", dso->remote_name); |
| break; |
| |
| case kDSOEventType_Disconnected: |
| INFO("Connection to " PRI_S_SRP " disconnected", dso->remote_name); |
| break; |
| case kDSOEventType_ShouldReconnect: |
| INFO("Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name); |
| break; |
| case kDSOEventType_Inactive: |
| INFO(PRI_S_SRP "Inactivity timer went off, closing connection.", name); |
| break; |
| case kDSOEventType_Keepalive: |
| INFO("should send a keepalive now."); |
| break; |
| case kDSOEventType_KeepaliveRcvd: |
| keepalive_context = event_context; |
| keepalive_context->send_response = false; |
| INFO(PRI_S_SRP ": keepalive received, xid %04x.", name, keepalive_context->xid); |
| srpl_keepalive_receive(srpl_connection, keepalive_context->keepalive_interval, keepalive_context->xid); |
| srpl_connection->last_message_received = srp_time(); |
| ioloop_add_wake_event(srpl_connection->keepalive_receive_wakeup, |
| srpl_connection, srpl_keepalive_receive_wakeup, srpl_connection_context_release, |
| srpl_connection->keepalive_interval * 2); |
| RETAIN_HERE(srpl_connection, srpl_connection); // for the callback |
| break; |
| case kDSOEventType_RetryDelay: |
| disconnect_context = event_context; |
| INFO(PRI_S_SRP ": retry delay received, %d seconds", name, disconnect_context->reconnect_delay); |
| srpl_dso_retry_delay(srpl_connection, disconnect_context->reconnect_delay); |
| break; |
| } |
| } |
| |
| static void |
| srpl_datagram_callback(comm_t *comm, message_t *message, void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| srpl_instance_t *instance = srpl_connection->instance; |
| |
| // If this is a DSO message, see if we have a session yet. |
| switch(dns_opcode_get(&message->wire)) { |
| case dns_opcode_dso: |
| if (srpl_connection->dso == NULL) { |
| INFO("dso message received with no DSO object on instance " PRI_S_SRP, instance->instance_name); |
| srpl_disconnect(srpl_connection); |
| return; |
| } |
| dso_message_received(srpl_connection->dso, (uint8_t *)&message->wire, message->length, message); |
| return; |
| break; |
| } |
| INFO("datagram on connection " PRI_S_SRP " not handled, type = %d.", |
| comm->name, dns_opcode_get(&message->wire)); |
| } |
| |
| static void |
| srpl_connection_dso_cleanup(void *UNUSED context) |
| { |
| dso_cleanup(false); |
| } |
| |
| // Call this to break the current srpl_connection without sending the state machine into idle. |
| static void |
| srpl_trigger_disconnect(srpl_connection_t *srpl_connection) |
| { |
| // Trigger a disconnect |
| if (srpl_connection->dso != NULL) { |
| dso_state_cancel(srpl_connection->dso); |
| srpl_connection->dso = NULL; |
| } else { |
| ioloop_comm_cancel(srpl_connection->connection); |
| ioloop_comm_release(srpl_connection->connection); |
| srpl_connection->connection = NULL; |
| } |
| } |
| |
| static bool |
| srpl_connection_dso_life_cycle_callback(dso_life_cycle_t cycle, void *const context, dso_state_t *const dso) |
| { |
| if (cycle == dso_life_cycle_cancel) { |
| srpl_connection_t *connection = context; |
| INFO(PRI_S_SRP ": %p %p", connection->name, connection, dso); |
| if (connection->connection != NULL) { |
| ioloop_comm_cancel(connection->connection); |
| connection->connection->dso = NULL; |
| ioloop_comm_release(connection->connection); |
| connection->connection = NULL; |
| } |
| srpl_connection_reset(connection); |
| connection->dso = NULL; |
| RELEASE_HERE(connection, srpl_connection); |
| ioloop_run_async(srpl_connection_dso_cleanup, NULL); |
| return true; |
| } |
| return false; |
| } |
| |
| static void |
| srpl_associate_incoming_with_instance(comm_t *connection, message_t *message, |
| dso_state_t *dso, srpl_instance_t *instance) |
| { |
| srpl_connection_t *old_connection = NULL; |
| |
| srpl_connection_t *srpl_connection = srpl_connection_create(instance, false); |
| if (srpl_connection == NULL) { |
| ioloop_comm_cancel(connection); |
| return; |
| } |
| |
| srpl_connection->connection = connection; |
| ioloop_comm_retain(srpl_connection->connection); |
| |
| srpl_connection->dso = dso; |
| srpl_connection->instance = instance; |
| srpl_connection->connected_address = connection->address; |
| srpl_connection->state = srpl_state_session_message_wait; |
| |
| dso_set_event_context(dso, srpl_connection); |
| RETAIN_HERE(srpl_connection, srpl_connection); // dso holds reference. |
| dso_set_event_callback(dso, srpl_instance_dso_event_callback); |
| dso_set_life_cycle_callback(dso, srpl_connection_dso_life_cycle_callback); |
| |
| connection->datagram_callback = srpl_datagram_callback; |
| connection->disconnected = srpl_disconnected_callback; |
| ioloop_comm_context_set(connection, srpl_connection, srpl_connection_context_release); |
| RETAIN_HERE(srpl_connection, srpl_connection); // the connection has a reference. |
| |
| srpl_connection_next_state(srpl_connection, srpl_state_session_message_wait); |
| srpl_instance_dso_event_callback(srpl_connection, message, dso, kDSOEventType_DSOMessage); |
| |
| // We drop it after we set it up because that lets us send a retry_delay to the peer. |
| if (instance->domain == NULL || instance->domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) { |
| INFO(PRI_S_SRP ": dropping peer reconnect because we aren't in routine state.", instance->instance_name); |
| RELEASE_HERE(instance, srpl_instance); |
| srpl_connection->instance = NULL; |
| srpl_connection_discontinue(srpl_connection); |
| RELEASE_HERE(srpl_connection, srpl_connection); |
| return; |
| } |
| |
| // If we already have a connection with the remote partner, we replace it with the new connection. |
| if (instance->connection != NULL) { |
| INFO(PRI_S_SRP "we already have a connection.", instance->instance_name); |
| old_connection = instance->connection; |
| RELEASE_HERE(old_connection->instance, srpl_instance); |
| old_connection->instance = NULL; |
| srpl_connection_discontinue(old_connection); |
| RELEASE_HERE(old_connection, srpl_connection); |
| } |
| instance->connection = srpl_connection; // Retained via create/copy rule. |
| } |
| |
| static void |
| srpl_add_unidentified_server(comm_t *connection, message_t *message, dso_state_t *dso, srp_server_t *server_state) |
| { |
| srpl_instance_t **inp, *unmatched_instance = NULL; |
| |
| const char *instance_name; |
| char nbuf[kDNSServiceMaxDomainName]; |
| // Take ip address as the instance name |
| if (connection->address.sa.sa_family == AF_INET6) { |
| instance_name = inet_ntop(AF_INET6, &connection->address.sin6.sin6_addr, nbuf, sizeof nbuf); |
| } else { |
| instance_name = inet_ntop(AF_INET, &connection->address.sin.sin_addr, nbuf, sizeof nbuf); |
| } |
| |
| // Check if an unmatched instance has been created for the same address |
| for (inp = &unmatched_instances; *inp != NULL; inp = &(*inp)->next) { |
| if (!strcmp((*inp)->instance_name, instance_name)) { |
| INFO("we already have an unmatched instance " PRI_S_SRP, instance_name); |
| unmatched_instance = *inp; |
| break; |
| } |
| } |
| if (unmatched_instance == NULL) { |
| INFO("create a temporary instance " PRI_S_SRP, instance_name); |
| unmatched_instance = calloc(1, sizeof(*unmatched_instance)); |
| if (unmatched_instance == NULL) { |
| ERROR("no memory for unmatched instance"); |
| return; |
| } |
| RETAIN_HERE(unmatched_instance, srpl_instance); // The unmatched instance list will hold this reference. |
| // Create a dummy domain because domain can not be decided at this point |
| srpl_domain_t *domain = calloc(1, sizeof(*domain)); |
| if (domain == NULL) { |
| ERROR("no memory for domain structure"); |
| RELEASE_HERE(unmatched_instance, srpl_instance); |
| return; |
| } |
| RETAIN_HERE(domain, srpl_domain); |
| unmatched_instance->domain = domain; |
| domain->server_state = server_state; |
| unmatched_instance->unmatched = true; |
| |
| unmatched_instance->instance_name = strdup(instance_name); |
| if (unmatched_instance->instance_name == NULL) { |
| ERROR("no memory for unmatched instance" PRI_S_SRP, instance_name); |
| RELEASE_HERE(unmatched_instance, srpl_instance); |
| return; |
| } |
| // Find the end of the list and append the newly created instance. |
| for (inp = &unmatched_instances; *inp != NULL; inp = &(*inp)->next) { |
| } |
| *inp = unmatched_instance; |
| } |
| srpl_associate_incoming_with_instance(connection, message, dso, unmatched_instance); |
| } |
| |
| static void |
| srpl_match_unidentified_with_instance(srpl_connection_t *connection, |
| srpl_instance_t *instance) |
| { |
| srpl_instance_t *cur = connection->instance; |
| |
| // Take the connection from its instance. |
| RETAIN_HERE(connection, srpl_connection); // for the function |
| RELEASE_HERE(cur->connection, srpl_connection); |
| cur->connection = NULL; |
| |
| // Remove the currently associated instance from the unmatched_instances |
| srpl_instance_t **sp = NULL; |
| |
| INFO("matched temporary instance " PRI_S_SRP " to instance " PRI_S_SRP " with partner_id %" PRIx64, |
| cur->instance_name, instance->instance_name, instance->partner_id); |
| snprintf(connection->name, strlen(instance->instance_name) + 2, "%s%s", connection->is_server ? "<" : ">", instance->instance_name); |
| for (sp = &unmatched_instances; *sp; sp = &(*sp)->next) { |
| if (*sp == cur) { |
| *sp = cur->next; |
| break; |
| } |
| } |
| |
| // Release the reference that the unmatched instance list had. This should dispose of it, since we didn't have a |
| // reference to this instance from the connection itself. |
| RELEASE_HERE(cur, srpl_instance); |
| |
| if (instance->domain == NULL || instance->domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) { |
| INFO(PRI_S_SRP "dropping peer reconnect because we aren't in routine state.", instance->domain->name); |
| srpl_disconnect(connection); |
| goto out; |
| } |
| |
| if (connection->state == srpl_state_idle || |
| connection->state == srpl_state_disconnect || |
| connection->state == srpl_state_disconnect_wait) |
| { |
| INFO("connection " PRI_S_SRP " is in " PUB_S_SRP, connection->name, srpl_state_name(connection->state)); |
| goto out; |
| } |
| |
| // Release any older connection we might have. |
| if (instance->connection) { |
| srpl_disconnect(instance->connection); |
| RELEASE_HERE(instance->connection, srpl_connection); // Instance still holds reference. |
| instance->connection = NULL; |
| INFO("release connection on instance " PRI_S_SRP " with partner_id %" PRIx64, instance->instance_name, instance->partner_id); |
| } |
| instance->connection = connection; |
| RETAIN_HERE(instance->connection, srpl_connection); |
| connection->instance = instance; |
| RETAIN_HERE(connection->instance, srpl_instance); // Retain on the connection |
| out: |
| RELEASE_HERE(connection, srpl_connection); // done with using it for this function. |
| } |
| |
| void |
| srpl_dso_server_message(comm_t *connection, message_t *message, dso_state_t *dso, srp_server_t *server_state) |
| { |
| srpl_domain_t *domain; |
| srpl_instance_t *instance; |
| srpl_instance_service_t *service; |
| address_query_t *address; |
| int i; |
| |
| // Figure out from which instance this connection originated |
| for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| for (instance = domain->instances; instance != NULL; instance = instance->next) { |
| for (service = instance->services; service != NULL; service = service->next) { |
| address = service->address_query; |
| if (address == NULL) { |
| continue; |
| } |
| for (i = 0; i < address->num_addresses; i++) { |
| if (ip_addresses_equal(&connection->address, &address->addresses[i])) { |
| INFO("SRP Replication connection received from " PRI_S_SRP " on " PRI_S_SRP, |
| address->hostname, connection->name); |
| srpl_associate_incoming_with_instance(connection, message, dso, instance); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| INFO("incoming SRP Replication server connection from unrecognized server " PRI_S_SRP, connection->name); |
| srpl_add_unidentified_server(connection, message, dso, server_state); |
| } |
| |
| static void |
| srpl_connected(comm_t *connection, void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| |
| INFO(PRI_S_SRP " connected", connection->name); |
| connection->dso = dso_state_create(false, 2, connection->name, srpl_instance_dso_event_callback, |
| srpl_connection, srpl_connection_dso_life_cycle_callback, connection); |
| if (connection->dso == NULL) { |
| ERROR(PRI_S_SRP " can't create dso state object.", srpl_connection->name); |
| srpl_disconnect(srpl_connection); |
| return; |
| } |
| RETAIN_HERE(srpl_connection, srpl_connection); // dso holds reference to connection |
| srpl_connection->dso = connection->dso; |
| |
| // Generate an event indicating that we've been connected |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_connected); |
| srpl_event_deliver(srpl_connection, &event); |
| } |
| |
| static bool |
| srpl_connection_connect(srpl_connection_t *srpl_connection) |
| { |
| if (srpl_connection->instance == NULL) { |
| ERROR(PRI_S_SRP ": no instance to connect to", srpl_connection->name); |
| return false; |
| } |
| srpl_connection->connection = ioloop_connection_create(&srpl_connection->connected_address, |
| // tls, stream, stable, opportunistic |
| true, true, true, true, |
| srpl_datagram_callback, srpl_connected, |
| srpl_disconnected_callback, srpl_connection_context_release, |
| srpl_connection); |
| if (srpl_connection->connection == NULL) { |
| ADDR_NAME_LOGGER(ERROR, &srpl_connection->connected_address, "can't create connection to address ", |
| " for srpl connection ", " port ", srpl_connection->name, |
| srpl_connection->connected_address.sa.sa_family == AF_INET ? |
| srpl_connection->connected_address.sin.sin_port: srpl_connection->connected_address.sin6.sin6_port); |
| return false; |
| } |
| ADDR_NAME_LOGGER(INFO, &srpl_connection->connected_address, "connecting to address ", " for instance ", " port ", |
| srpl_connection->name, srpl_connection->connected_address.sa.sa_family == AF_INET ? |
| srpl_connection->connected_address.sin.sin_port: srpl_connection->connected_address.sin6.sin6_port); |
| RETAIN_HERE(srpl_connection, srpl_connection); // For the connection's reference |
| return true; |
| } |
| |
| static void |
| srpl_instance_is_me(srpl_instance_t *instance, srpl_instance_service_t *service, const char *ifname, const addr_t *address) |
| { |
| instance->is_me = true; |
| if (ifname != NULL) { |
| INFO(PUB_S_SRP "/" PUB_S_SRP ": name server for service " PRI_S_SRP " is me.", service->host_name, ifname, service->full_service_name); |
| } else if (address != NULL) { |
| ADDR_NAME_LOGGER(INFO, address, "", " service ", " is me. ", service->host_name, 0); |
| } else { |
| ERROR("srpl_instance_is_me with null ifname and address!"); |
| return; |
| } |
| |
| // When we create the instance, we start an outgoing connection; when we discover that this is a connection |
| // to me, we can discontinue that outgoing connection. |
| if (instance->connection && !instance->connection->is_server) { |
| srpl_connection_discontinue(instance->connection); |
| } |
| } |
| |
| static bool |
| srpl_my_address_check(const addr_t *address) |
| { |
| static interface_address_state_t *ifaddrs = NULL; |
| interface_address_state_t *ifa; |
| static time_t last_fetch = 0; |
| // Update the interface address list every sixty seconds, but only if we're asked to check an address. |
| const time_t now = srp_time(); |
| if (last_fetch == 0 || now - last_fetch > 60) { |
| last_fetch = now; |
| ioloop_map_interface_addresses_here(&ifaddrs, NULL, NULL, NULL); |
| } |
| // See if there's a match. |
| for (ifa = ifaddrs; ifa; ifa = ifa->next) { |
| if (ip_addresses_equal(address, &ifa->addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static void |
| srpl_instance_address_callback(void *context, addr_t *address, bool added, int err) |
| { |
| srpl_instance_service_t *service = context; |
| srpl_instance_t *instance = service->instance; |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("service instance address resolution for " PRI_S_SRP " failed with %d", service->host_name, err); |
| if (service->address_query) { |
| address_query_cancel(service->address_query); |
| RELEASE_HERE(service->address_query, address_query); |
| service->address_query = NULL; |
| } |
| return; |
| } |
| |
| if (added) { |
| bool matched_unidentified = false; |
| srpl_instance_t **up = &unmatched_instances; |
| while (*up != NULL) { |
| srpl_instance_t *unmatched_instance = *up; |
| srpl_connection_t *unidentified = unmatched_instance->connection; |
| if (unidentified == NULL) { |
| up = &(*up)->next; |
| continue; |
| } |
| if (unidentified->dso == NULL) { |
| FAULT("unidentified instance " PRI_S_SRP " (%p) has outgoing connection (%p)!", |
| unmatched_instance->instance_name, unmatched_instance, unidentified); |
| srpl_connection_discontinue(unidentified); |
| RELEASE_HERE(unmatched_instance->connection, srpl_connection); |
| unmatched_instance->connection = NULL; |
| up = &(*up)->next; |
| continue; |
| } |
| if (ip_addresses_equal(address, &unidentified->connected_address)) { |
| INFO("Unidentified connection " PRI_S_SRP " matches new address for instance " PRI_S_SRP, |
| unidentified->dso->remote_name, instance->instance_name); |
| srpl_match_unidentified_with_instance(unidentified, instance); |
| matched_unidentified = true; |
| break; |
| } else { |
| if (unidentified->connected_address.sa.sa_family == AF_INET6) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&unidentified->connected_address.sin6.sin6_addr, rdata_buf); |
| INFO("Unidentified connection address is: " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&unidentified->connected_address.sin6.sin6_addr, rdata_buf)); |
| } else { |
| IPv4_ADDR_GEN_SRP(&unidentified->connected_address.sin.sin_addr, rdata_buf); |
| INFO("Unidentified connection address is: " PRI_IPv4_ADDR_SRP, |
| IPv4_ADDR_PARAM_SRP(&unidentified->connected_address.sin.sin_addr, rdata_buf)); |
| } |
| if (address->sa.sa_family == AF_INET6) { |
| SEGMENTED_IPv6_ADDR_GEN_SRP(&address->sin6.sin6_addr, rdata_buf); |
| INFO("New address is: " PRI_SEGMENTED_IPv6_ADDR_SRP, |
| SEGMENTED_IPv6_ADDR_PARAM_SRP(&address->sin6.sin6_addr, rdata_buf)); |
| } else { |
| IPv4_ADDR_GEN_SRP(&address->sin.sin_addr, rdata_buf); |
| INFO("New address is: " PRI_IPv4_ADDR_SRP, |
| IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, rdata_buf)); |
| } |
| INFO("Unidentified connection addr %p does not match new address for instance addr %p", |
| unidentified, instance); |
| INFO("Unidentified connection " PRI_S_SRP " does not match new address for instance " PRI_S_SRP, |
| unidentified->dso->remote_name, instance->instance_name); |
| up = &(*up)->next; |
| } |
| } |
| |
| if (srpl_my_address_check(address)) { |
| srpl_instance_is_me(instance, service, NULL, address); |
| } |
| |
| // Generate an event indicating that we have a new address. |
| else if (!matched_unidentified && instance->connection != NULL && !instance->connection->is_server) { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_address_add); |
| srpl_event_deliver(instance->connection, &event); |
| } |
| } else { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_address_remove); |
| |
| // Generate an event indicating that an address has been removed. |
| if (!instance->is_me) { |
| if (instance->connection != NULL) { |
| srpl_event_deliver(instance->connection, &event); |
| } |
| } |
| } |
| } |
| |
| static void |
| srpl_abandon_nonpreferred_dataset(srpl_domain_t *NONNULL domain) |
| { |
| srpl_instance_t *instance, *next; |
| for (instance = domain->instances; instance != NULL; instance = next) { |
| next = instance->next; |
| if (instance->have_dataset_id) { |
| int64_t distance = domain->dataset_id - instance->dataset_id; |
| if (distance > 0 || (distance == EQUI_DISTANCE64 && |
| (int64_t)(domain->dataset_id) > (int64_t)(instance->dataset_id))) |
| { |
| instance->sync_to_join = false; |
| if (instance->connection != NULL) { |
| INFO("abandon dataset with instance " PRI_S_SRP " of partner id %" PRIx64, |
| instance->instance_name, instance->partner_id); |
| srpl_connection_reset(instance->connection); |
| if (instance->connection != NULL && instance->connection->connection != NULL) { |
| srpl_trigger_disconnect(instance->connection); |
| } else { |
| srpl_instance_reconnect(instance); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| static void srpl_transition_to_startup_state(srpl_domain_t *domain); |
| |
| static bool |
| srpl_find_instance_with_current_dataset(srpl_domain_t *domain) |
| { |
| for (srpl_instance_t *instance = domain->instances; instance!= NULL; instance = instance->next) { |
| if (instance->dataset_id == domain->dataset_id) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static uint64_t |
| srpl_instances_max_dataset_id(srpl_domain_t *domain) |
| { |
| // at this point, domain->instances should contain at least one instance |
| srpl_instance_t *instance = domain->instances; |
| uint64_t max = instance->dataset_id; |
| instance = instance->next; |
| while (instance) { |
| int64_t distance = instance->dataset_id - max; |
| // the number 2^(N−1) (where N is 64) is equidistant in both directions in sequence number terms. |
| // they are both considered to be "less than" each other. This is true for any sequence number with |
| // distance of 0x8000000000000000 between them. To break the tie, higher signed number wins. |
| if (distance == EQUI_DISTANCE64) { |
| if ((int64_t)(instance->dataset_id) > (int64_t)max) { |
| max = instance->dataset_id; |
| } |
| } else if (distance > 0) { |
| max = instance->dataset_id; |
| } |
| instance = instance->next; |
| } |
| return max; |
| } |
| |
| // Return value |
| // True: continue setting up the replication with the discovered partner. |
| // True is returned if the discovered dataset id is equal to or greater |
| // than the current dataset id held for this domain |
| // False: Skip setting up the replication with the discovered partner. |
| // False is returned if the discovered dataset id is smaller than the |
| // current dataset id |
| static bool |
| srpl_evaluate_instance_dataset_id(srpl_domain_t *domain, srpl_instance_t *instance) |
| { |
| uint64_t dataset_id = instance->dataset_id; |
| if (!domain->have_dataset_id) { |
| domain->dataset_id = dataset_id; |
| domain->have_dataset_id = true; |
| INFO("domain " PRI_S_SRP ": first dataset id %" PRIx64, domain->name, dataset_id); |
| return true; |
| } else { |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE && |
| !srpl_find_instance_with_current_dataset(domain)) |
| { |
| // the instances that have generated the dataset id are gone; in this case, |
| // we update the dataset id to the max of all the current instances |
| domain->dataset_id = srpl_instances_max_dataset_id(domain); |
| INFO("all instances advertising the current dataset id are gone; update the dataset id to %" PRIx64, domain->dataset_id); |
| } |
| int64_t distance = domain->dataset_id - dataset_id; |
| if (distance == 0) { |
| return true; |
| } |
| bool dataset_id_win = false; |
| if (distance > 0 || (distance == EQUI_DISTANCE64 && |
| (int64_t)(domain->dataset_id) > (int64_t)dataset_id)) |
| { |
| dataset_id_win = true; |
| } |
| if (!dataset_id_win) { |
| // local dataset_id is smaller than the remote partner's. |
| INFO("domain " PRI_S_SRP ": current dataset id %" PRIx64 " < discovered dataset id %" PRIx64 |
| ", abandon current dataset and re-enter the startup state", |
| domain->name, domain->dataset_id, dataset_id); |
| domain->dataset_id = dataset_id; |
| // DNS-SD SRP Replication Spec: if at any time (regardless of "startup" or "routine |
| // operation" state) an SRPL partner discovers that it is synchronizing with a |
| // non-preferred dataset ID, it MUST abandon that dataset, re-enter the "startup" |
| // state, and attempt to synchronize with the (newly discovered) preferred dataset id. |
| srpl_abandon_nonpreferred_dataset(domain); |
| srpl_transition_to_startup_state(domain); |
| return true; |
| } else { |
| // local dataset_id is larger than the remote partner's |
| instance->sync_to_join = false; |
| INFO("domain " PRI_S_SRP ": current dataset id %" PRIx64 " > discovered dataset id %" PRIx64 |
| ", skip setting up replication with the remote partner", |
| domain->name, domain->dataset_id, dataset_id); |
| } |
| } |
| return false; |
| } |
| |
| static void |
| srpl_instance_add(const char *hostname, const char *service_name, |
| const char *ifname, srpl_domain_t *domain, srpl_instance_service_t *service, |
| bool have_partner_id, uint64_t advertised_partner_id, |
| bool have_dataset_id, uint64_t advertised_dataset_id) |
| { |
| srpl_instance_t **sp, *instance; |
| srpl_instance_service_t **hp; |
| |
| // Find the service on the instance list for this domain. |
| for (instance = domain->instances; instance != NULL; instance = instance->next) { |
| for (hp = &instance->services; *hp != NULL; hp = &(*hp)->next) { |
| if (service == *hp) { |
| INFO("service " PRI_S_SRP " is found with instance " PRI_S_SRP, service_name, instance->instance_name); |
| break; |
| } |
| } |
| if (*hp != NULL) { |
| break; |
| } |
| } |
| |
| if (instance == NULL) { |
| INFO("service " PRI_S_SRP " for " PRI_S_SRP "/" PUB_S_SRP " " PUB_S_SRP |
| "id %" PRIx64 " " PUB_S_SRP "did %" PRIx64 " not on list", |
| service_name != NULL ? service_name : "<NULL>", hostname, ifname, |
| have_partner_id ? "" : "!", advertised_partner_id, |
| have_dataset_id ? "" : "!", advertised_dataset_id); |
| // Look for the instance with the same partner id |
| for (sp = &domain->instances; *sp != NULL; sp = &(*sp)->next) { |
| instance = *sp; |
| if (instance->have_partner_id && instance->partner_id == advertised_partner_id) { |
| INFO("instance " PRI_S_SRP " has matched partner_id %" PRIx64, instance->instance_name, instance->partner_id); |
| break; |
| } |
| } |
| |
| if (*sp == NULL) { |
| // We don't have the instance to the remote partner yet, create one |
| instance = calloc(1, sizeof(*instance)); |
| if (instance == NULL) { |
| ERROR("no memory to create instance for service " PRI_S_SRP, service_name); |
| RELEASE_HERE(service, srpl_instance_service); |
| return; |
| } |
| // Retain for the instance list on the domain |
| RETAIN_HERE(instance, srpl_instance); |
| instance->domain = domain; |
| RETAIN_HERE(instance->domain, srpl_domain); |
| instance->services = service; |
| *sp = instance; |
| INFO("create a new instance for service " PRI_S_SRP, service_name); |
| } else { |
| for (hp = &instance->services; *hp != NULL; hp = &(*hp)->next); |
| *hp = service; |
| INFO("instance " PRI_S_SRP " exists; just link the service " PRI_S_SRP, instance->instance_name, service_name); |
| } |
| // Retain service for the instance service list |
| RETAIN_HERE(service, srpl_instance_service); |
| // Retain instance for service |
| RETAIN_HERE(instance, srpl_instance); |
| service->instance = instance; |
| } |
| // take the host name of the remote partner as the instance name |
| char *pch = strchr(service_name, '.'); |
| if (instance->instance_name == NULL || strncmp(instance->instance_name, service_name, pch - service_name)) { |
| char partner_name[kDNSServiceMaxDomainName]; |
| memcpy(partner_name, service_name, pch - service_name); |
| partner_name[pch - service_name] = '\0'; |
| char *new_partner_name = strdup(partner_name); |
| if (new_partner_name == NULL) { |
| ERROR("no memory for instance name."); |
| return; |
| } else { |
| INFO("instance name changed from " PRI_S_SRP " to " PRI_S_SRP, instance->instance_name ? |
| instance->instance_name : "NULL", new_partner_name); |
| free(instance->instance_name); |
| instance->instance_name = new_partner_name; |
| } |
| } |
| bool some_id_updated = false; |
| if (have_dataset_id && (!instance->have_dataset_id || instance->dataset_id != advertised_dataset_id)) { |
| some_id_updated = true; |
| instance->have_dataset_id = true; |
| instance->dataset_id = advertised_dataset_id; |
| INFO("update instance " PRI_S_SRP " dataset_id %" PRIx64 " from service " PRI_S_SRP, |
| instance->instance_name, instance->dataset_id, service_name); |
| } |
| |
| // If this add changed the partner ID, we may want to re-attempt a connect. |
| if (have_partner_id && (!instance->have_partner_id || instance->partner_id != advertised_partner_id)) { |
| some_id_updated = true; |
| instance->have_partner_id = true; |
| instance->partner_id = advertised_partner_id; |
| INFO("instance " PRI_S_SRP " update partner_id to %" PRIx64, instance->instance_name, advertised_partner_id); |
| } |
| |
| if (!srpl_evaluate_instance_dataset_id(domain, instance)) { |
| INFO("non-preferred dataset id %" PRIx64 " for domain " PRI_S_SRP ", so we should not connect.", |
| advertised_dataset_id, domain->name); |
| return; |
| } |
| |
| // To join the replication, sync with remote partners that are discovered during the |
| // discovery window. |
| if (domain->partner_discovery_pending) { |
| instance->sync_to_join = true; |
| } |
| |
| // If the hostname changed, we need to restart the address query. |
| if (service->host_name == NULL || strcmp(service->host_name, hostname)) { |
| if (service->address_query != NULL) { |
| address_query_cancel(service->address_query); |
| RELEASE_HERE(service->address_query, address_query); |
| service->address_query = NULL; |
| } |
| |
| if (service->host_name != NULL) { |
| INFO("name server name change from " PRI_S_SRP " to " PRI_S_SRP " for " PRI_S_SRP "/" PUB_S_SRP " in domain " PRI_S_SRP, |
| service->host_name, hostname, service_name == NULL ? "<NULL>" : service_name, ifname, domain->name); |
| } else { |
| INFO("new name server " PRI_S_SRP " for " PRI_S_SRP "/" PUB_S_SRP " in domain " PRI_S_SRP, |
| hostname, service_name == NULL ? "<NULL>" : service_name, ifname, domain->name); |
| } |
| |
| char *new_name = strdup(hostname); |
| if (new_name == NULL) { |
| // This should never happen, and if it does there's actually no clean way to recover from it. This approach |
| // will result in no crash, and since we don't start an address query in this case, we will just wind up in |
| // a quiescent state for this replication peer until something changes. |
| ERROR("no memory for service name."); |
| return; |
| } else { |
| free(service->host_name); |
| service->host_name = new_name; |
| } |
| // The instance may be connected. It's possible its IP address hasn't changed. If it has changed, we should |
| // get a disconnect due to a connection timeout or (if something else got the same address, a reset) if for |
| // no other reason, and then we'll try to reconnect, so this should be harmless. |
| } |
| |
| // The address query can be NULL either because we only just created the instance, or because the instance name changed (e.g. |
| // as the result of a hostname conflict). |
| if (service->address_query == NULL) { |
| service->address_query = address_query_create(service->host_name, service, |
| srpl_instance_address_callback, |
| srpl_instance_service_context_release); |
| if (service->address_query == NULL) { |
| INFO("unable to create address query"); |
| } else { |
| RETAIN_HERE(service, srpl_instance_service); // retain for the address query. |
| } |
| } |
| |
| // If there's no existing connection, the partner initiates an outgoing connection if |
| // it is in the startup state or its partner id is greater than the remote partner id. |
| if (!instance->is_me && |
| instance->connection == NULL && |
| (some_id_updated || |
| (domain->srpl_opstate == SRPL_OPSTATE_STARTUP || domain->partner_id > advertised_partner_id))) |
| { |
| char msg_buf[256]; |
| if (domain->srpl_opstate == SRPL_OPSTATE_STARTUP) { |
| snprintf(msg_buf, sizeof(msg_buf), "I am in startup state"); |
| } else { |
| snprintf(msg_buf, sizeof(msg_buf), "local partner_id %" PRIx64 |
| " greater than remote partner_id %" PRIx64, domain->partner_id, |
| advertised_partner_id); |
| } |
| INFO("making outgoing connection on instance " PRI_S_SRP " (partner_id: %" PRIx64 ") since " PUB_S_SRP, |
| instance->instance_name, instance->partner_id, msg_buf); |
| if (instance->connection != NULL && instance->connection->connection != NULL) { |
| srpl_trigger_disconnect(instance->connection); |
| } else { |
| srpl_instance_reconnect(instance); |
| } |
| } |
| } |
| |
| static void |
| srpl_resolve_callback(srpl_instance_service_t *service) |
| { |
| char ifname[IFNAMSIZ]; |
| srpl_domain_t *domain = service->domain; |
| const char *domain_name; |
| uint8_t domain_len; |
| const char *partner_id_string; |
| const char *dataset_id_string; |
| const char *xpanid_string; |
| uint8_t partner_id_string_len; |
| uint8_t dataset_id_string_len; |
| uint8_t xpanid_string_len; |
| char partner_id_buf[INT64_HEX_STRING_MAX]; |
| char dataset_id_buf[INT64_HEX_STRING_MAX]; |
| char xpanid_buf[INT64_HEX_STRING_MAX]; |
| uint64_t advertised_partner_id = 0; |
| bool have_partner_id = false; |
| uint64_t advertised_dataset_id = 0; |
| bool have_dataset_id = false; |
| srpl_instance_service_t **sp; |
| srp_server_t *server_state = domain->server_state; |
| |
| // These are just used to do the "satisfied" check--we can tell that we have these records from the rdata pointers. |
| service->have_txt_record = service->have_srv_record = false; |
| |
| // In case we later determine that the data we got is stale, this flag indicates that it's okay to try to |
| // reconfirm it. |
| service->got_new_info = true; |
| |
| if (service->txt_rdata == NULL) { |
| INFO(PRI_S_SRP ": service update with no TXT record--skipping", service->full_service_name); |
| return; |
| } |
| if (service->srv_rdata == NULL) { |
| INFO(PRI_S_SRP ": service update with no SRV record--skipping", service->full_service_name); |
| return; |
| } |
| |
| domain_name = TXTRecordGetValuePtr(service->txt_length, service->txt_rdata, "dn", &domain_len); |
| if (domain_name == NULL) { |
| INFO("resolve for " PRI_S_SRP " succeeded, but there is no domain name.", service->full_service_name); |
| return; |
| } |
| |
| if (domain_len != strlen(domain->name) || memcmp(domain_name, domain->name, domain_len)) { |
| const char *domain_print; |
| char *domain_terminated = malloc(domain_len + 1); |
| if (domain_terminated == NULL) { |
| domain_print = "<no memory for domain name>"; |
| } else { |
| memcpy(domain_terminated, domain_name, domain_len); |
| domain_terminated[domain_len] = 0; |
| domain_print = domain_terminated; |
| } |
| INFO("domain (" PRI_S_SRP ") for " PRI_S_SRP " doesn't match expected domain " PRI_S_SRP, |
| domain_print, service->full_service_name, domain->name); |
| free(domain_terminated); |
| return; |
| } |
| if (strcmp(domain->name, server_state->current_thread_domain_name)) { |
| INFO("discovered srpl instance is not for current thread domain, so not setting up replication."); |
| return; |
| } |
| |
| INFO("server " PRI_S_SRP " for " PRI_S_SRP, service->full_service_name, domain->name); |
| |
| // Make sure it's for our mesh. |
| snprintf(xpanid_buf, sizeof(xpanid_buf), "%" PRIx64, server_state->xpanid); |
| xpanid_string = TXTRecordGetValuePtr(service->txt_length, service->txt_rdata, "xpanid", &xpanid_string_len); |
| if (xpanid_string == NULL || |
| (xpanid_string_len != strlen(xpanid_buf) || memcmp(xpanid_buf, xpanid_string, xpanid_string_len))) |
| { |
| char other_xpanid_buf[INT64_HEX_STRING_MAX]; |
| if (xpanid_string_len >= sizeof(other_xpanid_buf)) { |
| xpanid_string_len = sizeof(other_xpanid_buf) - 1; |
| } |
| if (xpanid_string == NULL) { |
| const char none[] = "(none)"; |
| memcpy(other_xpanid_buf, none, sizeof(none)); |
| } else { |
| memcpy(other_xpanid_buf, xpanid_string, xpanid_string_len); |
| other_xpanid_buf[xpanid_string_len] = 0; |
| } |
| INFO("discovered srpl instance is not for xpanid " PRI_S_SRP ", not " PRI_S_SRP |
| " so not setting up replication.", xpanid_buf, other_xpanid_buf); |
| return; |
| } |
| |
| partner_id_string = TXTRecordGetValuePtr(service->txt_length, service->txt_rdata, "pid", &partner_id_string_len); |
| if (partner_id_string != NULL && partner_id_string_len < INT64_HEX_STRING_MAX) { |
| char *endptr, *nulptr; |
| unsigned long long num; |
| memcpy(partner_id_buf, partner_id_string, partner_id_string_len); |
| nulptr = &partner_id_buf[partner_id_string_len]; |
| *nulptr = '\0'; |
| num = strtoull(partner_id_buf, &endptr, 16); |
| // On current architectures, unsigned long long and uint64_t are the same size, but we should have a check here |
| // just in case, because the standard doesn't guarantee that this will be true. |
| // If endptr == nulptr, that means we converted the entire buffer and didn't run into a NUL in the middle of it |
| // somewhere. |
| if (num < UINT64_MAX && endptr == nulptr) { |
| advertised_partner_id = num; |
| have_partner_id = true; |
| } |
| } |
| |
| dataset_id_string = TXTRecordGetValuePtr(service->txt_length, service->txt_rdata, "did", &dataset_id_string_len); |
| if (dataset_id_string != NULL && dataset_id_string_len < INT64_HEX_STRING_MAX) { |
| char *endptr, *nulptr; |
| unsigned long long num; |
| memcpy(dataset_id_buf, dataset_id_string, dataset_id_string_len); |
| nulptr = &dataset_id_buf[dataset_id_string_len]; |
| *nulptr = '\0'; |
| num = strtoull(dataset_id_buf, &endptr, 16); |
| if (num < UINT64_MAX && endptr == nulptr) { |
| advertised_dataset_id = num; |
| have_dataset_id = true; |
| } |
| } |
| |
| dns_rr_t srv_record; |
| unsigned offset = 0; |
| memset(&srv_record, 0, sizeof(srv_record)); |
| srv_record.type = dns_rrtype_srv; |
| if (!dns_rdata_parse_data(&srv_record, service->srv_rdata, &offset, offset + service->srv_length, service->srv_length, 0)) { |
| ERROR(PRI_S_SRP ": unable to parse srv record", service->full_service_name); |
| return; |
| } |
| |
| service->outgoing_port = srv_record.data.srv.port; |
| |
| if (if_indextoname(service->ifindex, ifname) == NULL) { |
| snprintf(ifname, sizeof(ifname), "%d", service->ifindex); |
| } |
| |
| char namebuf[DNS_MAX_NAME_SIZE_ESCAPED + 1]; |
| dns_name_print(srv_record.data.srv.name, namebuf, sizeof(namebuf)); |
| dns_name_free(srv_record.data.srv.name); |
| |
| srpl_instance_add(namebuf, service->full_service_name, ifname, service->domain, service, have_partner_id, |
| advertised_partner_id, have_dataset_id, advertised_dataset_id); |
| |
| // After the service is associated with a resolved instance, we should take it off the unresolved |
| // list if the service is still on it. If the service fails to assocaited with an instance because |
| // for example, the resolve shows a service that does not include required data, we should still |
| // keep the service on the unresolved list. Later on when we get an expected resolve, the service |
| // can be moved to the associated list. This guarantees that a service at a time has to be on either |
| // unresolved or associated list. |
| for (sp = &domain->unresolved_services; *sp; sp = &(*sp)->next) { |
| if (*sp == service) { |
| *sp = service->next; |
| service->next = NULL; |
| RELEASE_HERE(service, srpl_instance_service); |
| break; |
| } |
| } |
| } |
| |
| static void |
| srpl_instance_service_newdata_timeout(void *context) |
| { |
| srpl_instance_service_t *service = context; |
| srpl_resolve_callback(service); |
| } |
| |
| static void |
| srpl_instance_service_satisfied_check(srpl_instance_service_t *service) |
| { |
| if (service->have_srv_record && service->have_txt_record) { |
| if (service->resolve_wakeup != NULL) { |
| ioloop_cancel_wake_event(service->resolve_wakeup); |
| } |
| srpl_resolve_callback(service); |
| return; |
| } |
| INFO(PRI_S_SRP ": not satisfied, waiting.", service->full_service_name); |
| if (service->resolve_wakeup == NULL) { |
| service->resolve_wakeup = ioloop_wakeup_create(); |
| if (service->resolve_wakeup == NULL) { |
| ERROR(PRI_S_SRP ": unable to allocate resolve wakeup", service->full_service_name); |
| return; |
| } |
| } |
| ioloop_add_wake_event(service->resolve_wakeup, service, |
| srpl_instance_service_newdata_timeout, srpl_instance_service_context_release, 1000); // max one second |
| RETAIN_HERE(service, srpl_instance_service); // for the wakeup |
| } |
| |
| static void |
| srpl_service_txt_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags UNUSED flags, uint32_t UNUSED interfaceIndex, |
| DNSServiceErrorType errorCode, const char *fullname, uint16_t UNUSED rrtype, uint16_t UNUSED rrclass, |
| uint16_t rdlen, const void *rdata, uint32_t UNUSED ttl, void *context) |
| { |
| srpl_instance_service_t *service = context; |
| if (errorCode != kDNSServiceErr_NoError) { |
| ERROR("txt resolve for " PRI_S_SRP " failed with %d", fullname, errorCode); |
| if (service->txt_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->txt_txn); |
| ioloop_dnssd_txn_release(service->txt_txn); |
| service->txt_txn = NULL; |
| } |
| return; |
| } |
| |
| free(service->txt_rdata); |
| if (!(flags & kDNSServiceFlagsAdd)) { |
| INFO("TXT record for " PRI_S_SRP " went away", service->full_service_name); |
| service->txt_rdata = NULL; |
| service->txt_length = 0; |
| service->have_txt_record = false; |
| return; |
| } |
| service->txt_rdata = malloc(rdlen); |
| if (service->txt_rdata == NULL) { |
| ERROR("unable to save txt rdata for " PRI_S_SRP, service->full_service_name); |
| return; |
| } |
| memcpy(service->txt_rdata, rdata, rdlen); |
| service->txt_length = rdlen; |
| service->have_txt_record = true; |
| srpl_instance_service_satisfied_check(service); |
| } |
| |
| static void |
| srpl_service_srv_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t UNUSED interfaceIndex, |
| DNSServiceErrorType errorCode, const char *UNUSED fullname, uint16_t UNUSED rrtype, uint16_t UNUSED rrclass, |
| uint16_t rdlen, const void *rdata, uint32_t UNUSED ttl, void *context) |
| { |
| srpl_instance_service_t *service = context; |
| if (errorCode != kDNSServiceErr_NoError) { |
| ERROR("srv resolve for " PRI_S_SRP " failed with %d", fullname, errorCode); |
| if (service->srv_txn != NULL) { |
| ioloop_dnssd_txn_cancel(service->srv_txn); |
| ioloop_dnssd_txn_release(service->srv_txn); |
| service->srv_txn = NULL; |
| } |
| return; |
| } |
| |
| free(service->srv_rdata); |
| if (!(flags & kDNSServiceFlagsAdd)) { |
| INFO("SRV record for " PRI_S_SRP " went away", service->full_service_name); |
| service->srv_rdata = NULL; |
| service->srv_length = 0; |
| service->have_srv_record = false; |
| return; |
| } |
| service->srv_rdata = malloc(rdlen); |
| if (service->srv_rdata == NULL) { |
| ERROR("unable to save srv rdata for " PRI_S_SRP, service->full_service_name); |
| return; |
| } |
| memcpy(service->srv_rdata, rdata, rdlen); |
| service->srv_length = rdlen; |
| service->have_srv_record = true; |
| srpl_instance_service_satisfied_check(service); |
| } |
| |
| static void |
| srpl_browse_restart(void *context) |
| { |
| srpl_domain_t *domain = context; |
| ERROR("restarting browse on domain " PRI_S_SRP, domain->name); |
| srpl_domain_browse_start(domain); |
| } |
| |
| static bool |
| srpl_service_instance_query_start(srpl_instance_service_t *service, dnssd_txn_t **txn, const char *rrtype_name, |
| uint16_t rrtype, uint16_t qclass, DNSServiceQueryRecordReply callback) |
| { |
| DNSServiceRef sdref; |
| |
| int err = DNSServiceQueryRecord(&sdref, kDNSServiceFlagsLongLivedQuery, kDNSServiceInterfaceIndexAny, |
| service->full_service_name, rrtype, qclass, callback, service); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to resolve " PUB_S_SRP " record for " PRI_S_SRP ": code %d", |
| rrtype_name, service->full_service_name, err); |
| return false; |
| } |
| *txn = ioloop_dnssd_txn_add(sdref, service, srpl_instance_service_context_release, NULL); |
| if (*txn == NULL) { |
| ERROR("unable to allocate dnssd_txn_t for " PUB_S_SRP " record for " PRI_S_SRP, |
| rrtype_name, service->full_service_name); |
| DNSServiceRefDeallocate(sdref); |
| return false; |
| } |
| // Retain for the dnssd_txn. |
| RETAIN_HERE(service, srpl_instance_service); |
| return true; |
| } |
| |
| static void |
| srpl_browse_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, |
| const char *replyDomain, void *context) |
| { |
| srpl_domain_t *domain = context; |
| if (errorCode != kDNSServiceErr_NoError) { |
| ERROR("browse on domain " PRI_S_SRP " failed with %d", domain->name, errorCode); |
| if (domain->query != NULL) { |
| ioloop_dnssd_txn_cancel(domain->query); |
| ioloop_dnssd_txn_release(domain->query); |
| domain->query = NULL; |
| } |
| |
| // Get rid of all instances on the domain, because we aren't going to get remove events for them. |
| // If we start a new browse and get add events while the connections are still up, this will |
| // have no effect. |
| for (srpl_instance_t *instance = domain->instances; instance; instance = instance->next) { |
| INFO("_srpl-tls._tcp instance " PRI_S_SRP " went away.", instance->instance_name); |
| srpl_instance_discontinue(instance); |
| } |
| |
| srpl_instance_service_t *service, *next; |
| for (service = domain->unresolved_services; service != NULL; service = next) { |
| INFO("discontinue unresolved service " PRI_S_SRP, service->full_service_name); |
| next = service->next; |
| service->num_copies = 0; |
| srpl_instance_service_discontinue(service); |
| } |
| |
| if (domain->server_state->srpl_browse_wakeup == NULL) { |
| domain->server_state->srpl_browse_wakeup = ioloop_wakeup_create(); |
| } |
| if (domain->server_state->srpl_browse_wakeup != NULL) { |
| ioloop_add_wake_event(domain->server_state->srpl_browse_wakeup, |
| domain, srpl_browse_restart, NULL, 1000); |
| } |
| return; |
| } |
| |
| char full_service_name[kDNSServiceMaxDomainName]; |
| DNSServiceConstructFullName(full_service_name, serviceName, regtype, replyDomain); |
| |
| srpl_instance_t *instance; |
| srpl_instance_service_t *service; |
| // See if we already have a service record going; First search in unresolved_services list which |
| // contains the services that haven't been resolved yet. |
| for (service = domain->unresolved_services; service; service = service->next) { |
| if (!strcmp(service->full_service_name, full_service_name)) { |
| break; |
| } |
| } |
| // If the service is not found in unresolved_services list, search in instance list which contains services |
| // that have been resolved. |
| if (service == NULL) { |
| for (instance = domain->instances; instance; instance = instance->next) { |
| for (service = instance->services; service; service = service->next) { |
| if (!strcmp(service->full_service_name, full_service_name)) { |
| break; |
| } |
| } |
| if (service != NULL) { |
| break; |
| } |
| } |
| } |
| if (flags & kDNSServiceFlagsAdd) { |
| if (service != NULL) { |
| if (service->resolve_started) { |
| INFO(PRI_S_SRP ": resolve_started true, incrementing num_copies to %d", |
| full_service_name, service->num_copies + 1); |
| service->num_copies++; |
| INFO("duplicate add for service " PRI_S_SRP, full_service_name); |
| // it's possbile that a service goes away and starts discontinuing, and before the timeout, |
| // the service comes back again. In this case, since the service is still on the list, it |
| // appears as a duplicate add. But we should cancel the discontinue timer. |
| if (service->discontinue_timeout != NULL) { |
| if (service->discontinuing) { |
| INFO("discontinue on service " PRI_S_SRP " canceled.", service->full_service_name); |
| ioloop_cancel_wake_event(service->discontinue_timeout); |
| service->discontinuing = false; |
| if (service->instance != NULL) { |
| service->instance->discontinuing = false; |
| } |
| } |
| } |
| return; |
| } |
| // In this case the service went away and came back, so service->resolve_started is false, but the |
| // instance still exists. |
| INFO(PRI_S_SRP ": resolve_started false, incrementing num_copies to %d", |
| full_service_name, service->num_copies + 1); |
| service->num_copies++; |
| INFO("service " PRI_S_SRP " went away but came back.", full_service_name); |
| } else { |
| service = calloc(1, sizeof(*service)); |
| if (service == NULL) { |
| ERROR("no memory for service " PRI_S_SRP, full_service_name); |
| return; |
| } |
| // Retain for unresolved_services list |
| RETAIN_HERE(service, srpl_instance_service); |
| service->domain = domain; |
| RETAIN_HERE(service->domain, srpl_domain); |
| |
| service->full_service_name = strdup(full_service_name); |
| if (service->full_service_name == NULL) { |
| ERROR("no memory for service name " PRI_S_SRP, full_service_name); |
| RELEASE_HERE(service, srpl_instance_service); |
| return; |
| } |
| INFO(PRI_S_SRP ": new service, setting num_copies to 1", full_service_name); |
| service->num_copies = 1; |
| service->ifindex = interfaceIndex; |
| // add to the unresolved service list |
| srpl_instance_service_t **sp; |
| for (sp = &domain->unresolved_services; *sp != NULL; sp = &(*sp)->next) {;} |
| *sp = service; |
| |
| dns_towire_state_t towire; |
| uint8_t name_buffer[kDNSServiceMaxDomainName]; |
| memset(&towire, 0, sizeof(towire)); |
| towire.p = name_buffer; |
| towire.lim = towire.p + kDNSServiceMaxDomainName; |
| dns_full_name_to_wire(NULL, &towire, full_service_name); |
| |
| free(service->ptr_rdata); |
| service->ptr_length = towire.p - name_buffer; |
| service->ptr_rdata = malloc(service->ptr_length); |
| if (service->ptr_rdata == NULL) { |
| ERROR("unable to save PTR rdata for " PRI_S_SRP, full_service_name); |
| return; |
| } |
| memcpy(service->ptr_rdata, name_buffer, service->ptr_length); |
| } |
| |
| if (!srpl_service_instance_query_start(service, &service->txt_txn, "TXT", dns_rrtype_txt, dns_qclass_in, |
| srpl_service_txt_callback) || |
| !srpl_service_instance_query_start(service, &service->srv_txn, "SRV", dns_rrtype_srv, dns_qclass_in, |
| srpl_service_srv_callback)) |
| { |
| return; |
| } |
| INFO("resolving " PRI_S_SRP, full_service_name); |
| service->resolve_started = true; |
| |
| // If we have a discontinue timer going, cancel it. |
| if (service->discontinue_timeout != NULL) { |
| if (service->discontinuing) { |
| INFO("discontinue on service " PRI_S_SRP " canceled.", service->full_service_name); |
| ioloop_cancel_wake_event(service->discontinue_timeout); |
| service->discontinuing = false; |
| if (service->instance != NULL) { |
| service->instance->discontinuing = false; |
| } |
| } |
| } |
| |
| } else { |
| if (service != NULL) { |
| INFO(PRI_S_SRP ": decrementing num_copies to %d", full_service_name, service->num_copies - 1); |
| service->num_copies--; |
| if (service->num_copies < 0) { |
| FAULT("num_copies went negative"); |
| service->num_copies = 0; |
| } |
| if (service->num_copies == 0) { |
| INFO("discontinuing service " PRI_S_SRP, full_service_name); |
| srpl_instance_service_discontinue(service); |
| return; |
| } |
| } |
| } |
| } |
| |
| static void |
| srpl_domain_context_release(void *context) |
| { |
| srpl_domain_t *domain = context; |
| RELEASE_HERE(domain, srpl_domain); |
| } |
| |
| static void |
| srpl_dnssd_txn_fail(void *context, int err) |
| { |
| srpl_domain_t *domain = context; |
| ERROR("service browse " PRI_S_SRP " i/o failure: %d", domain->name, err); |
| } |
| |
| static bool |
| srpl_domain_browse_start(srpl_domain_t *domain) |
| { |
| int ret; |
| DNSServiceRef sdref; |
| |
| INFO("starting browse on _srpl-tls._tcp"); |
| // Look for an NS record for the specified domain using mDNS, not DNS. |
| ret = DNSServiceBrowse(&sdref, kDNSServiceFlagsLongLivedQuery, |
| kDNSServiceInterfaceIndexAny, "_srpl-tls._tcp", NULL, srpl_browse_callback, domain); |
| if (ret != kDNSServiceErr_NoError) { |
| ERROR("Unable to query for NS records for " PRI_S_SRP, domain->name); |
| return false; |
| } |
| domain->query = ioloop_dnssd_txn_add(sdref, srpl_domain_context_release, NULL, srpl_dnssd_txn_fail); |
| if (domain->query == NULL) { |
| ERROR("Unable to set up ioloop transaction for NS query on " PRI_S_SRP, domain->name); |
| DNSServiceRefDeallocate(sdref); |
| return false; |
| } |
| return true; |
| } |
| |
| static void |
| srpl_domain_add(srp_server_t *server_state, const char *domain_name) |
| { |
| srpl_domain_t **dp, *domain; |
| |
| // Find the domain, if it's already there. |
| for (dp = &server_state->srpl_domains; *dp; dp = &(*dp)->next) { |
| domain = *dp; |
| if (!strcasecmp(domain->name, domain_name)) { |
| break; |
| } |
| } |
| |
| // If not there, make it. |
| if (*dp == NULL) { |
| domain = calloc(1, sizeof(*domain)); |
| if (domain == NULL || (domain->name = strdup(domain_name)) == NULL) { |
| ERROR("Unable to allocate replication structure for domain " PRI_S_SRP, domain_name); |
| free(domain); |
| return; |
| } |
| *dp = domain; |
| // Hold a reference for the domain list |
| RETAIN_HERE(domain, srpl_domain); |
| INFO("New service replication browsing domain: " PRI_S_SRP, domain->name); |
| |
| domain->srpl_opstate = SRPL_OPSTATE_STARTUP; |
| domain->partner_discovery_timeout = ioloop_wakeup_create(); |
| if (domain->partner_discovery_timeout) { |
| ioloop_add_wake_event(domain->partner_discovery_timeout, domain, |
| srpl_partner_discovery_timeout, NULL, |
| MIN_PARTNER_DISCOVERY_INTERVAL + srp_random16() % PARTNER_DISCOVERY_INTERVAL_RANGE); |
| } else { |
| ERROR("unable to add wakeup event for partner discovery for domain " PRI_S_SRP, domain->name); |
| return; |
| } |
| domain->partner_discovery_pending = true; |
| domain->partner_id = srp_random64(); |
| INFO("generate partner id %" PRIx64 " for domain " PRI_S_SRP, domain->partner_id, domain->name); |
| } else { |
| ERROR("Unexpected duplicate replication domain: " PRI_S_SRP, domain_name); |
| return; |
| } |
| |
| // Start a browse on the domain. |
| if (!srpl_domain_browse_start(domain)) { |
| return; |
| } |
| domain->server_state = server_state; |
| RETAIN_HERE(domain, srpl_domain); |
| } |
| |
| static void |
| srpl_domain_rename(const char *current_name, const char *new_name) |
| { |
| ERROR("replication domain " PRI_S_SRP " renamed to " PRI_S_SRP ", not currently handled.", current_name, new_name); |
| } |
| |
| // Note that when this is implemented, it has the potential to return new thread domain names more than once, so |
| // in principle we need to change the name of the domain we are advertising. |
| static cti_status_t |
| cti_get_thread_network_name(void *context, cti_string_property_reply_t NONNULL callback, |
| run_context_t NULLABLE UNUSED client_queue) |
| { |
| callback(context, "openthread", kCTIStatus_NoError); |
| return kCTIStatus_NoError; |
| } |
| |
| // |
| // Event apply functions, print functions, and state actions, generally in order |
| // |
| |
| static bool |
| event_is_message(srpl_event_t *event) |
| { |
| switch(event->event_type) { |
| case srpl_event_invalid: |
| case srpl_event_address_add: |
| case srpl_event_address_remove: |
| case srpl_event_server_disconnect: |
| case srpl_event_reconnect_timer_expiry: |
| case srpl_event_disconnected: |
| case srpl_event_connected: |
| case srpl_event_advertise_finished: |
| case srpl_event_srp_client_update_finished: |
| case srpl_event_do_sync: |
| return false; |
| |
| case srpl_event_session_response_received: |
| case srpl_event_send_candidates_response_received: |
| case srpl_event_candidate_received: |
| case srpl_event_host_message_received: |
| case srpl_event_candidate_response_received: |
| case srpl_event_host_response_received: |
| case srpl_event_session_message_received: |
| case srpl_event_send_candidates_message_received: |
| return true; |
| } |
| return false; |
| } |
| |
| // States that require an instance (most states). We also validate the chain up to the server state, because |
| // it's possible for that to go away and yet still for one last event to arrive, at least in principle. |
| #define REQUIRE_SRPL_INSTANCE(srpl_connection) \ |
| do { \ |
| if ((srpl_connection)->instance == NULL || (srpl_connection)->instance->domain == NULL || \ |
| (srpl_connection)->instance->domain->server_state == NULL) { \ |
| ERROR(PRI_S_SRP ": no instance in state " PUB_S_SRP, srpl_connection->name, \ |
| srpl_connection->state_name); \ |
| return srpl_state_invalid; \ |
| } \ |
| } while(false) |
| |
| // For states that never receive events. |
| #define REQUIRE_SRPL_EVENT_NULL(srpl_connection, event) \ |
| do { \ |
| if ((event) != NULL) { \ |
| ERROR(PRI_S_SRP ": received unexpected " PUB_S_SRP " event in state " PUB_S_SRP, \ |
| srpl_connection->name, event->name, srpl_connection->state_name); \ |
| return srpl_state_invalid; \ |
| } \ |
| } while (false) |
| |
| // Announce that we have entered a state that takes no events |
| #define STATE_ANNOUNCE_NO_EVENTS(srpl_connection) \ |
| do { \ |
| INFO(PRI_S_SRP ": entering state " PUB_S_SRP, srpl_connection->name, srpl_connection->state_name); \ |
| } while (false) |
| |
| // Announce that we have entered a state that takes no events |
| #define STATE_ANNOUNCE_NO_EVENTS_NAME(connection, fqdn) \ |
| do { \ |
| char hostname[kDNSServiceMaxDomainName]; \ |
| dns_name_print(fqdn, hostname, sizeof(hostname)); \ |
| INFO(PRI_S_SRP ": entering state " PUB_S_SRP " with host " PRI_S_SRP, \ |
| connection->name, connection->state_name, hostname); \ |
| } while (false) |
| |
| // Announce that we have entered a state that takes no events |
| #define STATE_ANNOUNCE(srpl_connection, event) \ |
| do { \ |
| if (event != NULL) { \ |
| INFO(PRI_S_SRP ": event " PUB_S_SRP " received in state " PUB_S_SRP, \ |
| srpl_connection->name, event->name, srpl_connection->state_name); \ |
| } else { \ |
| INFO(PRI_S_SRP ": entering state " PUB_S_SRP, \ |
| srpl_connection->name, srpl_connection->state_name); \ |
| } \ |
| } while (false) |
| |
| #define UNEXPECTED_EVENT_MAIN(srpl_connection, event, bad) \ |
| do { \ |
| if (event_is_message(event)) { \ |
| INFO(PRI_S_SRP ": invalid event " PUB_S_SRP " in state " PUB_S_SRP, \ |
| (srpl_connection)->name, (event)->name, srpl_connection->state_name); \ |
| return bad; \ |
| } \ |
| INFO(PRI_S_SRP ": unexpected event " PUB_S_SRP " in state " PUB_S_SRP, \ |
| (srpl_connection)->name, (event)->name, \ |
| srpl_connection->state_name); \ |
| return srpl_state_invalid; \ |
| } while (false) |
| |
| // UNEXPECTED_EVENT flags the response as bad on a protocol level, triggering a retry delay |
| // UNEXPECTED_EVENT_NO_ERROR doesn't. |
| #define UNEXPECTED_EVENT(srpl_connection, event) UNEXPECTED_EVENT_MAIN(srpl_connection, event, srpl_state_invalid) |
| #define UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event) \ |
| UNEXPECTED_EVENT_MAIN(srpl_connection, event, srpl_connection_drop_state(srpl_connection->instance, srpl_connection)) |
| |
| static void |
| srpl_instance_reconnect(srpl_instance_t *instance) |
| { |
| srpl_event_t event; |
| |
| // If we have a new connection, no need to reconnect. |
| if (instance->connection != NULL && instance->connection->is_server && |
| SRPL_CONNECTION_IS_CONNECTED(instance->connection)) |
| { |
| INFO(PRI_S_SRP ": we have a valid connection.", instance->instance_name); |
| return; |
| } |
| // We shouldn't have an outgoing connection. |
| if (instance->connection != NULL && !instance->connection->is_server && |
| SRPL_CONNECTION_IS_CONNECTED(instance->connection)) |
| { |
| FAULT(PRI_S_SRP ": got to srpl_instance_reconnect with a connected (" PUB_S_SRP ") outgoing connection.", |
| instance->instance_name, srpl_state_name(instance->connection->state)); |
| return; |
| } |
| |
| // Start from the beginning of the address list. |
| srpl_instance_address_query_reset(instance); |
| |
| // If we don't have an srpl_connection at this point, make one. |
| if (instance->connection == NULL) { |
| INFO(PRI_S_SRP ": instance has no connection.", instance->instance_name); |
| instance->connection = srpl_connection_create(instance, true); |
| if (instance->connection == NULL) { |
| ERROR(PRI_S_SRP ": unable to create srpl_connection", instance->instance_name); |
| return; |
| } |
| srpl_connection_next_state(instance->connection, srpl_state_idle); |
| } |
| |
| // Trigger a reconnect if appropriate |
| if (!instance->is_me && instance->domain != NULL && |
| (instance->domain->srpl_opstate == SRPL_OPSTATE_STARTUP || instance->domain->partner_id > instance->partner_id)) |
| { |
| // We might be in some disconnected state other than idle, so first move to idle if that's the case. |
| if (instance->connection->state != srpl_state_idle) { |
| srpl_connection_next_state(instance->connection, srpl_state_idle); |
| } |
| srpl_event_initialize(&event, srpl_event_reconnect_timer_expiry); |
| srpl_event_deliver(instance->connection, &event); |
| } else { |
| ERROR(PRI_S_SRP ": reconnect requested but not appropriate: is_me = " PUB_S_SRP |
| ", domain = %p, opstate = %d, ddid %" PRIx64 ", idid %" PRIx64, |
| instance->instance_name, instance->is_me ? "true" : "false", instance->domain, |
| instance->domain == NULL ? -1 : instance->domain->srpl_opstate, |
| instance->domain == NULL ? 0 : instance->domain->partner_id, instance->partner_id); |
| } |
| } |
| |
| static void |
| srpl_instance_reconnect_callback(void *context) |
| { |
| srpl_instance_reconnect(context); |
| } |
| |
| static srpl_state_t |
| srpl_connection_drop_state_delay(srpl_instance_t *instance, srpl_connection_t *srpl_connection, int delay) |
| { |
| // Schedule a reconnect. |
| if (instance->reconnect_timeout == NULL) { |
| instance->reconnect_timeout = ioloop_wakeup_create(); |
| } |
| if (instance->reconnect_timeout == NULL) { |
| FAULT(PRI_S_SRP "disconnecting, but can't reconnect!", srpl_connection->name); |
| } else { |
| RETAIN_HERE(instance, srpl_instance); // for the timeout |
| ioloop_add_wake_event(instance->reconnect_timeout, instance, |
| srpl_instance_reconnect_callback, srpl_instance_context_release, delay * MSEC_PER_SEC); |
| } |
| |
| if (srpl_connection == instance->connection && srpl_connection->is_server) { |
| srpl_connection->retry_delay = delay; |
| return srpl_state_retry_delay_send; |
| } else { |
| return srpl_state_disconnect; |
| } |
| } |
| |
| static srpl_state_t |
| srpl_connection_drop_state(srpl_instance_t *instance, srpl_connection_t *srpl_connection) |
| { |
| if (instance == NULL) { |
| return srpl_state_disconnect; |
| } else if (instance->unmatched) { |
| if (instance->connection == srpl_connection) { |
| RELEASE_HERE(instance->connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| return srpl_state_disconnect; |
| } else { |
| return srpl_connection_drop_state_delay(instance, srpl_connection, 300); |
| } |
| } |
| |
| // Call when there's a protocol error, so that we don't start reconnecting over and over. |
| static void |
| srpl_disconnect(srpl_connection_t *srpl_connection) |
| { |
| const int delay = 300; // five minutes |
| srpl_instance_t *instance = srpl_connection->instance; |
| if (instance != NULL && srpl_connection->connection != NULL) { |
| srpl_state_t state = srpl_connection_drop_state_delay(instance, srpl_connection, delay); |
| if (state == srpl_state_retry_delay_send) { |
| srpl_retry_delay_send(srpl_connection, delay); |
| } |
| } |
| srpl_connection_discontinue(srpl_connection); |
| } |
| |
| // We arrive at the disconnected state when there is no connection to make, or no need to make a connection. |
| // This state takes no action, but waits for events. If we get an add event and we don't have a viable incoming |
| // connection, we go to the next_address_get event. |
| static srpl_state_t |
| srpl_disconnected_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_address_add) { |
| return srpl_state_next_address_get; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| static void |
| srpl_instance_address_query_reset(srpl_instance_t *instance) |
| { |
| for (srpl_instance_service_t *service = instance->services; service != NULL; service = service->next) { |
| address_query_t *address_query = service->address_query; |
| if (address_query != NULL && address_query->num_addresses > 0) { |
| address_query->cur_address = -1; |
| } |
| } |
| } |
| |
| // This state takes the action of looking for an address to try. This can have three outcomes: |
| // |
| // * No addresses available: go to the disconnected state |
| // * End of address list: go to the reconnect_wait state |
| // * Address found: got to the connect state |
| |
| static srpl_state_t |
| srpl_next_address_get_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| address_query_t *address_query = NULL; |
| srpl_instance_t *instance; |
| srpl_instance_service_t *service; |
| bool no_address = true; |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| |
| instance = srpl_connection->instance; |
| // Get the next address |
| // Return an event, one of "next address", "end of address list" or "no addresses" |
| for (service = instance->services; service != NULL; service = service->next) { |
| address_query = service->address_query; |
| if (address_query == NULL || address_query->num_addresses == 0) { |
| continue; |
| } else { |
| no_address = false; |
| if (address_query->cur_address == address_query->num_addresses || |
| ++address_query->cur_address == address_query->num_addresses) |
| { |
| continue; |
| } else { |
| memcpy(&srpl_connection->connected_address, |
| &address_query->addresses[address_query->cur_address], sizeof(addr_t)); |
| if (srpl_connection->connected_address.sa.sa_family == AF_INET) { |
| srpl_connection->connected_address.sin.sin_port = htons(service->outgoing_port); |
| } else { |
| srpl_connection->connected_address.sin6.sin6_port = htons(service->outgoing_port); |
| } |
| return srpl_state_connect; |
| } |
| } |
| } |
| |
| if (no_address) { |
| return srpl_state_disconnected; |
| } else { |
| srpl_instance_address_query_reset(instance); |
| return srpl_state_reconnect_wait; |
| } |
| } |
| |
| // This state takes the action of connecting to the connection's current address, which is expected to have |
| // been set. This can have two outcomes: |
| // |
| // * The connect attempt fails immediately: go to the next_address_get state |
| // * The connection attempt is in progress: go to the connecting state |
| static srpl_state_t |
| srpl_connect_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| |
| // Connect to the address from the event. |
| if (!srpl_connection_connect(srpl_connection)) { |
| return srpl_state_next_address_get; |
| } else { |
| return srpl_state_connecting; |
| } |
| } |
| |
| // We reach this state when we are disconnected and don't need to reconnect because we have an active server |
| // connection. If we get a server disconnect here, then we go to the next_address_get state. |
| static srpl_state_t |
| srpl_idle_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events |
| } else if (event->event_type == srpl_event_server_disconnect || |
| event->event_type == srpl_event_reconnect_timer_expiry) |
| { |
| INFO(PRI_S_SRP ": event " PUB_S_SRP " received in state " PUB_S_SRP, |
| srpl_connection->name, event->name, srpl_connection->state_name); |
| return srpl_state_next_address_get; |
| } else { |
| // We don't log unhandled events in the idle state because it creates a lot of noise. |
| return srpl_state_invalid; |
| } |
| } |
| |
| // We've received a timeout event on the reconnect timer. Generate a reconnect_timeout event and send it to the |
| // connection. |
| static void |
| srpl_connection_reconnect_timeout(void *context) |
| { |
| srpl_connection_t *srpl_connection = context; |
| srpl_instance_t *instance = srpl_connection->instance; |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_reconnect_timer_expiry); |
| srpl_event_deliver(srpl_connection, &event); |
| INFO("reconnect timeout on " PRI_S_SRP, srpl_connection->name); |
| // If we have tried to connect to all the addresses but failed, we assume the peer is |
| // gone. We no longer need to synchronize with this peer and if this was an obstacle |
| // to enter the routine state, we should recheck again. |
| if (instance != NULL && instance->sync_to_join && |
| domain != NULL && domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) |
| { |
| instance->sync_to_join = false; |
| srpl_maybe_sync_or_transition(domain); |
| } |
| } |
| |
| static srpl_state_t |
| srpl_connection_schedule_reconnect_event(srpl_connection_t *srpl_connection, uint32_t when) |
| { |
| // Create a reconnect timer on the srpl_connection_t |
| if (srpl_connection->reconnect_wakeup == NULL) { |
| srpl_connection->reconnect_wakeup = ioloop_wakeup_create(); |
| if (srpl_connection->reconnect_wakeup == NULL) { |
| ERROR("no memory for reconnect_wakeup for service instance " PRI_S_SRP, srpl_connection->name); |
| return srpl_state_invalid; |
| } |
| } else { |
| ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup); |
| } |
| ioloop_add_wake_event(srpl_connection->reconnect_wakeup, srpl_connection, srpl_connection_reconnect_timeout, |
| srpl_connection_context_release, when); |
| RETAIN_HERE(srpl_connection, srpl_connection); // the timer has a reference. |
| return srpl_state_invalid; |
| } |
| |
| // We reach the set_reconnect_timer state when we have tried to connect to all the known addresses. Once we have set a |
| // timer, we wait for events. If we get a reconnect_timeout event, we go to the next_address_get state. If we get an |
| // add_adress event, we cancel the retransmit timer and go to the next_address_get state. |
| static srpl_state_t |
| srpl_reconnect_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_connection_schedule_reconnect_event(srpl_connection, 60 * 1000); |
| } |
| if (event->event_type == srpl_event_reconnect_timer_expiry) { |
| return srpl_state_next_address_get; |
| } else if (event->event_type == srpl_event_address_add) { |
| ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup); |
| return srpl_state_next_address_get; |
| } |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| |
| // We get to this state when the remote end has sent something bogus; in this case we send a retry_delay message to |
| // tell the client not to reconnect for a while. |
| static srpl_state_t |
| srpl_retry_delay_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| srpl_retry_delay_send(srpl_connection, srpl_connection->retry_delay); |
| return srpl_state_disconnect; |
| } |
| |
| // We go to the disconnect state when the connection needs to be dropped either because we lost the session ID |
| // coin toss or something's gone wrong. In either case, we do not attempt to reconnect--we either go to the idle state |
| // or the disconnect_wait state, depending on whether or not the connection has already been closed. |
| static srpl_state_t |
| srpl_disconnect_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // Any ongoing state needs to be discarded. |
| srpl_connection_reset(srpl_connection); |
| |
| // Disconnect the srpl_connection_t |
| if (srpl_connection->connection == NULL) { |
| return srpl_state_idle; |
| } |
| srpl_trigger_disconnect(srpl_connection); |
| return srpl_state_disconnect_wait; |
| } |
| |
| // We enter disconnect_wait when we are waiting for a disconnect event after cancelling a connection. |
| // There is no action for this event. The only event we are interested in is the disconnect event. |
| static srpl_state_t |
| srpl_disconnect_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE(srpl_connection, event); |
| if (event == NULL) { |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_disconnected) { |
| return srpl_state_idle; |
| } else { |
| UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event); |
| } |
| return srpl_state_invalid; |
| } |
| |
| // We enter the connecting state when we've attempted a connection to some address. |
| // This state has no action. If a connected event is received, we move to the connected state. |
| // If a disconnected event is received, we move to the next_address_get state. |
| static srpl_state_t |
| srpl_connecting_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE(srpl_connection, event); |
| if (event == NULL) { |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_disconnected) { |
| // We tried to connect and the connection failed. This may mean that the information we see in the _srpl-tls.tcp |
| // advertisement is wrong, or that the address records are wrong. Reconfirm the records. |
| srpl_reconfirm(srpl_connection); |
| return srpl_state_next_address_get; |
| } else if (event->event_type == srpl_event_connected) { |
| return srpl_state_session_send; |
| } else { |
| UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event); |
| } |
| return srpl_state_invalid; |
| } |
| |
| static srpl_state_t |
| srpl_sync_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE(srpl_connection, event); |
| if (event == NULL) { |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_do_sync) { |
| // When starting to sync, we reset the keepalive_interval so that we can detect |
| // the problem sooner during synchronization. |
| srpl_connection->keepalive_interval = DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2; |
| return srpl_state_send_candidates_send; |
| } else { |
| UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event); |
| } |
| return srpl_state_invalid; |
| } |
| |
| // This state sends a SRPL session message and then goes to session_response_wait, unless the send failed, in which |
| // case it goes to the disconnect state. |
| static srpl_state_t |
| srpl_session_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // Send a session message |
| // Now we say hello. |
| if (!srpl_session_message_send(srpl_connection, false)) { |
| return srpl_state_disconnect; |
| } |
| return srpl_state_session_response_wait; |
| } |
| |
| // This state waits for a session response with the remote partner ID and whether |
| // the remote partner is in startup state. |
| // When the response arrives, it goes to the send_candidates_send state. |
| static srpl_state_t |
| srpl_session_response_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| STATE_ANNOUNCE(srpl_connection, event); |
| if (event == NULL) { |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_session_response_received) { |
| srpl_connection->remote_partner_id = event->content.session.partner_id; |
| srpl_connection->new_partner = event->content.session.new_partner; |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| // if we are already in the routine state, we can directly move forward |
| // with sync; otherwise we put the srpl connection in the sync_wait state |
| // where we check the number of active srp servers to decide if we should |
| // continue sync at this point. |
| if (domain->srpl_opstate == SRPL_OPSTATE_ROUTINE) { |
| return srpl_state_send_candidates_send; |
| } |
| return srpl_state_sync_wait; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| return srpl_state_invalid; |
| } |
| |
| // When evaluating the incoming session, we've decided to continue (called by srpl_session_evaluate_action). |
| static srpl_state_t |
| srpl_evaluate_incoming_continue(srpl_connection_t *srpl_connection) |
| { |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| |
| if (srpl_connection->new_partner) { |
| INFO(PRI_S_SRP " connecting partner is in startup state", srpl_connection->name); |
| } else { |
| INFO(PRI_S_SRP ": my partner id %" PRIx64 " < connecting partner id %" PRIx64, |
| srpl_connection->name, domain->partner_id, srpl_connection->remote_partner_id); |
| } |
| if (srpl_connection->is_server) { |
| return srpl_state_session_response_send; |
| } else { |
| return srpl_state_send_candidates_send; |
| } |
| } |
| |
| // When evaluating the incoming ID, we've decided to disconnect (called by srpl_session_evaluate_action). |
| static srpl_state_t |
| srpl_evaluate_incoming_disconnect(srpl_connection_t *srpl_connection, bool bad) |
| { |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) { |
| INFO(PRI_S_SRP ": not in the routine state yet in domain " PRI_S_SRP, |
| srpl_connection->name, domain->name); |
| } else { |
| INFO(PRI_S_SRP ": my partner id %" PRIx64 " > connectiong partner id %" PRIx64, |
| srpl_connection->name, domain->partner_id, srpl_connection->remote_partner_id); |
| } |
| if (srpl_connection->instance->is_me) { |
| return srpl_evaluate_incoming_continue(srpl_connection); |
| } else { |
| if (bad) { |
| // bad is set if the server send back the same ID we sent, which means it's misbehaving. |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| return srpl_state_disconnect; |
| } |
| } |
| |
| // This state's action is to evaluate if the partner should accept the connection. |
| // The receiving partner accepts the connection if the connecting partner is in the |
| // "startup" state (flaged as new partner), or the receiving partner's ID is smaller |
| // than the connecting partner's ID. Otherwise, the receiving partner disconnects. |
| static srpl_state_t |
| srpl_session_evaluate_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| |
| // The receiving partner must be in routine state to accept the connection. |
| // Recceiving connection in startup state should not happen, but add a guard |
| // here anyway to protect against such situation. |
| if (domain->srpl_opstate == SRPL_OPSTATE_ROUTINE && (srpl_connection->new_partner || |
| domain->partner_id < srpl_connection->remote_partner_id)) |
| { |
| return srpl_evaluate_incoming_continue(srpl_connection); |
| } else { |
| return srpl_evaluate_incoming_disconnect(srpl_connection, false); |
| } |
| } |
| |
| // This state's action is to send the "send candidates" message, and then go to the send_candidates_wait state. |
| static srpl_state_t |
| srpl_send_candidates_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // Send "send candidates" message |
| // Return no event |
| srpl_send_candidates_message_send(srpl_connection, false); |
| return srpl_state_send_candidates_wait; |
| } |
| |
| static bool |
| srpl_can_transition_to_routine_state(const srpl_domain_t *domain) |
| { |
| if (domain == NULL) { |
| INFO("returning false because there's no domain"); |
| return false; |
| } |
| |
| // We only transition to routine state after discovery is completed, as |
| // we need to sync with all the partners discovered during the discovery |
| // window to join the replication |
| if (domain->partner_discovery_pending) { |
| INFO("returning false because partner discovery is still pending"); |
| return false; |
| } |
| |
| if (domain->srpl_opstate == SRPL_OPSTATE_ROUTINE) { |
| INFO("returning false because we are already in routine state"); |
| return false; |
| } |
| |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| // We skip checking the instance if the instance |
| // 1. is to myself (this could happen if I restarts and receives a stale advertisement from myself). |
| // 2. is not discovered during the discovery_timeout, or |
| // 3. is discontinuing |
| if (instance->is_me || !instance->sync_to_join || instance->discontinuing) { |
| INFO("instance " PUB_S_SRP ": is_me (" PUB_S_SRP ") or sync_to_join (" PUB_S_SRP ") or discontinuing (" PUB_S_SRP ")", |
| instance->instance_name, instance->is_me ? "true" : "false", instance->sync_to_join ? "true" : "false", |
| instance->discontinuing ? "true" : "false"); |
| continue; |
| } |
| // Instance is a valid partner that we should sync with to possibly move to the routine state |
| if (instance->connection == NULL || |
| !instance->connection->database_synchronized) |
| { |
| INFO("synchronization on " PRI_S_SRP " with partner_id %" PRIx64 " is not ready (%p " PUB_S_SRP ").", |
| instance->instance_name, instance->partner_id, instance->connection, |
| (instance->connection == NULL ? "null" : |
| instance->connection->database_synchronized ? "true" : "false")); |
| return false; |
| } |
| } |
| INFO("ready"); |
| return true; |
| } |
| |
| // SRPL partners MUST persist the highest (most significant byte or MSB) of the dataset ID. |
| // When generating a new dataset ID, the partner MUST increment the MSB of last used dataset |
| // ID to use as MSB of new dataset ID and populate the lower 56 bits randomly. If there is no |
| // previously saved ID, then the partner randomly generates the entire 64-bit ID. |
| static uint64_t |
| srpl_generate_store_dataset_id(srpl_domain_t *domain) |
| { |
| uint64_t dataset_id; |
| uint8_t msb; |
| OSStatus err; |
| |
| // read out the stored msb of the dataset id. increment the msb and generate the dataset id. |
| const CFStringRef app_id = CFSTR("com.apple.srp-mdns-proxy.preferences"); |
| const CFStringRef key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("dataset-id-msb-%s"), domain->name); |
| |
| if (key) { |
| msb = (uint8_t)CFPrefs_GetInt64(app_id, key, &err); |
| if (err) { |
| dataset_id = srp_random64(); |
| } else { |
| dataset_id = (((uint64_t)msb+1) << 56) | (srp_random64() & LOWER56_BIT_MASK); |
| } |
| // store the most significant byte (msb) of the generated dataset id |
| msb = (dataset_id & 0xFF00000000000000) >> 56; |
| err = CFPrefs_SetInt64(app_id, key, msb); |
| |
| if (err) { |
| ERROR("Unable to store the msb of the dataset id in preferences."); |
| } |
| CFRelease(key); |
| } else { |
| ERROR("unable to create key for domain " PRI_S_SRP, domain->name); |
| dataset_id = srp_random64(); |
| } |
| |
| return dataset_id; |
| } |
| |
| static void |
| srpl_transition_to_routine_state(srpl_domain_t *domain) |
| { |
| domain->srpl_opstate = SRPL_OPSTATE_ROUTINE; |
| INFO("transitions to routine state in domain " PRI_S_SRP, domain->name); |
| // If the partner does not discover any other partners advertising |
| // the same domain in the "startup" state, it generates a new dataset |
| // ID when entering the "routine operation" state. |
| // When generating a new dataset ID, the partner MUST increment the MSB of |
| // last used dataset ID to use as MSB of new dataset ID and populate the |
| // lower 56 bits randomly using a random number generator. If there is no |
| // previously saved ID, then the partner randomly generates the entire 64-bit ID. |
| if (!domain->have_dataset_id) { |
| domain->dataset_id = srpl_generate_store_dataset_id(domain); |
| domain->have_dataset_id = true; |
| INFO("generate new dataset id %" PRIx64 " for domain " PRI_S_SRP, |
| domain->dataset_id, domain->name); |
| } |
| #if STUB_ROUTER |
| srp_server_t *server_state = domain->server_state; |
| // Advertise the SRPL service in the "routine" state. |
| srpl_domain_advertise(domain); |
| if (!strcmp(domain->name, server_state->current_thread_domain_name)) { |
| route_state_t *route_state = server_state->route_state; |
| if (route_state != NULL) { |
| route_state->thread_sequence_number = (domain->dataset_id & 0xFF00000000000000) >> 56; |
| INFO("thread sequence number 0x%02x", route_state->thread_sequence_number); |
| route_state->partition_can_advertise_anycast_service = true; |
| partition_maybe_advertise_anycast_service(route_state); |
| } |
| } |
| #endif |
| } |
| |
| // Used by srpl_send_candidates_wait_action and srpl_host_wait_action |
| static srpl_state_t |
| srpl_send_candidates_wait_event_process(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| if (event->event_type == srpl_event_send_candidates_response_received) { |
| if (srpl_connection->is_server) { |
| srpl_connection->database_synchronized = true; |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE && |
| srpl_connection->instance != NULL && |
| srpl_connection->instance->sync_to_join) |
| { |
| srpl_maybe_sync_or_transition(domain); |
| } |
| return srpl_state_ready; |
| } else { |
| return srpl_state_send_candidates_message_wait; |
| } |
| } else if (event->event_type == srpl_event_candidate_received) { |
| srpl_connection_candidate_set(srpl_connection, event->content.candidate); |
| event->content.candidate = NULL; // steal! |
| return srpl_state_candidate_check; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // We reach this state after having sent a "send candidates" message, so we can in principle get either a |
| // "candidate" message or a "send candidates" response here, leading either to send_candidates check or one |
| // of two states depending on whether this connection is an incoming or outgoing connection. Outgoing |
| // connections send the "send candidates" message first, so when they get a "send candidates" reply, they |
| // need to wait for a "send candidates" message from the remote. Incoming connections send the "send candidates" |
| // message last, so when they get the "send candidates" reply, the database sync is done and it's time to |
| // just deal with ongoing updates. In this case we go to the check_for_srp_client_updates state, which |
| // looks to see if any updates came in from SRP clients while we were syncing the databases. |
| static srpl_state_t |
| srpl_send_candidates_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } |
| return srpl_send_candidates_wait_event_process(srpl_connection, event); |
| } |
| |
| static srpl_candidate_disposition_t |
| srpl_candidate_host_check(srpl_connection_t *srpl_connection, adv_host_t *host) |
| { |
| // Evaluate candidate |
| // Return "host candidate wanted" or "host candidate not wanted" event |
| if (host == NULL) { |
| INFO("host is NULL, answer is yes."); |
| return srpl_candidate_yes; |
| } else { |
| if (host->removed) { |
| INFO("host is removed, answer is yes."); |
| return srpl_candidate_yes; |
| } else if (host->key_id != srpl_connection->candidate->key_id) { |
| INFO("host key conflict (%x vs %x), answer is conflict.", host->key_id, srpl_connection->candidate->key_id); |
| return srpl_candidate_conflict; |
| } else { |
| // We allow for a bit of jitter. Bear in mind that candidates only happen on startup, so |
| // even if a previous run of the SRP server on this device was responsible for registering |
| // the candidate, we don't have it, so we still need it. |
| if (host->update_time - srpl_connection->candidate->update_time > SRPL_UPDATE_JITTER_WINDOW) { |
| INFO("host update time %" PRId64 " candidate update time %" PRId64 ", answer is no.", |
| (int64_t)host->update_time, (int64_t)srpl_connection->candidate->update_time); |
| return srpl_candidate_no; |
| } else { |
| INFO("host update time %" PRId64 " candidate update time %" PRId64 ", answer is yes.", |
| (int64_t)host->update_time, (int64_t)srpl_connection->candidate->update_time); |
| return srpl_candidate_yes; |
| } |
| } |
| } |
| } |
| |
| // We enter this state after we've received a "candidate" message, and check to see if we want the host the candidate |
| // represents. We then send an appropriate response. |
| static srpl_state_t |
| srpl_candidate_check_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS_NAME(srpl_connection, srpl_connection->candidate->name); |
| |
| adv_host_t *host = srp_adv_host_copy(srpl_connection->instance->domain->server_state, |
| srpl_connection->candidate->name); |
| srpl_candidate_disposition_t disposition = srpl_candidate_host_check(srpl_connection, host); |
| if (host != NULL) { |
| srp_adv_host_release(host); |
| } |
| switch(disposition) { |
| case srpl_candidate_yes: |
| srpl_candidate_response_send(srpl_connection, kDSOType_SRPLCandidateYes); |
| return srpl_state_candidate_host_wait; |
| case srpl_candidate_no: |
| srpl_candidate_response_send(srpl_connection, kDSOType_SRPLCandidateNo); |
| return srpl_state_send_candidates_wait; |
| case srpl_candidate_conflict: |
| srpl_candidate_response_send(srpl_connection, kDSOType_SRPLConflict); |
| return srpl_state_send_candidates_wait; |
| } |
| return srpl_state_invalid; |
| } |
| |
| // In candidate_host_send_wait, we take no action and wait for events. We're hoping for a "host" message, leading to |
| // candidate_host_prepare. We could also receive a "candidate" message, leading to candidate_received, or a "send |
| // candidates" reply, leading to candidate_reply_received. |
| |
| static srpl_state_t |
| srpl_candidate_host_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_host_message_received) { |
| // Copy the update information, retain what's refcounted, and free what's not on the event. |
| srpl_host_update_steal_parts(&srpl_connection->stashed_host, &event->content.host_update); |
| return srpl_state_candidate_host_prepare; |
| } else { |
| return srpl_send_candidates_wait_event_process(srpl_connection, event); |
| } |
| } |
| |
| // Here we want to see if we can do an immediate update; if so, we go to candidate_host_re_evaluate; otherwise |
| // we go to candidate_host_contention_wait |
| static srpl_state_t |
| srpl_candidate_host_prepare_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS_NAME(srpl_connection, srpl_connection->candidate->name); |
| |
| // Apply the host from the event to the current host list |
| // Return no event |
| adv_host_t *host = srp_adv_host_copy(srpl_connection->instance->domain->server_state, |
| srpl_connection->candidate->name); |
| if (host == NULL) { |
| // If we don't have this host, we can apply the update immediately. |
| return srpl_state_candidate_host_apply; |
| } |
| if (host->srpl_connection != NULL || host->update != NULL) { |
| // We are processing an update from a different srpl server or a client. |
| INFO(PRI_S_SRP ": host->srpl_connection = %p host->update=%p--going into contention", |
| srpl_connection->name, host->srpl_connection, host->update); |
| srp_adv_host_release(host); |
| return srpl_state_candidate_host_contention_wait; |
| } else { |
| srpl_connection->candidate->host = host; |
| return srpl_state_candidate_host_re_evaluate; |
| } |
| } |
| |
| static adv_host_t * |
| srpl_client_update_matches(dns_name_t *hostname, srpl_event_t *event) |
| { |
| adv_host_t *host = event->content.client_result.host; |
| if (event->content.client_result.rcode == dns_rcode_noerror && dns_names_equal_text(hostname, host->name)) { |
| INFO("returning host " PRI_S_SRP, host->name); |
| return host; |
| } |
| char name[kDNSServiceMaxDomainName]; |
| dns_name_print(hostname, name, sizeof(name)); |
| INFO("returning NULL: rcode = " PUB_S_SRP " hostname = " PRI_S_SRP " host->name = " PRI_S_SRP, |
| dns_rcode_name(event->content.client_result.rcode), name, host->name); |
| return NULL; |
| } |
| |
| // and wait for a srp_client_update_finished event for the host, which |
| // will trigger us to move to candidate_host_re_evaluate. |
| static srpl_state_t |
| srpl_candidate_host_contention_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_srp_client_update_finished) { |
| adv_host_t *host = srpl_client_update_matches(srpl_connection->candidate->name, event); |
| if (host != NULL) { |
| srpl_connection->candidate->host = host; |
| srp_adv_host_retain(srpl_connection->candidate->host); |
| return srpl_state_candidate_host_re_evaluate; |
| } |
| return srpl_state_invalid; // Keep waiting |
| } else if (event->event_type == srpl_event_advertise_finished) { |
| // See if this is an event on the host we were waiting for. |
| if (event->content.advertise_finished.hostname != NULL && |
| dns_names_equal_text(srpl_connection->candidate->name, event->content.advertise_finished.hostname)) |
| { |
| return srpl_state_candidate_host_re_evaluate; |
| } |
| return srpl_state_invalid; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // At this point we've either waited for the host to no longer be in contention, or else it wasn't in contention. |
| // There was a time gap between when we sent the candidate response and when the host message arrived, so an update |
| // may have arrived locally for that SRP client. We therefore re-evaluate at this point. |
| static srpl_state_t |
| srpl_candidate_host_re_evaluate_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS_NAME(srpl_connection, srpl_connection->candidate->name); |
| |
| adv_host_t *host = srpl_connection->candidate->host; |
| // The host we retained may have become invalid; if so, discard it |
| if (host != NULL && !srp_adv_host_valid(host)) { |
| srp_adv_host_release(srpl_connection->candidate->host); |
| srpl_connection->candidate->host = host = NULL; |
| } |
| // If it was invalidated, or if we got here directly, look up the host by name |
| if (host == NULL) { |
| host = srp_adv_host_copy(srpl_connection->instance->domain->server_state, |
| srpl_connection->candidate->name); |
| srpl_connection->candidate->host = host; |
| } |
| // It's possible that the host is gone; in this case we definitely want the update. |
| if (host == NULL) { |
| return srpl_state_candidate_host_apply; |
| } |
| |
| // At this point we know that the host we were looking for is valid. Now check to see if we still want to apply it. |
| srpl_state_t ret = srpl_state_invalid; |
| srpl_candidate_disposition_t disposition = srpl_candidate_host_check(srpl_connection, host); |
| switch(disposition) { |
| case srpl_candidate_yes: |
| ret = srpl_state_candidate_host_apply; |
| break; |
| case srpl_candidate_no: |
| // This happens if we got a candidate and wanted it, but then got an SRP update on that candidate while waiting |
| // for events. In this case, there's no real problem, and the successful update should trigger an update to be |
| // sent to the remote. |
| srpl_host_response_send(srpl_connection, dns_rcode_noerror); |
| ret = srpl_state_send_candidates_wait; |
| break; |
| case srpl_candidate_conflict: |
| srpl_host_response_send(srpl_connection, dns_rcode_yxdomain); |
| ret = srpl_state_send_candidates_wait; |
| break; |
| } |
| return ret; |
| } |
| |
| static bool |
| srpl_connection_host_apply(srpl_connection_t *srpl_connection) |
| { |
| DNS_NAME_GEN_SRP(srpl_connection->stashed_host.hostname, name_buf); |
| INFO("applying update from " PRI_S_SRP " for host " PRI_DNS_NAME_SRP " message #%d", |
| srpl_connection->name, DNS_NAME_PARAM_SRP(srpl_connection->stashed_host.hostname, name_buf), |
| srpl_connection->stashed_host.messages_processed); |
| if (!srp_dns_evaluate(NULL, srpl_connection->instance->domain->server_state, srpl_connection, |
| srpl_connection->stashed_host.messages[srpl_connection->stashed_host.messages_processed])) |
| { |
| srpl_host_response_send(srpl_connection, dns_rcode_formerr); |
| return false; |
| } |
| return true; |
| } |
| |
| // At this point we know there is no contention on the host, and we want to update it, so start the update by passing the |
| // host message to dns_evaluate. |
| static srpl_state_t |
| srpl_candidate_host_apply_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| // Apply the host from the event to the current host list |
| // Return no event |
| // Note that we set host->srpl_connection _after_ we call dns_evaluate. This ensures that any "advertise_finished" |
| // calls that are done during the call to dns_evaluate do not deliver an event here. |
| if (event == NULL) { |
| if (!srpl_connection_host_apply(srpl_connection)) { |
| return srpl_state_send_candidates_wait; |
| } |
| return srpl_state_candidate_host_apply_wait; |
| } else if (event->event_type == srpl_event_advertise_finished) { |
| // This shouldn't be possible anymore, but I'm putting a FAULT in here in case I'm mistaken. |
| FAULT(PRI_S_SRP ": advertise_finished event!", srpl_connection->name); |
| return srpl_state_invalid; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // Called by the SRP server when an advertise has finished for an update recevied on a connection. |
| static void |
| srpl_deferred_advertise_finished_event_deliver(void *context) |
| { |
| srpl_event_t *event = context; |
| srp_server_t *server_state = event->content.advertise_finished.server_state; |
| |
| for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL) { |
| srpl_event_deliver(instance->connection, event); |
| } |
| } |
| } |
| for (srpl_instance_t *instance = unmatched_instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL) { |
| srpl_event_deliver(instance->connection, event); |
| } |
| } |
| |
| free(event->content.advertise_finished.hostname); |
| free(event); |
| } |
| |
| // Send an advertise_finished event for the specified hostname to all connections. Because this is called from |
| // advertise_finished, we do not want any state machine to advance immediately, so we defer delivery of this |
| // event until the next time we return to the main event loop. |
| void |
| srpl_advertise_finished_event_send(char *hostname, int rcode, srp_server_t *server_state) |
| { |
| srpl_event_t *event = calloc(1, sizeof(*event)); |
| if (event == NULL) { |
| ERROR("No memory to defer advertise_finished event for " PUB_S_SRP, hostname); |
| return; |
| } |
| |
| srpl_event_initialize(event, srpl_event_advertise_finished); |
| event->content.advertise_finished.rcode = rcode; |
| event->content.advertise_finished.hostname = strdup(hostname); |
| event->content.advertise_finished.server_state = server_state; |
| if (event->content.advertise_finished.hostname == NULL) { |
| INFO(PRI_S_SRP ": no memory for hostname", hostname); |
| free(event); |
| return; |
| } |
| ioloop_run_async(srpl_deferred_advertise_finished_event_deliver, event); |
| } |
| |
| |
| // We enter this state to wait for the application of a host update to complete. |
| // We exit the state for the send_candidates_wait state when we receive an advertise_finished event. |
| // Additionally when we receive an advertise_finished event we send a "host" response with the rcode |
| // returned in the advertise_finished event. |
| static srpl_state_t |
| srpl_candidate_host_apply_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_advertise_finished) { |
| if (srpl_connection->stashed_host.rcode == dns_rcode_noerror) { |
| srpl_connection->stashed_host.messages_processed++; |
| if (srpl_connection->stashed_host.messages_processed < srpl_connection->stashed_host.num_messages) { |
| return srpl_state_candidate_host_apply; |
| } |
| } |
| srpl_host_response_send(srpl_connection, event->content.advertise_finished.rcode); |
| INFO("freeing parts"); |
| srpl_host_update_parts_free(&srpl_connection->stashed_host); |
| return srpl_state_send_candidates_wait; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // This marks the end of states that occur as a result of sending a "send candidates" message. |
| // This marks the beginning of states that occur as a result of receiving a send_candidates message. |
| |
| // We have received a "send candidates" message; the action is to create a candidates list. |
| static srpl_state_t |
| srpl_send_candidates_received_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| int num_candidates; |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // Make sure we don't have a candidate list. |
| if (srpl_connection->candidates != NULL) { |
| srpl_connection_candidates_free(srpl_connection); |
| srpl_connection->candidates = NULL; |
| // Just in case we exit due to a failure... |
| srpl_connection->num_candidates = 0; |
| srpl_connection->current_candidate = -1; |
| } |
| // Generate a list of candidates from the current host list. |
| // Return no event |
| srp_server_t *server_state = srpl_connection->instance->domain->server_state; |
| num_candidates = srp_current_valid_host_count(server_state); |
| if (num_candidates > 0) { |
| adv_host_t **candidates = calloc(num_candidates, sizeof(*candidates)); |
| int copied_candidates; |
| if (candidates == NULL) { |
| ERROR("unable to allocate candidates list."); |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| copied_candidates = srp_hosts_to_array(server_state, candidates, num_candidates); |
| if (copied_candidates > num_candidates) { |
| FAULT("copied_candidates %d > num_candidates %d", |
| copied_candidates, num_candidates); |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| if (num_candidates != copied_candidates) { |
| INFO("srp_hosts_to_array returned the wrong number of hosts: copied_candidates %d > num_candidates %d", |
| copied_candidates, num_candidates); |
| num_candidates = copied_candidates; |
| } |
| srpl_connection->candidates = candidates; |
| } |
| srpl_connection->candidates_not_generated = false; |
| srpl_connection->num_candidates = num_candidates; |
| srpl_connection->current_candidate = -1; |
| return srpl_state_send_candidates_remaining_check; |
| } |
| |
| // See if there are candidates remaining; if not, send "send candidates" response. |
| static srpl_state_t |
| srpl_candidates_remaining_check_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // Get the next candidate out of the candidate list |
| // Return "no candidates left" or "next candidate" |
| if (srpl_connection->current_candidate + 1 < srpl_connection->num_candidates) { |
| srpl_connection->current_candidate++; |
| return srpl_state_next_candidate_send; |
| } else { |
| return srpl_state_send_candidates_response_send; |
| } |
| } |
| |
| // Send the next candidate. |
| static srpl_state_t |
| srpl_next_candidate_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| srpl_candidate_message_send(srpl_connection, srpl_connection->candidates[srpl_connection->current_candidate]); |
| return srpl_state_next_candidate_send_wait; |
| } |
| |
| // Wait for a "candidate" response. |
| static srpl_state_t |
| srpl_next_candidate_send_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_candidate_response_received) { |
| switch (event->content.disposition) { |
| case srpl_candidate_yes: |
| return srpl_state_candidate_host_send; |
| case srpl_candidate_no: |
| case srpl_candidate_conflict: |
| return srpl_state_send_candidates_remaining_check; |
| } |
| return srpl_state_invalid; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // Send the host for the candidate. |
| static srpl_state_t |
| srpl_candidate_host_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| // It's possible that the host that we put on the candidates list has become invalid. If so, just go back and send |
| // the next candidate (or finish). |
| adv_host_t *host = srpl_connection->candidates[srpl_connection->current_candidate]; |
| if (!srp_adv_host_valid(host) || host->message == NULL) { |
| return srpl_state_send_candidates_remaining_check; |
| } |
| if (!srpl_host_message_send(srpl_connection, host)) { |
| srpl_disconnect(srpl_connection); |
| return srpl_state_invalid; |
| } |
| return srpl_state_candidate_host_response_wait; |
| } |
| |
| // Wait for a "host" response. |
| static srpl_state_t |
| srpl_candidate_host_response_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_host_response_received) { |
| // The only failure case we care about is a conflict, and we don't have a way to handle that, so just |
| // continue without checking the status. |
| return srpl_state_send_candidates_remaining_check; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // At this point we're done sending candidates, so we send a "send candidates" response. |
| static srpl_state_t |
| srpl_send_candidates_response_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| srpl_send_candidates_message_send(srpl_connection, true); |
| // When the server has sent its candidate response, it's immediately ready to send a "send candidate" message |
| // When the client has sent its candidate response, the database synchronization is done on the client. |
| if (srpl_connection->is_server) { |
| return srpl_state_send_candidates_send; |
| } else { |
| srpl_domain_t *domain = srpl_connection_domain(srpl_connection); |
| srpl_connection->database_synchronized = true; |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE && |
| srpl_connection->instance != NULL && |
| srpl_connection->instance->sync_to_join) |
| { |
| srpl_maybe_sync_or_transition(domain); |
| } |
| return srpl_state_ready; |
| } |
| } |
| |
| // The ready state is where we land when there's no remaining work to do. We wait for events, and when we get one, |
| // we handle it, ultimately returning to this state. |
| static srpl_state_t |
| srpl_ready_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| // Whenever we newly land in this state, see if there is an unsent client update at the head of the |
| // queue, and if so, send it. |
| if (srpl_connection->client_update_queue != NULL && !srpl_connection->client_update_queue->sent) { |
| adv_host_t *host = srpl_connection->client_update_queue->host; |
| if (host == NULL || host->name == NULL) { |
| INFO(PRI_S_SRP ": we have an update to send for bogus host %p.", srpl_connection->name, host); |
| } else { |
| INFO(PRI_S_SRP ": we have an update to send for host " PRI_S_SRP, srpl_connection->name, |
| srpl_connection->client_update_queue->host->name); |
| } |
| return srpl_state_srp_client_update_send; |
| } else { |
| if (srpl_connection->client_update_queue != NULL) { |
| adv_host_t *host = srpl_connection->client_update_queue->host; |
| if (host == NULL || host->name == NULL) { |
| INFO(PRI_S_SRP ": there is anupdate that's marked sent for bogus host %p.", |
| srpl_connection->name, host); |
| } else { |
| INFO(PRI_S_SRP ": there is an update on the queue that's marked sent for host " PRI_S_SRP, |
| srpl_connection->name, host->name); |
| } |
| } else { |
| INFO(PRI_S_SRP ": the client update queue is empty.", srpl_connection->name); |
| } |
| } |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_host_message_received) { |
| if (srpl_connection->stashed_host.messages != NULL) { |
| FAULT(PRI_S_SRP ": stashed host present but host message received", srpl_connection->name); |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| // Copy the update information, retain what's refcounted, and NULL out what's not, on the event. |
| srpl_host_update_steal_parts(&srpl_connection->stashed_host, &event->content.host_update); |
| return srpl_state_stashed_host_check; |
| } else if (event->event_type == srpl_event_host_response_received) { |
| return srpl_state_srp_client_ack_evaluate; |
| } else if (event->event_type == srpl_event_advertise_finished) { |
| if (srpl_connection->stashed_host.hostname != NULL && |
| event->content.advertise_finished.hostname != NULL && |
| dns_names_equal_text(srpl_connection->stashed_host.hostname, event->content.advertise_finished.hostname)) |
| { |
| srpl_connection->stashed_host.rcode = event->content.advertise_finished.rcode; |
| return srpl_state_stashed_host_finished; |
| } |
| return srpl_state_invalid; |
| } else if (event->event_type == srpl_event_srp_client_update_finished) { |
| // When we receive a client update in ready state, we just need to re-run the state's action. |
| return srpl_state_ready; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // We get here when there is at least one client update queued up to send |
| static srpl_state_t |
| srpl_srp_client_update_send_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| srpl_srp_client_queue_entry_t *update = srpl_connection->client_update_queue; |
| if (update != NULL) { |
| // If the host has a message, send it. Note that this host may well be removed, but if it had been removed |
| // through a lease expiry we wouldn't have got here, because the host object would have been removed from |
| // the list. So if it has a message attached to it, that means that either it's been removed explicitly by |
| // the client, which we need to propagate, or else it is still valid, and so we need to propagate the most |
| // recent update we got. |
| if (update->host->message != NULL) { |
| srpl_host_message_send(srpl_connection, update->host); |
| update->sent = true; |
| } else { |
| ERROR(PRI_S_SRP ": no host message to send for host " PRI_S_SRP ".", |
| srpl_connection->name, update->host->name); |
| |
| // We're not going to send this update, so take it off the queue. |
| srpl_connection->client_update_queue = update->next; |
| srp_adv_host_release(update->host); |
| free(update); |
| } |
| } |
| return srpl_state_ready; |
| } |
| |
| // We go here when we get a "host" response; all we do is remove the host from the top of the queue. |
| static srpl_state_t |
| srpl_srp_client_ack_evaluate_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| if (srpl_connection->client_update_queue == NULL) { |
| FAULT(PRI_S_SRP ": update queue empty in ready, but host_response_received event received.", |
| srpl_connection->name); |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| if (!srpl_connection->client_update_queue->sent) { |
| FAULT(PRI_S_SRP ": top of update queue not sent, but host_response_received event received.", |
| srpl_connection->name); |
| return srpl_connection_drop_state(srpl_connection->instance, srpl_connection); |
| } |
| srpl_srp_client_queue_entry_t *finished_update = srpl_connection->client_update_queue; |
| srpl_connection->client_update_queue = finished_update->next; |
| if (finished_update->host != NULL) { |
| srp_adv_host_release(finished_update->host); |
| } |
| free(finished_update); |
| return srpl_state_ready; |
| } |
| |
| // We go here when we get a "host" message |
| static srpl_state_t |
| srpl_stashed_host_check_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS_NAME(srpl_connection, srpl_connection->stashed_host.hostname); |
| |
| adv_host_t *host = srp_adv_host_copy(srpl_connection->instance->domain->server_state, |
| srpl_connection->stashed_host.hostname); |
| // No contention... |
| if (host == NULL) { |
| INFO("applying host because it doesn't exist locally."); |
| return srpl_state_stashed_host_apply; |
| } else if (host->update == NULL && host->srpl_connection == NULL) { |
| INFO("applying host because there's no contention."); |
| srp_adv_host_release(host); |
| return srpl_state_stashed_host_apply; |
| } else { |
| INFO("not applying host because there is contention. host->update %p host->srpl_connection: %p", |
| host->update, host->srpl_connection); |
| } |
| srp_adv_host_release(host); |
| return srpl_state_ready; // Wait for something to happen |
| } |
| |
| // We go here when we have a stashed host to apply. |
| static srpl_state_t |
| srpl_stashed_host_apply_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| if (!srpl_connection_host_apply(srpl_connection)) { |
| srpl_connection->stashed_host.rcode = dns_rcode_servfail; |
| return srpl_state_stashed_host_finished; |
| } |
| return srpl_state_ready; // Wait for something to happen |
| } else if (event->event_type == srpl_event_advertise_finished) { |
| // This shouldn't be possible anymore, but I'm putting a FAULT in here in case I'm mistaken. |
| FAULT(PRI_S_SRP ": advertise_finished event!", srpl_connection->name); |
| return srpl_state_invalid; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // We go here when a host update advertise finishes. |
| static srpl_state_t |
| srpl_stashed_host_finished_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| if (srpl_connection->stashed_host.hostname == NULL) { |
| FAULT(PRI_S_SRP ": stashed host not present, but advertise_finished event received.", srpl_connection->name); |
| return srpl_state_ready; |
| } |
| if (srpl_connection->stashed_host.messages == NULL) { |
| FAULT(PRI_S_SRP ": stashed host present, no messages.", srpl_connection->name); |
| return srpl_state_ready; |
| } |
| if (srpl_connection->stashed_host.rcode == dns_rcode_noerror) { |
| srpl_connection->stashed_host.messages_processed++; |
| if (srpl_connection->stashed_host.messages_processed < srpl_connection->stashed_host.num_messages) { |
| return srpl_state_stashed_host_apply; |
| } |
| } |
| srpl_host_response_send(srpl_connection, srpl_connection->stashed_host.rcode); |
| INFO("freeing parts"); |
| srpl_host_update_parts_free(&srpl_connection->stashed_host); |
| return srpl_state_ready; |
| } |
| |
| // We land here immediately after a server connection is received. |
| static srpl_state_t |
| srpl_session_message_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_session_message_received) { |
| srpl_connection->remote_partner_id = event->content.session.partner_id; |
| srpl_connection->new_partner = event->content.session.new_partner; |
| return srpl_state_session_evaluate; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // Send a session response |
| static srpl_state_t |
| srpl_session_response_send(srpl_connection_t *UNUSED srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_EVENT_NULL(srpl_connection, event); |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE_NO_EVENTS(srpl_connection); |
| |
| if (!srpl_session_message_send(srpl_connection, true)) { |
| return srpl_state_disconnect; |
| } |
| return srpl_state_send_candidates_message_wait; |
| } |
| |
| // We land here immediately after a server connection is received. |
| static srpl_state_t |
| srpl_send_candidates_message_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| REQUIRE_SRPL_INSTANCE(srpl_connection); |
| STATE_ANNOUNCE(srpl_connection, event); |
| |
| if (event == NULL) { |
| return srpl_state_invalid; // Wait for events. |
| } else if (event->event_type == srpl_event_send_candidates_message_received) { |
| return srpl_state_send_candidates_received; |
| } else { |
| UNEXPECTED_EVENT(srpl_connection, event); |
| } |
| } |
| |
| // Check to see if host is on the list of remaining candidates to send. If so, no need to do anything--it'll go out soon. |
| static bool |
| srpl_reschedule_candidate(srpl_connection_t *srpl_connection, adv_host_t *host) |
| { |
| // We don't need to queue new updates if we haven't yet generated a candidates list. |
| if (srpl_connection->candidates_not_generated) { |
| INFO("returning true because we haven't generated candidates."); |
| return true; |
| } |
| if (srpl_connection->candidates == NULL) { |
| INFO("returning false because we have no candidates."); |
| return false; |
| } |
| for (int i = srpl_connection->current_candidate + 1; i < srpl_connection->num_candidates; i++) { |
| if (srpl_connection->candidates[i] == host) { |
| INFO("returning true because the host is on the candidate list."); |
| return true; |
| } |
| } |
| INFO("returning false because the host is not on the candidate list."); |
| return false; |
| } |
| |
| static void |
| srpl_queue_srp_client_update(srpl_connection_t *srpl_connection, adv_host_t *host) |
| { |
| srpl_srp_client_queue_entry_t *new_entry, **qp; |
| // Find the end of the queue |
| for (qp = &srpl_connection->client_update_queue; *qp; qp = &(*qp)->next) { |
| srpl_srp_client_queue_entry_t *entry = *qp; |
| // No need to re-queue if we're already on the queue |
| if (!entry->sent && entry->host == host) { |
| INFO("host " PRI_S_SRP " is already on the update queue for connection " PRI_S_SRP, |
| host->name, srpl_connection->name); |
| return; |
| } |
| } |
| new_entry = calloc(1, sizeof(*new_entry)); |
| if (new_entry == NULL) { |
| ERROR(PRI_S_SRP ": no memory to queue SRP client update.", srpl_connection->name); |
| return; |
| } |
| INFO("adding host " PRI_S_SRP " to the update queue for connection " PRI_S_SRP, host->name, srpl_connection->name); |
| new_entry->host = host; |
| srp_adv_host_retain(new_entry->host); |
| *qp = new_entry; |
| } |
| |
| // Client update events are interesting in two cases. First, we might have received a host update for a |
| // host that was in contention when the update was received; in this case, we want to now apply the update, |
| // assuming that the contention is no longer present (it's possible that there are multiple sources of |
| // contention). |
| // |
| // The second case is where a client update succeeded; in this case we want to send that update to all of |
| // the remotes. |
| // |
| // We do not receive this event when an update that was triggered by an SRP Replication update; in that |
| // case we get an "apply finished" event instead of a "client update finished" event. |
| static void |
| srpl_srp_client_update_send_event_to_connection(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| if (event->content.client_result.rcode == dns_rcode_noerror) { |
| adv_host_t *host = event->content.client_result.host; |
| if (!srpl_reschedule_candidate(srpl_connection, host)) { |
| srpl_queue_srp_client_update(srpl_connection, host); |
| } |
| } |
| srpl_event_deliver(srpl_connection, event); |
| } |
| |
| static void |
| srpl_deferred_srp_client_update_finished_event_deliver(void *context) |
| { |
| srpl_event_t *event = context; |
| srp_server_t *server_state = event->content.client_result.host->server_state; |
| if (server_state == NULL) { |
| FAULT("server state is NULL."); // this can't currently happen, because we just finished updating the host. |
| goto out; |
| } |
| for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) { |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL) { |
| srpl_srp_client_update_send_event_to_connection(instance->connection, event); |
| } |
| } |
| } |
| for (srpl_instance_t *instance = unmatched_instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL) { |
| srpl_srp_client_update_send_event_to_connection(instance->connection, event); |
| } |
| } |
| out: |
| srp_adv_host_release(event->content.client_result.host); |
| free(event); |
| } |
| |
| // When an SRP client update finished, we need to deliver an event to all connections indicating that this has |
| // occurred. This event must be delivered from the main run loop, to avoid starting an update before advertise_finish |
| // has completed its work. |
| void |
| srpl_srp_client_update_finished_event_send(adv_host_t *host, int rcode) |
| { |
| srpl_event_t *event; |
| event = malloc(sizeof(*event)); |
| if (event == NULL) { |
| FAULT(PRI_S_SRP ": unable to allocate memory to defer event", host->name); |
| return; |
| } |
| srpl_event_initialize(event, srpl_event_srp_client_update_finished); |
| srpl_event_content_type_set(event, srpl_event_content_type_client_result); |
| event->content.client_result.host = host; |
| srp_adv_host_retain(event->content.client_result.host); |
| event->content.client_result.rcode = rcode; |
| ioloop_run_async(srpl_deferred_srp_client_update_finished_event_deliver, event); |
| } |
| |
| typedef struct { |
| srpl_state_t state; |
| char *name; |
| srpl_action_t action; |
| } srpl_connection_state_t; |
| |
| #define STATE_NAME_DECL(name) srpl_state_##name, #name |
| static srpl_connection_state_t srpl_connection_states[] = { |
| { STATE_NAME_DECL(invalid), NULL }, |
| { STATE_NAME_DECL(disconnected), srpl_disconnected_action }, |
| { STATE_NAME_DECL(next_address_get), srpl_next_address_get_action }, |
| { STATE_NAME_DECL(connect), srpl_connect_action }, |
| { STATE_NAME_DECL(idle), srpl_idle_action }, |
| { STATE_NAME_DECL(reconnect_wait), srpl_reconnect_wait_action }, |
| { STATE_NAME_DECL(retry_delay_send), srpl_retry_delay_send_action }, |
| { STATE_NAME_DECL(disconnect), srpl_disconnect_action }, |
| { STATE_NAME_DECL(disconnect_wait), srpl_disconnect_wait_action }, |
| // If a disconnected state is added here, please fix SRPL_CONNECTION_IS_CONNECTED above. |
| // connecting is counted as a connected state because we have a connection, even if it is not |
| // actually connected, and we'll get a disconnect if it fails, so we aren't stuck. |
| { STATE_NAME_DECL(connecting), srpl_connecting_action }, |
| { STATE_NAME_DECL(session_send), srpl_session_send_action }, |
| { STATE_NAME_DECL(session_response_wait), srpl_session_response_wait_action }, |
| { STATE_NAME_DECL(session_evaluate), srpl_session_evaluate_action }, |
| { STATE_NAME_DECL(sync_wait), srpl_sync_wait_action }, |
| // Here we are the endpoint that has send the "send candidates message" and we are cycling through the candidates |
| // we receive until we get a "send candidates" reply. |
| |
| { STATE_NAME_DECL(send_candidates_send), srpl_send_candidates_send_action }, |
| { STATE_NAME_DECL(send_candidates_wait), srpl_send_candidates_wait_action }, |
| |
| // Got a "candidate" message, need to check it and send the right reply. |
| { STATE_NAME_DECL(candidate_check), srpl_candidate_check_action }, |
| |
| // At this point we've send a candidate reply, so we're waiting for a host message. It's possible that the host |
| // went away in the interim, in which case we will get a "candidate" message or a "send candidate" reply. |
| |
| { STATE_NAME_DECL(candidate_host_wait), srpl_candidate_host_wait_action }, |
| { STATE_NAME_DECL(candidate_host_prepare), srpl_candidate_host_prepare_action }, |
| { STATE_NAME_DECL(candidate_host_contention_wait), srpl_candidate_host_contention_wait_action }, |
| { STATE_NAME_DECL(candidate_host_re_evaluate), srpl_candidate_host_re_evaluate_action }, |
| |
| // Here we've gotten the host message (the SRP message), and need to apply it and send a response |
| { STATE_NAME_DECL(candidate_host_apply), srpl_candidate_host_apply_action }, |
| { STATE_NAME_DECL(candidate_host_apply_wait), srpl_candidate_host_apply_wait_action }, |
| |
| // We've received a "send candidates" message. Make a list of candidates to send, and then start sending them. |
| { STATE_NAME_DECL(send_candidates_received), srpl_send_candidates_received_action }, |
| // See if there are any candidates left to send; if not, go to send_candidates_response_send |
| { STATE_NAME_DECL(send_candidates_remaining_check), srpl_candidates_remaining_check_action }, |
| // Send a "candidate" message for the next candidate |
| { STATE_NAME_DECL(next_candidate_send), srpl_next_candidate_send_action }, |
| // Wait for a response to the "candidate" message |
| { STATE_NAME_DECL(next_candidate_send_wait), srpl_next_candidate_send_wait_action }, |
| // The candidate requested, so send its host info |
| { STATE_NAME_DECL(candidate_host_send), srpl_candidate_host_send_action }, |
| // We're waiting for the remote to acknowledge the host update |
| { STATE_NAME_DECL(candidate_host_response_wait), srpl_candidate_host_response_wait_action }, |
| |
| // When we've run out of candidates to send, we send the candidates response. |
| { STATE_NAME_DECL(send_candidates_response_send), srpl_send_candidates_response_send_action }, |
| |
| // This is the quiescent state for servers and clients after session establishment database sync. |
| // Waiting for updates received locally, or updates sent by remote |
| { STATE_NAME_DECL(ready), srpl_ready_action }, |
| // An update was received locally |
| { STATE_NAME_DECL(srp_client_update_send), srpl_srp_client_update_send_action }, |
| // We've gotten an ack |
| { STATE_NAME_DECL(srp_client_ack_evaluate), srpl_srp_client_ack_evaluate_action }, |
| // See if we have an update from the remote that we stashed because it arrived while we were sending one |
| { STATE_NAME_DECL(stashed_host_check), srpl_stashed_host_check_action }, |
| // Apply a stashed update (which may have been stashed in the ready state or the client_update_ack_wait state |
| { STATE_NAME_DECL(stashed_host_apply), srpl_stashed_host_apply_action }, |
| // A stashed update finished; check the results |
| { STATE_NAME_DECL(stashed_host_finished), srpl_stashed_host_finished_action }, |
| |
| // Initial startup state for server |
| { STATE_NAME_DECL(session_message_wait), srpl_session_message_wait_action }, |
| // Send a response once we've figured out that we're going to continue |
| { STATE_NAME_DECL(session_response_send), srpl_session_response_send }, |
| // Wait for a "send candidates" message. |
| { STATE_NAME_DECL(send_candidates_message_wait), srpl_send_candidates_message_wait_action }, |
| }; |
| #define SRPL_NUM_CONNECTION_STATES (sizeof(srpl_connection_states) / sizeof(srpl_connection_state_t)) |
| |
| static srpl_connection_state_t * |
| srpl_state_get(srpl_state_t state) |
| { |
| static bool once = false; |
| if (!once) { |
| for (unsigned i = 0; i < SRPL_NUM_CONNECTION_STATES; i++) { |
| if (srpl_connection_states[i].state != (srpl_state_t)i) { |
| ERROR("srpl connection state %d doesn't match " PUB_S_SRP, i, srpl_connection_states[i].name); |
| STATE_DEBUGGING_ABORT(); |
| return NULL; |
| } |
| } |
| once = true; |
| } |
| if (state < 0 || state >= SRPL_NUM_CONNECTION_STATES) { |
| STATE_DEBUGGING_ABORT(); |
| return NULL; |
| } |
| return &srpl_connection_states[state]; |
| } |
| |
| static void |
| srpl_connection_next_state(srpl_connection_t *srpl_connection, srpl_state_t state) |
| { |
| srpl_state_t next_state = state; |
| |
| do { |
| srpl_connection_state_t *new_state = srpl_state_get(next_state); |
| |
| if (new_state == NULL) { |
| ERROR(PRI_S_SRP " next state is invalid: %d", srpl_connection->name, next_state); |
| STATE_DEBUGGING_ABORT(); |
| return; |
| } |
| srpl_connection->state = next_state; |
| srpl_connection->state_name = new_state->name; |
| srpl_action_t action = new_state->action; |
| if (action != NULL) { |
| next_state = action(srpl_connection, NULL); |
| } |
| } while (next_state != srpl_state_invalid); |
| } |
| |
| // |
| // Event functions |
| // |
| |
| typedef struct { |
| srpl_event_type_t event_type; |
| char *name; |
| } srpl_event_configuration_t; |
| |
| #define EVENT_NAME_DECL(name) { srpl_event_##name, #name } |
| |
| srpl_event_configuration_t srpl_event_configurations[] = { |
| EVENT_NAME_DECL(invalid), |
| EVENT_NAME_DECL(address_add), |
| EVENT_NAME_DECL(address_remove), |
| EVENT_NAME_DECL(server_disconnect), |
| EVENT_NAME_DECL(reconnect_timer_expiry), |
| EVENT_NAME_DECL(disconnected), |
| EVENT_NAME_DECL(connected), |
| EVENT_NAME_DECL(session_response_received), |
| EVENT_NAME_DECL(send_candidates_response_received), |
| EVENT_NAME_DECL(candidate_received), |
| EVENT_NAME_DECL(host_message_received), |
| EVENT_NAME_DECL(srp_client_update_finished), |
| EVENT_NAME_DECL(advertise_finished), |
| EVENT_NAME_DECL(candidate_response_received), |
| EVENT_NAME_DECL(host_response_received), |
| EVENT_NAME_DECL(session_message_received), |
| EVENT_NAME_DECL(send_candidates_message_received), |
| EVENT_NAME_DECL(do_sync), |
| }; |
| #define SRPL_NUM_EVENT_TYPES (sizeof(srpl_event_configurations) / sizeof(srpl_event_configuration_t)) |
| |
| static srpl_event_configuration_t * |
| srpl_event_configuration_get(srpl_event_type_t event) |
| { |
| static bool once = false; |
| if (!once) { |
| for (unsigned i = 0; i < SRPL_NUM_EVENT_TYPES; i++) { |
| if (srpl_event_configurations[i].event_type != (srpl_event_type_t)i) { |
| ERROR("srpl connection event %d doesn't match " PUB_S_SRP, i, srpl_event_configurations[i].name); |
| STATE_DEBUGGING_ABORT(); |
| return NULL; |
| } |
| } |
| once = true; |
| } |
| if (event < 0 || event >= SRPL_NUM_EVENT_TYPES) { |
| STATE_DEBUGGING_ABORT(); |
| return NULL; |
| } |
| return &srpl_event_configurations[event]; |
| } |
| |
| static const char * |
| srpl_state_name(srpl_state_t state) |
| { |
| for (unsigned i = 0; i < SRPL_NUM_CONNECTION_STATES; i++) { |
| if (srpl_connection_states[i].state == state) { |
| return srpl_connection_states[i].name; |
| } |
| } |
| return "unknown state"; |
| } |
| |
| static void |
| srpl_event_initialize(srpl_event_t *event, srpl_event_type_t event_type) |
| { |
| memset(event, 0, sizeof(*event)); |
| srpl_event_configuration_t *event_config = srpl_event_configuration_get(event_type); |
| if (event_config == NULL) { |
| ERROR("invalid event type %d", event_type); |
| STATE_DEBUGGING_ABORT(); |
| return; |
| } |
| event->event_type = event_type; |
| event->name = event_config->name; |
| } |
| |
| static void |
| srpl_event_deliver(srpl_connection_t *srpl_connection, srpl_event_t *event) |
| { |
| srpl_connection_state_t *state = srpl_state_get(srpl_connection->state); |
| if (state == NULL) { |
| ERROR(PRI_S_SRP ": event " PUB_S_SRP " received in invalid state %d", |
| srpl_connection->name, event->name, srpl_connection->state); |
| STATE_DEBUGGING_ABORT(); |
| return; |
| } |
| if (state->action == NULL) { |
| FAULT(PRI_S_SRP": event " PUB_S_SRP " received in state " PUB_S_SRP " with NULL action", |
| srpl_connection->name, event->name, state->name); |
| return; |
| } |
| srpl_state_t next_state = state->action(srpl_connection, event); |
| if (next_state != srpl_state_invalid) { |
| srpl_connection_next_state(srpl_connection, next_state); |
| } |
| } |
| |
| static void |
| srpl_re_register(void *context) |
| { |
| INFO("re-registering SRPL service"); |
| srpl_domain_advertise(context); |
| } |
| |
| static void |
| srpl_register_completion(DNSServiceRef UNUSED sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code, |
| const char *name, const char *regtype, const char *domain, void *context) |
| { |
| srpl_domain_t *srpl_domain = context; |
| |
| if (error_code != kDNSServiceErr_NoError) { |
| ERROR("unable to advertise _srpl-tls._tcp service: %d", error_code); |
| if (srpl_domain->srpl_register_wakeup == NULL) { |
| srpl_domain->srpl_register_wakeup = ioloop_wakeup_create(); |
| } |
| if (srpl_domain->srpl_register_wakeup != NULL) { |
| // Try registering again in one second. |
| ioloop_add_wake_event(srpl_domain->srpl_register_wakeup, srpl_domain, srpl_re_register, NULL, 1000); |
| } |
| return; |
| } |
| INFO("registered SRP Replication instance name " PRI_S_SRP "." PUB_S_SRP "." PRI_S_SRP, name, regtype, domain); |
| } |
| |
| static void |
| srpl_domain_advertise(srpl_domain_t *domain) |
| { |
| DNSServiceRef sdref = NULL; |
| TXTRecordRef txt_record; |
| char partner_id_buf[INT64_HEX_STRING_MAX]; |
| char dataset_id_buf[INT64_HEX_STRING_MAX]; |
| char xpanid_buf[INT64_HEX_STRING_MAX]; |
| srp_server_t *server_state = domain->server_state; |
| |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) { |
| goto exit; |
| } |
| |
| TXTRecordCreate(&txt_record, 0, NULL); |
| |
| int err = TXTRecordSetValue(&txt_record, "dn", strlen(server_state->current_thread_domain_name), domain->name); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to set domain in TXT record for _srpl-tls._tcp to " PRI_S_SRP, domain->name); |
| goto exit; |
| } |
| |
| snprintf(partner_id_buf, sizeof(partner_id_buf), "%" PRIx64, domain->partner_id); |
| err = TXTRecordSetValue(&txt_record, "pid", strlen(partner_id_buf), partner_id_buf); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to set partner-id in TXT record for _srpl-tls._tcp to " PUB_S_SRP, partner_id_buf); |
| goto exit; |
| } |
| |
| snprintf(dataset_id_buf, sizeof(dataset_id_buf), "%" PRIx64, domain->dataset_id); |
| err = TXTRecordSetValue(&txt_record, "did", strlen(dataset_id_buf), dataset_id_buf); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to set dataset-id in TXT record for _srpl-tls._tcp to " PUB_S_SRP, dataset_id_buf); |
| goto exit; |
| } |
| |
| snprintf(xpanid_buf, sizeof(xpanid_buf), "%" PRIx64, domain->server_state->xpanid); |
| err = TXTRecordSetValue(&txt_record, "xpanid", strlen(xpanid_buf), xpanid_buf); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to set xpanid in TXT record for _srpl-tls._tcp to " PUB_S_SRP, dataset_id_buf); |
| goto exit; |
| } |
| |
| // If there is already a registration, get rid of it |
| if (domain->srpl_advertise_txn != NULL) { |
| ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn); |
| ioloop_dnssd_txn_release(domain->srpl_advertise_txn); |
| domain->srpl_advertise_txn = NULL; |
| } |
| |
| err = DNSServiceRegister(&sdref, kDNSServiceFlagsUnique, |
| kDNSServiceInterfaceIndexAny, NULL, "_srpl-tls._tcp", NULL, |
| NULL, htons(853), TXTRecordGetLength(&txt_record), TXTRecordGetBytesPtr(&txt_record), |
| srpl_register_completion, domain); |
| if (err != kDNSServiceErr_NoError) { |
| ERROR("unable to advertise _srpl-tls._tcp service"); |
| goto exit; |
| } |
| domain->srpl_advertise_txn = ioloop_dnssd_txn_add(sdref, NULL, NULL, NULL); |
| if (domain->srpl_advertise_txn == NULL) { |
| ERROR("unable to set up a dnssd_txn_t for _srpl-tls._tcp advertisement."); |
| goto exit; |
| } |
| sdref = NULL; // srpl_advertise_txn holds the reference. |
| |
| exit: |
| if (sdref != NULL) { |
| DNSServiceRefDeallocate(sdref); |
| } |
| TXTRecordDeallocate(&txt_record); |
| return; |
| } |
| |
| static void |
| srpl_thread_network_name_callback(void *NULLABLE context, const char *NULLABLE thread_network_name, cti_status_t status) |
| { |
| size_t thread_domain_size; |
| char domain_buf[kDNSServiceMaxDomainName]; |
| char *new_thread_domain_name; |
| srp_server_t *server_state = context; |
| |
| if (thread_network_name == NULL || status != kCTIStatus_NoError) { |
| ERROR("unable to get thread network name."); |
| return; |
| } |
| thread_domain_size = snprintf(domain_buf, sizeof(domain_buf), |
| "%s.%s", thread_network_name, SRP_THREAD_DOMAIN); |
| if (thread_domain_size < 0 || thread_domain_size >= sizeof (domain_buf) || |
| (new_thread_domain_name = strdup(domain_buf)) == NULL) |
| { |
| ERROR("no memory for new thread network name: " PRI_S_SRP, thread_network_name); |
| return; |
| } |
| |
| if (server_state->current_thread_domain_name != NULL) { |
| srpl_domain_rename(server_state->current_thread_domain_name, new_thread_domain_name); |
| } |
| srpl_domain_add(server_state, new_thread_domain_name); |
| free(server_state->current_thread_domain_name); |
| server_state->current_thread_domain_name = new_thread_domain_name; |
| } |
| |
| // If the partner does not discover any other SRPL partners to synchronize with, |
| // or it has synchronized with all the partners discovered so far, it transitions |
| // out of the "startup" state to the "routine operation" state. |
| static void |
| srpl_partner_discovery_timeout(void *context) |
| { |
| srpl_domain_t *domain = context; |
| |
| INFO("partner discovery timeout."); |
| |
| domain->partner_discovery_pending = false; |
| if (domain->partner_discovery_timeout != NULL) { |
| ioloop_cancel_wake_event(domain->partner_discovery_timeout); |
| ioloop_wakeup_release(domain->partner_discovery_timeout); |
| domain->partner_discovery_timeout = NULL; |
| } |
| |
| srpl_maybe_sync_or_transition(domain); |
| } |
| |
| // We check how many active srp servers we discovered. If less than 5, we first |
| // check if we are ready to enter the routine state. If there are still srp servers |
| // that we haven't started wo sync with, we do so. |
| static void |
| srpl_maybe_sync_or_transition(srpl_domain_t *domain) |
| { |
| int num_peers = 0; |
| |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL && |
| instance->connection->state > srpl_state_session_evaluate) |
| { |
| num_peers++; |
| } |
| } |
| |
| if (num_peers < MAX_ANYCAST_NUM) { |
| INFO("%d other srp servers are advertising.", num_peers); |
| if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE && |
| srpl_can_transition_to_routine_state(domain)) |
| { |
| srpl_transition_to_routine_state(domain); |
| } |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL && |
| instance->connection->state == srpl_state_sync_wait) |
| { |
| srpl_event_t event; |
| srpl_event_initialize(&event, srpl_event_do_sync); |
| srpl_event_deliver(instance->connection, &event); |
| } |
| } |
| } else { |
| INFO("%d other srp servers are advertising.", num_peers); |
| } |
| } |
| |
| static void |
| srpl_transition_to_startup_state(srpl_domain_t *domain) |
| { |
| srp_server_t *server_state = domain->server_state; |
| |
| // stop advertising the domain. |
| srpl_stop_srpl_domain_advertisement(domain); |
| // move to "startup" state. |
| domain->srpl_opstate = SRPL_OPSTATE_STARTUP; |
| |
| if (server_state == NULL) { |
| ERROR("server state is NULL."); |
| return; |
| } |
| |
| #if STUB_ROUTER |
| route_state_t *route_state = NULL; |
| route_state = server_state->route_state; |
| if (route_state != NULL && !strcmp(domain->name, server_state->current_thread_domain_name)) { |
| route_state->partition_can_advertise_anycast_service = false; |
| partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number); |
| } |
| #endif |
| if (domain->partner_discovery_timeout == NULL) { |
| domain->partner_discovery_timeout = ioloop_wakeup_create(); |
| } |
| if (domain->partner_discovery_timeout) { |
| ioloop_add_wake_event(domain->partner_discovery_timeout, domain, |
| srpl_partner_discovery_timeout, NULL, |
| MIN_PARTNER_DISCOVERY_INTERVAL + |
| srp_random16() % PARTNER_DISCOVERY_INTERVAL_RANGE); |
| } else { |
| ERROR("unable to add wakeup event for partner discovery."); |
| return; |
| } |
| domain->partner_discovery_pending = true; |
| |
| // Existing connections represent a previous dataset ID. We need to resynchronize with these |
| // instances because we have a new dataset ID. |
| for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) { |
| if (instance->connection != NULL) { |
| srpl_connection_discontinue(instance->connection); |
| RELEASE_HERE(instance->connection, srpl_connection); |
| instance->connection = NULL; |
| } |
| } |
| } |
| |
| void |
| srpl_startup(srp_server_t *server_state) |
| { |
| cti_get_thread_network_name(server_state, srpl_thread_network_name_callback, NULL); |
| } |
| #endif // SRP_FEATURE_REPLICATION |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 120 |
| // indent-tabs-mode: nil |
| // End: |