blob: 532e08460ee15c0a12089b92817dafb7fcfe548c [file] [log] [blame]
/* srp-replication.c
*
* Copyright (c) 2020-2022 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 "srp.h"
#include "dns-msg.h"
#include "srp-crypto.h"
#include "ioloop.h"
#include "dnssd-proxy.h"
#include "srp-gw.h"
#include "srp-proxy.h"
#include "srp-mdns-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"
static char *current_thread_domain_name;
static unclaimed_connection_t *unclaimed_connections;
#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(srp_server_t *server_state);
static void srpl_connection_finalize(srpl_connection_t *srpl_connection);
static void srpl_instance_reconnect(void *context);
static bool srpl_domain_browse_start(srpl_domain_t *domain);
static const char *srpl_state_name(srpl_state_t state);
#ifdef DEBUG
#define STATE_DEBUGGING_ABORT() abort();
#else
#define STATE_DEBUGGING_ABORT()
#endif
// Return continuous time, if provided by O.S., otherwise unadjusted time.
time_t srpl_time(void)
{
#ifdef CLOCK_BOOTTIME
// CLOCK_BOOTTIME is a Linux-specific constant that indicates a monotonic time that includes time asleep
const int clockid = CLOCK_BOOTTIME;
#elif defined(CLOCK_MONOTONIC_RAW)
// On MacOS, CLOCK_MONOTONIC_RAW is a monotonic time that includes time asleep and is not adjusted.
// According to the man page, CLOCK_MONOTONIC on MacOS violates the POSIX spec in that it can be adjusted.
const int clockid = CLOCK_MONOTONIC_RAW;
#else
// On other Posix systems, CLOCK_MONOTONIC should be the right thing, at least according to the POSIX spec.
const int clockid = CLOCK_MONOTONIC;
#endif
struct timespec tm;
clock_gettime(clockid, &tm);
// We are only accurate to the second.
return tm.tv_sec;
}
//
// 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 (!memcmp(&address->addresses[i], &addr, sizeof(addr))) {
if (flags & kDNSServiceFlagsAdd) {
if (address->address_interface[i] == interfaceIndex) {
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_finalize);
}
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); // 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); // For the QueryRecord context
}
address->change_callback = change_callback;
address->cancel_callback = cancel_callback;
address->context = context;
address->cur_address = -1;
return address;
exit:
RELEASE_HERE(address, address_query_finalize);
address = NULL;
exit_no_free:
return address;
}
static void
srpl_domain_finalize(srpl_domain_t *domain)
{
free(domain->name);
if (domain->query != NULL) {
ioloop_dnssd_txn_cancel(domain->query);
ioloop_dnssd_txn_release(domain->query);
}
free(domain);
}
static void
srpl_instance_finalize(srpl_instance_t *instance)
{
if (instance->resolve_txn != NULL) {
ioloop_dnssd_txn_cancel(instance->resolve_txn);
ioloop_dnssd_txn_release(instance->resolve_txn);
instance->resolve_txn = NULL;
}
if (instance->domain != NULL) {
RELEASE_HERE(instance->domain, srpl_domain_finalize);
}
free(instance->instance_name);
free(instance->name);
if (instance->incoming != NULL) {
srpl_connection_discontinue(instance->incoming);
RELEASE_HERE(instance->incoming, srpl_connection_finalize);
}
if (instance->outgoing != NULL) {
srpl_connection_discontinue(instance->outgoing);
RELEASE_HERE(instance->outgoing, srpl_connection_finalize);
}
if (instance->address_query != NULL) {
address_query_cancel(instance->address_query);
RELEASE_HERE(instance->address_query, address_query_finalize);
}
if (instance->discontinue_timeout != NULL) {
ioloop_cancel_wake_event(instance->discontinue_timeout);
ioloop_wakeup_release(instance->discontinue_timeout);
instance->discontinue_timeout = NULL;
}
if (instance->reconnect_timeout != NULL) {
ioloop_cancel_wake_event(instance->reconnect_timeout);
ioloop_wakeup_release(instance->reconnect_timeout);
instance->reconnect_timeout = 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->message != NULL) {
ioloop_message_release(update->message);
update->message = NULL;
}
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;
}
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_finalize);
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);
srpl_connection->reconnect_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_finalize);
}
void
srpl_connection_retain_(srpl_connection_t *srpl_connection, const char *file, int line)
{
RETAIN(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));
size_t srpl_connection_name_length;
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) {
free(srpl_connection);
return NULL;
}
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);
RETAIN_HERE(srpl_connection);
return srpl_connection;
}
static void
srpl_connection_context_release(void *context)
{
srpl_connection_t *srpl_connection = context;
RELEASE_HERE(srpl_connection, srpl_connection_finalize);
}
static void
srpl_instance_context_release(void *context)
{
srpl_instance_t *instance = context;
RELEASE_HERE(instance, srpl_instance_finalize);
}
static void
srpl_instance_discontinue_timeout(void *context)
{
srpl_instance_t **sp = NULL, *instance = context;
INFO("discontinuing instance " PRI_S_SRP, instance->instance_name);
for (sp = &instance->domain->instances; *sp; sp = &(*sp)->next) {
if (*sp == instance) {
*sp = instance->next;
break;
}
}
if (instance->discontinue_timeout != NULL) {
ioloop_cancel_wake_event(instance->discontinue_timeout);
ioloop_wakeup_release(instance->discontinue_timeout);
instance->discontinue_timeout = NULL;
}
if (instance->address_query != NULL) {
address_query_cancel(instance->address_query);
RELEASE_HERE(instance->address_query, address_query_finalize);
instance->address_query = NULL;
}
for (int i = 0; i < 2; i++) {
srpl_connection_t **cp = i ? &instance->incoming : &instance->outgoing;
srpl_connection_t *srpl_connection = *cp;
*cp = NULL;
if (srpl_connection == NULL) {
continue;
}
RELEASE_HERE(srpl_connection->instance, srpl_instance_finalize);
srpl_connection->instance = NULL;
if (srpl_connection->connection != NULL) {
srpl_connection_discontinue(srpl_connection);
}
// The instance no longer has a reference to the srpl_connection object.
RELEASE_HERE(srpl_connection, srpl_connection_finalize);
}
if (instance->resolve_txn != NULL) {
ioloop_dnssd_txn_cancel(instance->resolve_txn);
ioloop_dnssd_txn_release(instance->resolve_txn);
instance->resolve_txn = NULL;
}
RELEASE_HERE(instance, srpl_instance_finalize);
}
static void
srpl_instance_discontinue(srpl_instance_t *instance)
{
// Already discontinuing.
if (instance->discontinuing) {
INFO("Replication service instance " PRI_S_SRP " went away, already discontinuing", instance->instance_name);
return;
}
if (instance->num_copies > 0) {
INFO("Replication service instance " PRI_S_SRP " went away, %d still left", instance->name, instance->num_copies);
return;
}
INFO("Replication service instance " PRI_S_SRP " went away, none left, discontinuing", instance->instance_name);
instance->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 (instance->resolve_txn != NULL) {
ioloop_dnssd_txn_cancel(instance->resolve_txn);
ioloop_dnssd_txn_release(instance->resolve_txn);
instance->resolve_txn = NULL;
}
// It's not uncommon for a name to drop and then come back immediately. Wait 30s before
// discontinuing the instance.
if (instance->discontinue_timeout == NULL) {
instance->discontinue_timeout = ioloop_wakeup_create();
// Oh well.
if (instance->discontinue_timeout == NULL) {
srpl_instance_discontinue_timeout(instance);
return;
}
}
RETAIN_HERE(instance);
ioloop_add_wake_event(instance->discontinue_timeout, instance, srpl_instance_discontinue_timeout, srpl_instance_context_release, 30 * 1000);
}
void
srpl_disable(srp_server_t *server_state)
{
srpl_domain_t *domain;
srpl_instance_t *instance, *next;
for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) {
for (instance = domain->instances; instance != NULL; instance = next) {
next = instance->next;
srpl_instance_discontinue_timeout(instance);
}
if (domain->query != NULL) {
ioloop_dnssd_txn_cancel(domain->query);
ioloop_dnssd_txn_release(domain->query);
domain->query = NULL;
}
}
if (server_state->srpl_advertise_txn != NULL) {
ioloop_dnssd_txn_cancel(server_state->srpl_advertise_txn);
ioloop_dnssd_txn_release(server_state->srpl_advertise_txn);
server_state->srpl_advertise_txn = NULL;
}
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->incoming != NULL && instance->incoming->state > srpl_state_disconnect_wait) {
srpl_connection_discontinue(instance->incoming);
}
if (instance->outgoing != NULL && instance->outgoing->state > srpl_state_disconnect_wait) {
srpl_connection_discontinue(instance->outgoing);
}
}
}
}
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);
}
}
}
void
srpl_drop_srpl_advertisement(srp_server_t *NONNULL server_state)
{
if (server_state->srpl_advertise_txn != NULL) {
ioloop_dnssd_txn_cancel(server_state->srpl_advertise_txn);
ioloop_dnssd_txn_release(server_state->srpl_advertise_txn);
server_state->srpl_advertise_txn = NULL;
}
}
void
srpl_undrop_srpl_advertisement(srp_server_t *NONNULL server_state)
{
srpl_domain_advertise(server_state);
}
// 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;
ioloop_message_retain(to->message);
from->hostname = NULL;
}
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_server_id:
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;
}
// Return true if the client connection to us is functional.
static bool
srpl_incoming_connection_is_active(srpl_instance_t *instance)
{
if (instance == NULL || instance->incoming == NULL) {
return false;
}
switch(instance->incoming->state) {
case srpl_state_session_message_wait:
return false;
default:
return true;
}
}
static void
srpl_disconnected_callback(comm_t *UNUSED comm, void *context, int UNUSED error)
{
srpl_connection_t *srpl_connection = context;
// 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; once that reference is released, if there's no instance
// holding a reference, the srp_connection will be finalized.
if (srpl_connection->connection != NULL) {
comm_t *connection = srpl_connection->connection;
srpl_connection->connection = NULL;
ioloop_comm_release(connection);
}
// If there's no instance, this connection just needs to go away (and presumably has).
if (instance == NULL) {
return;
}
// Because instance is still holding a reference to srpl_connection, it's safe to keep using srpl_connection.
// For server connections, we just dispose of the connection--the client is responsible for reconnecting
if (srpl_connection->is_server) {
instance->incoming = NULL;
srpl_connection->instance = NULL;
RELEASE_HERE(srpl_connection, srpl_connection_finalize);
#ifndef __clang_analyzer__
RELEASE_HERE(instance, srpl_instance_finalize);
#endif
return;
}
// 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);
return;
}
// Clear old data from connection.
srpl_connection_reset(srpl_connection);
// For outgoing connections, we only need to reconnect if we don't have a confirmed incoming connection.
if (srpl_incoming_connection_is_active(instance)) {
INFO(PRI_S_SRP ": disconnect received, we have an active incoming connection, going idle.",
srpl_connection->name);
srpl_connection_next_state(srpl_connection, srpl_state_idle);
} else {
INFO(PRI_S_SRP ": disconnect received, reconnecting.", srpl_connection->name);
srpl_connection_next_state(srpl_connection, srpl_state_next_address_get);
}
}
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 response, int rcode, void *context)
{
if (buffer_size < DNS_HEADER_SIZE) {
ERROR("internal: invalid buffer size %zd", buffer_size);
return false;
}
dso_make_message(state, buffer, buffer_size, dso, false, response, response ? message->wire.id : 0, 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 srp_server_t *
srpl_connection_server_state(srpl_connection_t *srpl_connection)
{
if (srpl_connection->instance == NULL || srpl_connection->instance->domain == NULL) {
INFO("connection has no server_state %p.", srpl_connection->instance);
return NULL;
}
return srpl_connection->instance->domain->server_state;
}
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;
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == NULL) {
return false;
}
if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf),
srpl_connection_message_get(srpl_connection), response, 0, srpl_connection)) {
return false;
}
dns_u16_to_wire(&towire, kDSOType_SRPLSession);
dns_rdlength_begin(&towire);
dns_u64_to_wire(&towire, server_state->server_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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLSession " PUB_S_SRP ", id %" PRIx64, srpl_connection->name,
response ? "response" : "message", server_state->server_id);
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), response, 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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLSendCandidates " PUB_S_SRP, srpl_connection->name, response ? "response" : "query");
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;
if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire,
dsobuf, sizeof(dsobuf), NULL, false, 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, 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)(srpl_time() - host->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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLCandidate message on connection.", srpl_connection->name);
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), true, 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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLCandidate response on connection.", srpl_connection->name);
return true;
}
static bool
srpl_host_message_send(srpl_connection_t *srpl_connection, adv_host_t *host)
{
uint8_t dsobuf[SRPL_HOST_MESSAGE_LENGTH];
dns_towire_state_t towire;
dso_message_t message;
struct iovec iov[2];
if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire,
dsobuf, sizeof(dsobuf), NULL, false, 0, srpl_connection)) {
return false;
}
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);
dns_u16_to_wire(&towire, kDSOType_SRPLTimeOffset);
dns_rdlength_begin(&towire);
dns_u32_to_wire(&towire, (uint32_t)(srpl_time() - host->update_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);
dns_u16_to_wire(&towire, kDSOType_SRPLHostMessage);
dns_u16_to_wire(&towire, host->message->length);
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[0].iov_len = towire.p - dsobuf;
iov[0].iov_base = dsobuf;
iov[1].iov_len = host->message->length;
iov[1].iov_base = &host->message->wire;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), iov, 2);
INFO(PRI_S_SRP " sent SRPLHost message %02x%02x " PRI_S_SRP " stable ID %" PRIx64,
srpl_connection->name, message.buf[0], message.buf[1], host->name, host->server_stable_id);
return true;
}
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), true, rcode, 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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLHost response %02x%02x rcode %d on connection.",
srpl_connection->name, message.buf[0], message.buf[1], rcode);
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;
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == 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), true, dns_rcode_noerror, 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;
ioloop_send_message(srpl_connection->connection, srpl_connection_message_get(srpl_connection), &iov, 1);
INFO(PRI_S_SRP " sent SRPLHost response, id %" PRIx64, srpl_connection->name, server_state->server_id);
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, 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 (int 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) {
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);
ret = false;
}
}
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;
srpl_connection_reset(srpl_connection);
srpl_connection_next_state(srpl_connection, srpl_state_disconnect);
}
static bool
srpl_session_message_parse(srpl_connection_t *srpl_connection,
srpl_event_t *event, dso_state_t *dso, const char *message_name)
{
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;
} else {
unsigned offp = 0;
srpl_event_content_type_set(event, srpl_event_content_type_server_id);
if (!dns_u64_parse(dso->primary.payload, 8, &offp, &event->content.server_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;
}
INFO(PRI_S_SRP " received " PUB_S_SRP ", id %" PRIx64,
srpl_connection->name, message_name, event->content.server_id);
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);
if (!srpl_session_message_parse(srpl_connection, &event, dso, "SRPLSession message")) {
srpl_disconnect(srpl_connection);
return;
}
srpl_connection_message_set(srpl_connection, message);
srpl_event_deliver(srpl_connection, &event);
}
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);
}
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);
if (srpl_send_candidates_message_parse(srpl_connection, dso, "SRPLSendCandidates message")) {
INFO(PRI_S_SRP " received SRPLSendCandidates query", srpl_connection->name);
srpl_connection_message_set(srpl_connection, message);
srpl_event_deliver(srpl_connection, &event);
return;
}
}
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);
if (!srpl_event_content_type_set(&event, srpl_event_content_type_candidate) ||
!srpl_find_dso_additionals(srpl_connection, dso, additionals,
required, names, indices, 3, 3, 3, "SRPLCandidate message",
event.content.candidate, srpl_candidate_message_parse_in)) {
goto fail;
}
srpl_connection_message_set(srpl_connection, message);
event.content.candidate->update_time = srpl_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, 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:
return dns_name_parse(&update->hostname, buffer, length, offp, length);
case 1:
return dns_u32_parse(buffer, length, offp, &update->update_offset);
case 2:
update->message = ioloop_message_create(length);
if (update->message == NULL) {
return false;
}
memcpy(&update->message->wire, buffer, length);
*offp = length;
return true;
case 3:
return dns_u64_parse(buffer, length, offp, &update->server_stable_id);
}
return false;
}
static void
srpl_host_message(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 message.",
srpl_connection->name, dso->primary.length);
goto fail;
} else {
const char *names[4] = { "Host Name", "Time Offset", "Host Message", "Server Stable ID" };
dso_message_types_t additionals[4] = { kDSOType_SRPLHostname, kDSOType_SRPLTimeOffset, kDSOType_SRPLHostMessage,
kDSOType_SRPLServerStableID };
bool required[4] = { true, true, true, false };
int indices[4];
srpl_event_t event;
// 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_find_dso_additionals(srpl_connection, dso, additionals,
required, names, indices, 4, 3, 4, "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);
event.content.host_update.update_time =
srpl_time() - event.content.host_update.update_offset;
event.content.host_update.message->received_time = event.content.host_update.update_time;
srpl_connection_message_set(srpl_connection, message);
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));
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_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;
}
// Set things up to reconnect later.
srpl_connection_drop_state_delay(srpl_connection->instance, srpl_connection, reconnect_delay);
// Drop the connection
srpl_connection_discontinue(srpl_connection);
}
static void
srpl_dso_message(srpl_connection_t *srpl_connection, message_t *message, dso_state_t *dso, bool response)
{
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;
default:
INFO("unexpected primary TLV %d", dso->primary.opcode);
dso_simple_response(srpl_connection->connection, NULL, &message->wire, dns_rcode_dsotypeni);
break;
}
}
static void
srpl_unclaimed_finalize(unclaimed_connection_t *unclaimed)
{
if (unclaimed->wakeup_timeout != NULL) {
ioloop_wakeup_release(unclaimed->wakeup_timeout);
unclaimed->wakeup_timeout = NULL;
}
if (unclaimed->message != NULL) {
ioloop_message_release(unclaimed->message);
}
free(unclaimed);
}
static void
srpl_unclaimed_cancel(unclaimed_connection_t *unclaimed)
{
unclaimed_connection_t **up;
bool found = false;
// Remove it from the list if it's on the list
for (up = &unclaimed_connections; *up != NULL; up = &(*up)->next) {
if (*up == unclaimed) {
*up = unclaimed->next;
found = true;
break;
}
}
ERROR("unclaimed connection " PRI_S_SRP " (%p) removed",
unclaimed->connection == NULL ? "<NULL>" : unclaimed->connection->name, unclaimed);
if (unclaimed->dso != NULL) {
dso_state_cancel(unclaimed->dso);
unclaimed->dso = NULL;
}
if (unclaimed->wakeup_timeout != NULL) {
ioloop_cancel_wake_event(unclaimed->wakeup_timeout);
ioloop_wakeup_release(unclaimed->wakeup_timeout);
unclaimed->wakeup_timeout = NULL;
}
if (unclaimed->message != NULL) {
ioloop_message_release(unclaimed->message);
unclaimed->message = NULL;
}
if (unclaimed->connection) {
ioloop_comm_cancel(unclaimed->connection);
ioloop_comm_release(unclaimed->connection);
unclaimed->connection = NULL;
}
// If we removed it from the list, release the reference.
if (found) {
RELEASE_HERE(unclaimed, srpl_unclaimed_finalize);
}
return;
}
static void
srpl_unclaimed_dispose_callback(void *context)
{
unclaimed_connection_t *unclaimed = context;
INFO("unclaimed connection " PRI_S_SRP " (%p) timed out", unclaimed->connection->name, unclaimed);
srpl_unclaimed_cancel(unclaimed);
}
static void
srpl_unclaimed_disconnect_callback(comm_t *connection, void *context, int err)
{
unclaimed_connection_t *unclaimed = context;
INFO("unclaimed connection " PRI_S_SRP " (%p) disconnected (error code %d)", connection->name, unclaimed, err);
srpl_unclaimed_cancel(unclaimed);
}
static void
srpl_unclaimed_datagram_callback(comm_t *comm, message_t *message, void *context)
{
unclaimed_connection_t *unclaimed = context;
INFO("unclaimed connection " PRI_S_SRP " (%p) received unexpected message type %d",
comm->name, unclaimed, dns_opcode_get(&message->wire));
srpl_unclaimed_cancel(unclaimed);
}
static void
srpl_unclaimed_context_release(void *context)
{
unclaimed_connection_t *unclaimed = context;
INFO("unclaimed connection %p context released", unclaimed);
RELEASE_HERE(unclaimed, srpl_unclaimed_finalize);
}
static void
srpl_add_unclaimed_server(comm_t *connection, message_t *message, dso_state_t *dso, srp_server_t *server_state)
{
unclaimed_connection_t **up, *unclaimed = calloc(1, sizeof(*unclaimed));
if (unclaimed == NULL) {
ERROR("no memory to hold on to unclaimed " PRI_S_SRP, connection->name);
dso_state_cancel(dso);
} else {
RETAIN_HERE(unclaimed);
unclaimed->wakeup_timeout = ioloop_wakeup_create();
// We want to reclaim the unclaimed connection after two minutes, if no matching advertisement has yet
// been found.
if (unclaimed->wakeup_timeout) {
ioloop_add_wake_event(unclaimed->wakeup_timeout, unclaimed,
srpl_unclaimed_dispose_callback, NULL, 120 * MSEC_PER_SEC);
} else {
ERROR("Unable to add wakeup event for unclaimed " PRI_S_SRP, connection->name);
}
unclaimed->dso = dso;
unclaimed->message = message;
unclaimed->server_state = server_state;
ioloop_message_retain(message);
unclaimed->connection = connection;
unclaimed->address = connection->address;
ioloop_comm_retain(unclaimed->connection);
// We shouldn't get any further datagrams before a connection is established.
connection->datagram_callback = srpl_unclaimed_datagram_callback;
ioloop_comm_context_set(connection, unclaimed, srpl_unclaimed_context_release);
RETAIN_HERE(unclaimed); // connection holds a reference.
// If we get a disconnect, we have to handle that.
ioloop_comm_disconnect_callback_set(connection, srpl_unclaimed_disconnect_callback);
// Find the end of the list.
for (up = &unclaimed_connections; *up != NULL; up = &(*up)->next) {
}
*up = unclaimed;
}
}
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;
switch(eventType)
{
case kDSOEventType_DNSMessage:
// We shouldn't get here because we already handled any DNS messages
message = event_context;
INFO("DNS Message (opcode=%d) received from " PRI_S_SRP, 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("DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
dso->remote_name);
break;
case kDSOEventType_DSOMessage:
INFO("DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
dso->primary.opcode, dso->remote_name);
message = event_context;
srpl_dso_message((srpl_connection_t *)context, message, dso, false);
break;
case kDSOEventType_DSOResponse:
INFO("DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
dso->primary.opcode, dso->remote_name);
response_context = event_context;
message = response_context->message_context;
srpl_dso_message((srpl_connection_t *)context, 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("Inactivity timer went off, closing connection.");
break;
case kDSOEventType_Keepalive:
INFO("should send a keepalive now.");
break;
case kDSOEventType_KeepaliveRcvd:
INFO("keepalive received.");
break;
case kDSOEventType_RetryDelay:
disconnect_context = event_context;
INFO("retry delay received, %d seconds", disconnect_context->reconnect_delay);
srpl_dso_retry_delay(context, 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_associate_incoming_with_instance(comm_t *connection, message_t *message,
dso_state_t *dso, srpl_instance_t *instance)
{
srpl_connection_t *srpl_connection = srpl_connection_create(instance, false);
if (srpl_connection == NULL) {
ioloop_comm_cancel(connection);
return;
}
instance->incoming = srpl_connection; // Retained via create/copy rule.
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);
dso_set_event_callback(dso, srpl_instance_dso_event_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); // 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);
}
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;
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) {
address = instance->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_unclaimed_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, 1, connection->name, srpl_instance_dso_event_callback,
srpl_connection, NULL, connection);
if (connection->dso == NULL) {
ERROR(PRI_S_SRP " can't create dso state object.", srpl_connection->name);
srpl_disconnect(srpl_connection);
return;
}
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;
}
int to_port = srpl_connection->instance->outgoing_port;
if (srpl_connection->connected_address.sa.sa_family == AF_INET) {
srpl_connection->connected_address.sin.sin_port = htons(to_port);
} else {
srpl_connection->connected_address.sin6.sin6_port = htons(to_port);
}
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, to_port);
return false;
}
ADDR_NAME_LOGGER(INFO, &srpl_connection->connected_address, "connecting to address ", " for instance ", " port ",
srpl_connection->name, to_port);
RETAIN_HERE(srpl_connection); // For the connection's reference
return true;
}
static void
srpl_instance_is_me(srpl_instance_t *instance, const char *ifname, const addr_t *address)
{
instance->is_me = true;
if (ifname != NULL) {
INFO(PRI_S_SRP "/" PUB_S_SRP ": name server for instance " PRI_S_SRP " is me.", instance->name, ifname, instance->instance_name);
} else if (address != NULL) {
ADDR_NAME_LOGGER(INFO, address, "", " instance ", " is me. ", instance->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->outgoing) {
srpl_connection_discontinue(instance->outgoing);
}
}
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 = srpl_time();
if (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_t *instance = context;
if (err != kDNSServiceErr_NoError) {
ERROR("service instance address resolution for " PRI_S_SRP " failed with %d", instance->name, err);
if (instance->address_query) {
address_query_cancel(instance->address_query);
instance->address_query = NULL;
}
return;
}
if (added) {
unclaimed_connection_t **up = &unclaimed_connections;
while (*up != NULL) {
unclaimed_connection_t *unclaimed = *up;
if (ip_addresses_equal(address, &unclaimed->address)) {
INFO("Unclaimed connection " PRI_S_SRP " matches new address for " PRI_S_SRP, unclaimed->dso->remote_name,
instance->name);
srpl_associate_incoming_with_instance(unclaimed->connection,
unclaimed->message, unclaimed->dso, instance);
unclaimed->dso = NULL;
// srpl_associate_incoming_with_instance retains unclaimed->connection. srpl_unclaimed_cancel would
// release it, but it also cancels it, which we don't want, so release it and NULL it out now.
ioloop_comm_release(unclaimed->connection);
unclaimed->connection = NULL;
srpl_unclaimed_cancel(unclaimed);
break;
} else {
if (unclaimed->address.sa.sa_family == AF_INET6) {
SEGMENTED_IPv6_ADDR_GEN_SRP(&unclaimed->address.sin6.sin6_addr, rdata_buf);
INFO("Unclaimed connection address is: " PRI_SEGMENTED_IPv6_ADDR_SRP,
SEGMENTED_IPv6_ADDR_PARAM_SRP(&unclaimed->address.sin6.sin6_addr, rdata_buf));
} else {
IPv4_ADDR_GEN_SRP(&unclaimed->address.sin.sin_addr, rdata_buf);
INFO("Unclaimed connection address is: " PRI_IPv4_ADDR_SRP,
IPv4_ADDR_PARAM_SRP(&unclaimed->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("Unclaimed connection 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("Unclaimed connection address is: " PRI_IPv4_ADDR_SRP,
IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, rdata_buf));
}
INFO("Unclaimed connection " PRI_S_SRP " does not match new address for " PRI_S_SRP, unclaimed->dso->remote_name,
instance->name);
up = &(*up)->next;
}
}
if (srpl_my_address_check(address)) {
srpl_instance_is_me(instance, NULL, address);
}
// Generate an event indicating that we have a new address.
else if (instance->outgoing != NULL) {
srpl_event_t event;
srpl_event_initialize(&event, srpl_event_address_add);
srpl_event_deliver(instance->outgoing, &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->incoming != NULL) {
srpl_event_deliver(instance->incoming, &event);
}
if (instance->outgoing != NULL) {
srpl_event_deliver(instance->outgoing, &event);
}
}
}
}
static void
srpl_instance_add(const char *hostname, const char *instance_name,
const char *ifname, srpl_domain_t *domain, srpl_instance_t *instance,
bool have_server_id, uint64_t advertised_server_id)
{
srpl_instance_t **sp;
// Find the instance on the instance list for this domain.
for (sp = &domain->instances; *sp != NULL; sp = &(*sp)->next) {
if (instance == *sp) {
break;
}
}
if (*sp == NULL) {
INFO("instance " PRI_S_SRP " for " PRI_S_SRP "/" PUB_S_SRP " " PUB_S_SRP "id %" PRIx64 " no longer on list",
instance_name != NULL ? instance_name : "<NULL>", hostname, ifname, have_server_id ? "" : "!", advertised_server_id);
if (instance->resolve_txn != NULL) {
ioloop_dnssd_txn_cancel(instance->resolve_txn);
ioloop_dnssd_txn_release(instance->resolve_txn);
instance->resolve_txn = NULL;
}
return;
}
INFO("instance " PRI_S_SRP " for " PRI_S_SRP "/" PUB_S_SRP " " PUB_S_SRP "id %" PRIx64 " " PUB_S_SRP "found",
instance_name != NULL ? instance_name : "<NULL>", hostname, ifname, have_server_id ? "" : "!", advertised_server_id,
*sp == NULL ? "!" : "");
// If the hostname changed, we need to restart the address query.
if (instance->name == NULL || strcmp(instance->name, hostname)) {
if (instance->address_query != NULL) {
address_query_cancel(instance->address_query);
instance->address_query = NULL;
}
if (instance->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,
instance->name, hostname, instance_name == NULL ? "<NULL>" : instance_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, instance_name == NULL ? "<NULL>" : instance_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 instance name.");
return;
} else {
free(instance->name);
instance->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 (instance->address_query == NULL) {
instance->address_query = address_query_create(instance->name, instance,
srpl_instance_address_callback,
srpl_instance_context_release);
if (instance->address_query == NULL) {
INFO("unable to create address query");
} else {
RETAIN_HERE(instance); // retain for the address query.
}
}
if (instance->outgoing == NULL && !instance->is_me) {
instance->outgoing = srpl_connection_create(instance, true);
srpl_connection_next_state(instance->outgoing, srpl_state_disconnected);
}
instance->num_copies++;
// If this add changed the server ID, we may want to re-attempt a connect.
if (have_server_id && (!instance->have_server_id || instance->server_id != advertised_server_id)) {
instance->have_server_id = true;
instance->server_id = advertised_server_id;
if (instance->outgoing != NULL && instance->outgoing->state == srpl_state_idle) {
srpl_connection_next_state(instance->outgoing, srpl_state_disconnected);
}
}
}
static void
srpl_resolve_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags UNUSED flags, uint32_t interfaceIndex,
DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port,
uint16_t UNUSED txtLen, const unsigned char *UNUSED txtRecord, void *context)
{
char ifname[IFNAMSIZ];
srpl_instance_t *instance = context;
srpl_domain_t *domain = instance->domain;
const char *domain_name;
uint8_t domain_len;
const char *server_id_string;
uint8_t server_id_string_len;
char server_id_buf[INT64_HEX_STRING_MAX];
uint64_t advertised_server_id = 0;
bool have_server_id = false;
if (errorCode != kDNSServiceErr_NoError) {
ERROR("resolve for " PRI_S_SRP " failed with %d", fullname, errorCode);
return;
}
domain_name = TXTRecordGetValuePtr(txtLen, txtRecord, "domain", &domain_len);
if (domain_name == NULL) {
INFO("resolve for " PRI_S_SRP " succeeded, but there is no domain name.", fullname);
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, fullname, domain->name);
free(domain_terminated);
return;
}
if (strcmp(domain->name, 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, fullname, domain->name);
server_id_string = TXTRecordGetValuePtr(txtLen, txtRecord, "server-id", &server_id_string_len);
if (server_id_string != NULL && server_id_string_len < INT64_HEX_STRING_MAX) {
char *endptr, *nulptr;
unsigned long long num;
memcpy(server_id_buf, server_id_string, server_id_string_len);
nulptr = &server_id_buf[server_id_string_len];
*nulptr = '\0';
num = strtoull(server_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_server_id = num;
have_server_id = true;
}
}
instance->outgoing_port = ntohs(port);
if (if_indextoname(interfaceIndex, ifname) == NULL) {
snprintf(ifname, sizeof(ifname), "%d", interfaceIndex);
}
srpl_instance_add(hosttarget, fullname, ifname, instance->domain, instance, have_server_id, advertised_server_id);
}
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 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)
{
DNSServiceRef sdref;
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 service instance " PRI_S_SRP " went away.", instance->instance_name);
instance->num_copies = 0;
srpl_instance_discontinue(instance);
}
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 instance_name[kDNSServiceMaxDomainName];
DNSServiceConstructFullName(instance_name, serviceName, regtype, replyDomain);
if (flags & kDNSServiceFlagsAdd) {
srpl_instance_t *instance = NULL, **sp;
// See if we already have an instance going; if so, just increment the number of copies of the instance that we've found.
for (sp = &domain->instances; *sp; sp = &(*sp)->next) {
instance = *sp;
if (!strcmp(instance->instance_name, instance_name)) {
if (instance->resolve_txn != NULL) {
instance->num_copies++;
INFO("duplicate add for " PRI_S_SRP, instance_name);
return;
}
// In this case the instance went away and came back, so instance->resolve_txn is NULL, but the instance still exists.
INFO(PRI_S_SRP " went away but came back.", instance_name);
break;
}
}
if (*sp == NULL) {
instance = calloc(1, sizeof(*instance));
if (instance == NULL) {
ERROR("no memory for instance" PRI_S_SRP, instance_name);
return;
}
// This retain is for the instance list on the domain.
RETAIN_HERE(instance);
instance->domain = domain;
RETAIN_HERE(instance->domain);
instance->instance_name = strdup(instance_name);
if (instance->instance_name == NULL) {
ERROR("no memory for instance " PRI_S_SRP, instance_name);
RELEASE_HERE(instance, srpl_instance_finalize);
return;
}
}
int err = DNSServiceResolve(&sdref, 0, interfaceIndex,
serviceName, regtype, replyDomain, srpl_resolve_callback, instance);
if (err != kDNSServiceErr_NoError) {
ERROR("unable to resolve " PRI_S_SRP ": code %d", instance_name, err);
RELEASE_HERE(instance, srpl_instance_finalize);
return;
}
instance->resolve_txn = ioloop_dnssd_txn_add(sdref, instance, srpl_instance_context_release, NULL);
if (instance->resolve_txn == NULL) {
ERROR("unable to allocate dnssd_txn_t for " PRI_S_SRP, instance_name);
DNSServiceRefDeallocate(sdref);
return;
}
// Retain for the dnssd_txn.
RETAIN_HERE(instance);
INFO("resolving " PRI_S_SRP, instance_name);
// If we have a discontinue timer going, cancel it.
if (instance->discontinue_timeout != NULL) {
if (instance->discontinuing) {
INFO("discontinue on instance " PRI_S_SRP " canceled.", instance->name);
ioloop_cancel_wake_event(instance->discontinue_timeout);
instance->discontinuing = false;
}
}
// If we created a new instance object, put it at the end of the list.
// The instance is already retained when it's allocated.
if (*sp == NULL) {
*sp = instance;
}
} else {
INFO("_srpl-tls._tcp service instance " PRI_S_SRP " went away.", instance_name);
for (srpl_instance_t *instance = domain->instances; instance; instance = instance->next) {
if (!strcmp(instance->instance_name, instance_name)) {
instance->num_copies--;
srpl_instance_discontinue(instance);
break;
}
}
}
}
static void
srpl_domain_context_release(void *context)
{
srpl_domain_t *domain = context;
RELEASE_HERE(domain, srpl_domain_finalize);
}
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);
INFO("New service replication browsing domain: " PRI_S_SRP, 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);
}
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_tunnel_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:
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(void *context)
{
srpl_instance_t *instance = context;
srpl_event_t event;
// If we have a new connection, no need to reconnect.
if (instance->incoming != NULL && instance->incoming->state != srpl_state_idle) {
INFO(PRI_S_SRP ": we now have a valid connection.", instance->name);
return;
}
// We shouldn't have an outgoing connection.
if (instance->outgoing != NULL && instance->outgoing->state != srpl_state_idle) {
FAULT(PRI_S_SRP ": got to srpl_instance_reconnect with a non-idle (" PUB_S_SRP ") outgoing connection.",
instance->name, srpl_state_name(instance->outgoing->state));
return;
}
// We don't get rid of the outgoing connection when it's idle, so we shouldn't be able to get here.
if (instance->outgoing == NULL) {
FAULT(PRI_S_SRP "instance->outgoing is NULL!", instance->name);
return;
}
// Trigger a reconnect.
srpl_event_initialize(&event, srpl_event_reconnect_timer_expiry);
srpl_event_deliver(instance->outgoing, &event);
}
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);
ioloop_add_wake_event(instance->reconnect_timeout,
instance, srpl_instance_reconnect, srpl_instance_context_release, delay * MSEC_PER_SEC);
}
if (srpl_connection == instance->incoming) {
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 {
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_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);
}
}
// 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;
STATE_ANNOUNCE_NO_EVENTS(srpl_connection);
REQUIRE_SRPL_INSTANCE(srpl_connection);
REQUIRE_SRPL_EVENT_NULL(srpl_connection, event);
address_query = srpl_connection->instance->address_query;
// Get the next address
// Return an event, one of "next address", "end of address list" or "no addresses"
if (address_query == NULL || address_query->num_addresses == 0) {
return srpl_state_disconnected;
} else {
// Go to the next address, if there is a next address.
if (address_query->cur_address == address_query->num_addresses ||
++address_query->cur_address == address_query->num_addresses)
{
address_query->cur_address = -1;
return srpl_state_reconnect_wait;
} else {
memcpy(&srpl_connection->connected_address,
&address_query->addresses[address_query->cur_address], sizeof(addr_t));
return srpl_state_connect;
}
}
}
// 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_event_t event;
srpl_event_initialize(&event, srpl_event_reconnect_timer_expiry);
srpl_event_deliver(srpl_connection, &event);
}
// 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) {
// 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, 60 * 1000);
RETAIN_HERE(srpl_connection); // the timer has a reference.
return srpl_state_invalid;
}
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;
}
ioloop_comm_cancel(srpl_connection->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) {
return srpl_state_idle;
} else if (event->event_type == srpl_event_connected) {
return srpl_state_server_id_send;
} else {
UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event);
}
return srpl_state_invalid;
}
// This state sends a server id and then goes to server_id_response_wait, unless the send failed, in which
// case it goes to the disconnect state.
static srpl_state_t
srpl_server_id_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 server id message
// Now we say hello.
if (!srpl_session_message_send(srpl_connection, false)) {
return srpl_state_disconnect;
}
return srpl_state_server_id_response_wait;
}
// This state waits for a session response with the remote server ID.
// When the response arrives, it goes to the send_candidates_send state.
static srpl_state_t
srpl_server_id_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_server_id = event->content.server_id;
return srpl_state_send_candidates_send;
} else {
UNEXPECTED_EVENT(srpl_connection, event);
}
return srpl_state_invalid;
}
// When evaluating the incoming ID, we've decided to continue (called by srpl_evaluate_incoming_id_action).
static srpl_state_t
srpl_evaluate_incoming_continue(srpl_connection_t *srpl_connection)
{
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == NULL) {
return srpl_state_invalid;
}
INFO(PRI_S_SRP ": our server id %" PRIx64 " < remote server id %" PRIx64,
srpl_connection->name, server_state->server_id, srpl_connection->remote_server_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_evaluate_incoming_id_action).
static srpl_state_t
srpl_evaluate_incoming_disconnect(srpl_connection_t *srpl_connection, bool bad)
{
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == NULL) {
return srpl_state_invalid;
}
INFO(PRI_S_SRP ": our server id %" PRIx64 " > remote server id %" PRIx64,
srpl_connection->name, server_state->server_id, srpl_connection->remote_server_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 the server ID that we received. If ours is greater than theirs,
// a server connection generated a new ID; a client connection disconnects.
static srpl_state_t
srpl_server_id_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);
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == NULL) {
return srpl_state_invalid;
}
// Compare the server id we received to our own
// Return one of "outgoing id equal", "outgoing id less" or "outgoing id more"
if (server_state->server_id > srpl_connection->remote_server_id) {
return srpl_evaluate_incoming_disconnect(srpl_connection, false);
} else if (server_state->server_id < srpl_connection->remote_server_id) {
return srpl_evaluate_incoming_continue(srpl_connection);
} else {
INFO(PRI_S_SRP ": our server id %" PRIx64 " == remote server id %" PRIx64,
srpl_connection->name, server_state->server_id, srpl_connection->remote_server_id);
if (srpl_connection->is_server) {
return srpl_state_server_id_regenerate;
} else {
return srpl_evaluate_incoming_disconnect(srpl_connection, true);
}
}
}
// This state's action is to regenerate the server ID, and then go back to evaluate_incoming_id.
static srpl_state_t
srpl_server_id_regenerate_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);
srp_server_t *server_state = srpl_connection_server_state(srpl_connection);
if (server_state == NULL) {
return srpl_state_invalid;
}
// Generate a new server id
server_state->server_id = srp_random64();
INFO(PRI_S_SRP ": new server id %" PRIx64, srpl_connection->name, server_state->server_id);
// Re-advertise the domain with the new server ID.
srpl_domain_advertise(srpl_connection->instance->domain->server_state);
// return the server id in a "new server id" event.
return srpl_state_server_id_evaluate;
}
// 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;
}
// 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)
{
if (event->event_type == srpl_event_send_candidates_response_received) {
if (srpl_connection->is_server) {
srpl_connection->database_synchronized = true;
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)
{
if (!srp_dns_evaluate(NULL, srpl_connection->instance->domain->server_state,
srpl_connection, srpl_connection->stashed_host.message)) {
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->outgoing != NULL) {
srpl_event_deliver(instance->outgoing, event);
}
if (instance->incoming != NULL) {
srpl_event_deliver(instance->incoming, 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) {
srpl_host_response_send(srpl_connection, event->content.advertise_finished.rcode);
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;
}
srpl_host_message_send(srpl_connection, host);
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_connection->database_synchronized = true;
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.message != 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;
}
srpl_host_response_send(srpl_connection, srpl_connection->stashed_host.rcode);
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_server_id = event->content.server_id;
return srpl_state_server_id_evaluate;
} else {
UNEXPECTED_EVENT(srpl_connection, event);
}
}
// Send a server id 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->outgoing != NULL) {
srpl_srp_client_update_send_event_to_connection(instance->outgoing, event);
}
if (instance->incoming != NULL) {
srpl_srp_client_update_send_event_to_connection(instance->incoming, 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 },
{ STATE_NAME_DECL(connecting), srpl_connecting_action },
{ STATE_NAME_DECL(server_id_send), srpl_server_id_send_action },
{ STATE_NAME_DECL(server_id_response_wait), srpl_server_id_response_wait_action },
{ STATE_NAME_DECL(server_id_evaluate), srpl_server_id_evaluate_action },
{ STATE_NAME_DECL(server_id_regenerate), srpl_server_id_regenerate_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),
};
#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)
{
srp_server_t *server_state = context;
if (error_code != kDNSServiceErr_NoError) {
ERROR("unable to advertise _srpl-tls._tcp service: %d", error_code);
if (server_state->srpl_register_wakeup == NULL) {
server_state->srpl_register_wakeup = ioloop_wakeup_create();
}
if (server_state->srpl_register_wakeup != NULL) {
// Try registering again in one second.
ioloop_add_wake_event(server_state->srpl_register_wakeup, server_state, 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(srp_server_t *server_state)
{
DNSServiceRef sdref = NULL;
TXTRecordRef txt_record;
char server_id_buf[INT64_HEX_STRING_MAX];
TXTRecordCreate(&txt_record, 0, NULL);
int err = TXTRecordSetValue(&txt_record, "domain", strlen(current_thread_domain_name), current_thread_domain_name);
if (err != kDNSServiceErr_NoError) {
ERROR("unable to set domain in TXT record for _srpl-tls._tcp to " PRI_S_SRP, current_thread_domain_name);
goto exit;
}
snprintf(server_id_buf, sizeof(server_id_buf), "%" PRIx64, server_state->server_id);
err = TXTRecordSetValue(&txt_record, "server-id", strlen(server_id_buf), server_id_buf);
if (err != kDNSServiceErr_NoError) {
ERROR("unable to set server-id in TXT record for _srpl-tls._tcp to " PUB_S_SRP, server_id_buf);
goto exit;
}
// If there is already a registration, get rid of it
if (server_state->srpl_advertise_txn != NULL) {
ioloop_dnssd_txn_cancel(server_state->srpl_advertise_txn);
ioloop_dnssd_txn_release(server_state->srpl_advertise_txn);
server_state->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, server_state);
if (err != kDNSServiceErr_NoError) {
ERROR("unable to advertise _srpl-tls._tcp service");
goto exit;
}
server_state->srpl_advertise_txn = ioloop_dnssd_txn_add(sdref, NULL, NULL, NULL);
if (server_state->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 (current_thread_domain_name != NULL) {
srpl_domain_rename(current_thread_domain_name, new_thread_domain_name);
}
srpl_domain_add(server_state, new_thread_domain_name);
free(current_thread_domain_name);
current_thread_domain_name = new_thread_domain_name;
srpl_domain_advertise(server_state);
}
void
srpl_startup(srp_server_t *server_state)
{
server_state->server_id = srp_random64();
INFO("server id = %" PRIx64, server_state->server_id);
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: