blob: 7288c32fed75f5a709dd5720ab5b9ca22feb7a36 [file] [log] [blame]
/* srp-mdns-proxy.c
*
* Copyright (c) 2019-2023 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This file contains the SRP Advertising Proxy, which is an SRP Server
* that offers registered addresses using mDNS.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.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 <ctype.h>
#include <mdns/pf.h>
#include "srp.h"
#include "dns-msg.h"
#include "srp-crypto.h"
#include "ioloop.h"
#include "srp-gw.h"
#include "srp-proxy.h"
#include "srp-mdns-proxy.h"
#include "dnssd-proxy.h"
#include "config-parse.h"
#include "cti-services.h"
#include "route.h"
#include "adv-ctl-server.h"
#include "srp-replication.h"
#include "ioloop-common.h"
#include "thread-device.h"
#include "nat64-macos.h"
#if SRP_FEATURE_NAT64 || STUB_ROUTER
#include <mdns/managed_defaults.h>
#endif
#if SRP_FEATURE_NAT64
#include "nat64.h"
#endif
#define ADDRESS_RECORD_TTL 120 // Address records have TTL of 120s to avoid advertising stale data for too long.
#define OTHER_RECORD_TTL 3600 // Other records we're not so worried about.
static const char local_suffix_ld[] = ".local";
static const char *local_suffix = &local_suffix_ld[1];
void *dns_service_op_not_to_be_freed;
srp_server_t *srp_servers;
const uint8_t thread_anycast_preamble[7] = { 0, 0, 0, 0xff, 0xfe, 0, 0xfc };
const uint8_t thread_rloc_preamble[6] = { 0, 0, 0, 0xff, 0xf0, 0 };
//======================================================================================================================
// MARK: - Forward references
static void register_host_record_completion(DNSServiceRef sdref, DNSRecordRef rref,
DNSServiceFlags flags, DNSServiceErrorType error_code, void *context);
static void register_instance_completion(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType error_code,
const char *name, const char *regtype, const char *domain, void *context);
static void update_from_host(adv_host_t *host);
static void start_host_update(adv_host_t *host);
static void prepare_update(adv_host_t *host, client_update_t *client_update);
static void delete_host(void *context);
static void lease_callback(void *context);
static void adv_host_finalize(adv_host_t *host);
static void adv_record_finalize(adv_record_t *record);
static void adv_update_finalize(adv_update_t *update);
//======================================================================================================================
// MARK: - Functions
static void
remove_shared_record(srp_server_t *server_state, adv_record_t *record)
{
RETAIN_HERE(record, adv_record);
if (record->rref != NULL && record->shared_txn != 0 &&
record->shared_txn == (intptr_t)server_state->shared_registration_txn)
{
int err = DNSServiceRemoveRecord(server_state->shared_registration_txn->sdref, record->rref, 0);
// We can't release the record here if we got an error removing it, because if we get an error removing it,
// it doesn't get removed from the list. This should never happen, but if it does, the record will leak.
if (err == kDNSServiceErr_NoError) {
RELEASE_HERE(record, adv_record); // Release the DNSService callback's reference
} else {
// At this point we should never see an error calling DNSServiceRemoveRecord, so if we do, call
// attention to it.
if (!record->update_pending) {
FAULT("DNSServiceRemoveRecord(%p, %p, %p, 0) returned %d",
server_state->shared_registration_txn->sdref, record, record->rref, err);
}
}
}
record->rref = NULL;
record->shared_txn = 0;
RELEASE_HERE(record, adv_record);
}
static void
adv_record_finalize(adv_record_t *record)
{
// We should not be able to get to the finalize function without having removed the rref, because the DNSService
// callback always holds a reference to the record.
if (record->update != NULL) {
RELEASE_HERE(record->update, adv_update);
}
if (record->host != NULL) {
RELEASE_HERE(record->host, adv_host);
}
free(record->rdata);
free(record);
}
static void
adv_instance_finalize(adv_instance_t *instance)
{
if (instance->txn != NULL) {
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
}
if (instance->txt_data != NULL) {
free(instance->txt_data);
}
if (instance->instance_name != NULL) {
free(instance->instance_name);
}
if (instance->service_type != NULL) {
free(instance->service_type);
}
if (instance->host != NULL) {
RELEASE_HERE(instance->host, adv_host);
instance->host = NULL;
}
if (instance->message != NULL) {
ioloop_message_release(instance->message);
instance->message = NULL;
}
if (instance->update != NULL) {
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
}
free(instance);
}
static void
adv_instance_context_release(void *NONNULL context)
{
adv_instance_t *instance = context;
RELEASE_HERE(instance, adv_instance);
}
#define DECLARE_VEC_CREATE(type) \
static type ## _vec_t * \
type ## _vec_create(int size) \
{ \
type ## _vec_t *vec; \
\
vec = calloc(1, sizeof(*vec)); \
if (vec != NULL) { \
if (size == 0) { \
size = 1; \
} \
vec->vec = calloc(size, sizeof (*(vec->vec))); \
if (vec->vec == NULL) { \
free(vec); \
vec = NULL; \
} else { \
RETAIN_HERE(vec, type##_vec); \
} \
} \
return vec; \
}
#define DECLARE_VEC_COPY(type) \
static type ## _vec_t * \
type ## _vec_copy(type ## _vec_t *vec) \
{ \
type ## _vec_t *new_vec; \
int i; \
\
new_vec = type ## _vec_create(vec->num); \
if (new_vec != NULL) { \
for (i = 0; i < vec->num; i++) { \
if (vec->vec[i] != NULL) { \
new_vec->vec[i] = vec->vec[i]; \
RETAIN_HERE(new_vec->vec[i], type); \
} \
} \
new_vec->num = vec->num; \
} \
return new_vec; \
}
#define DECLARE_VEC_FINALIZE(type) \
static void \
type ## _vec_finalize(type ## _vec_t *vec) \
{ \
int i; \
\
for (i = 0; i < vec->num; i++) { \
if (vec->vec[i] != NULL) { \
RELEASE_HERE(vec->vec[i], type); \
vec->vec[i] = NULL; \
} \
} \
free(vec->vec); \
free(vec); \
}
DECLARE_VEC_CREATE(adv_instance);
DECLARE_VEC_COPY(adv_instance);
DECLARE_VEC_FINALIZE(adv_instance);
DECLARE_VEC_CREATE(adv_record);
DECLARE_VEC_COPY(adv_record);
DECLARE_VEC_FINALIZE(adv_record);
// We call advertise_finished when a client request has finished, successfully or otherwise.
#if SRP_FEATURE_REPLICATION
static bool
srp_replication_advertise_finished(adv_host_t *host, char *hostname, srp_server_t *server_state,
srpl_connection_t *srpl_connection, comm_t *connection, int rcode)
{
if (server_state->srp_replication_enabled) {
INFO("hostname = " PRI_S_SRP " host = %p server_state = %p srpl_connection = %p connection = %p rcode = "
PUB_S_SRP, hostname, host, server_state, srpl_connection, connection, dns_rcode_name(rcode));
if (connection == NULL) {
// connection is the SRP client connection on which an update arrived. If it's null,
// this is an SRP replication update, not an actual client we're communicating with.
INFO("replication advertise finished: host " PRI_S_SRP ": rcode = " PUB_S_SRP,
hostname, dns_rcode_name(rcode));
if (srpl_connection != NULL) {
srpl_advertise_finished_event_send(hostname, rcode, server_state);
if (host != NULL && host->srpl_connection != NULL) {
if (rcode == dns_rcode_noerror) {
host->update_server_id = host->srpl_connection->remote_partner_id;
host->server_stable_id = host->srpl_connection->stashed_host.server_stable_id;
INFO("replicated host " PRI_S_SRP " server stable ID %" PRIx64, hostname, host->server_stable_id);
}
// This is the safest place to clear this pointer--we do not want the srpl_connection pointer to not
// get reset because of some weird sequence of events, leaving this host unable to be further updated
// or worse.
srpl_connection_release(host->srpl_connection);
host->srpl_connection = NULL;
} else {
if (host != NULL) {
INFO("disconnected host " PRI_S_SRP " server stable ID %" PRIx64, hostname, host->server_stable_id);
}
}
} else {
if (host != NULL) {
INFO("context-free host " PRI_S_SRP " server stable ID %" PRIx64, hostname, host->server_stable_id);
}
}
return true;
}
if (host != NULL) {
if (rcode == dns_rcode_noerror) {
memcpy(&host->server_stable_id, &host->server_state->ula_prefix, sizeof(host->server_stable_id));
}
INFO("local host " PRI_S_SRP " server stable ID %" PRIx64, hostname, host->server_stable_id);
srpl_srp_client_update_finished_event_send(host, rcode);
host->update_server_id = 0;
}
} else
{
if (host != NULL && host->server_state != NULL) {
memcpy(&host->server_stable_id, &host->server_state->ula_prefix, sizeof(host->server_stable_id));
host->update_server_id = 0;
}
}
return false;
}
#endif // SRP_FEATURE_REPLICATION
// We call advertise_finished when a client request has finished, successfully or otherwise.
static void
advertise_finished(adv_host_t *host, char *hostname, srp_server_t *server_state, srpl_connection_t *srpl_connection,
comm_t *connection, message_t *message, int rcode, client_update_t *client, bool send_response)
{
struct iovec iov;
dns_wire_t response;
#if SRP_FEATURE_REPLICATION
if (srp_replication_advertise_finished(host, hostname, server_state, srpl_connection, connection, rcode)) {
return;
}
#else
(void)host;
(void)server_state;
(void)srpl_connection;
#endif // SRP_FEATURE_REPLICATION
INFO("host " PRI_S_SRP ": rcode = " PUB_S_SRP ", lease = %d, key_lease = %d connection = %p", hostname, dns_rcode_name(rcode),
client ? client->host_lease : 0, client ? client->key_lease : 0, connection);
// This can happen if we turn off replication in the middle of an update of a replicated host.
if (connection == NULL) {
return;
}
if (!send_response) {
INFO("not sending response.");
return;
}
memset(&response, 0, DNS_HEADER_SIZE);
response.id = message->wire.id;
response.bitfield = message->wire.bitfield;
dns_rcode_set(&response, rcode);
dns_qr_set(&response, dns_qr_response);
iov.iov_base = &response;
// If this was a successful update, send back the lease time, which will either
// be what the client asked for, or a shorter lease, depending on what limit has
// been set.
if (client != NULL) {
dns_towire_state_t towire;
memset(&towire, 0, sizeof towire);
towire.p = &response.data[0]; // We start storing RR data here.
towire.lim = &response.data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
towire.message = &response;
response.qdcount = 0;
response.ancount = 0;
response.nscount = 0;
response.arcount = htons(1);
dns_edns0_header_to_wire(&towire, DNS_MAX_UDP_PAYLOAD, 0, 0, 1);
dns_rdlength_begin(&towire);
dns_u16_to_wire(&towire, dns_opt_update_lease); // OPTION-CODE
dns_edns0_option_begin(&towire); // OPTION-LENGTH
dns_u32_to_wire(&towire, client->host_lease); // LEASE (e.g. 1 hour)
dns_u32_to_wire(&towire, client->key_lease); // KEY-LEASE (7 days)
dns_edns0_option_end(&towire); // Now we know OPTION-LENGTH
if (!client->serial_sent) {
dns_u16_to_wire(&towire, dns_opt_srp_serial); // OPTION-CODE
dns_edns0_option_begin(&towire); // OPTION-LENGTH
dns_u32_to_wire(&towire, client->serial_number); // LEASE (e.g. 1 hour)
dns_edns0_option_end(&towire); // Now we know OPTION-LENGTH
}
dns_rdlength_end(&towire);
// It should not be possible for this to happen; if it does, the client
// might not renew its lease in a timely manner.
if (towire.error) {
ERROR("unexpectedly failed to send EDNS0 lease option.");
iov.iov_len = DNS_HEADER_SIZE;
} else {
iov.iov_len = towire.p - (uint8_t *)&response;
}
} else {
iov.iov_len = DNS_HEADER_SIZE;
}
ioloop_send_message(connection, message, &iov, 1);
}
static void
retry_callback(void *context)
{
adv_host_t *host = (adv_host_t *)context;
if (host->update == NULL) {
update_from_host(host);
} else {
start_host_update(host);
}
}
static void
wait_retry(adv_host_t *host)
{
int64_t now = ioloop_timenow();
#define MIN_HOST_RETRY_INTERVAL 15
#define MAX_HOST_RETRY_INTERVAL 120
// If we've been retrying long enough for the lease to expire, give up.
if (!host->lease_expiry || host->lease_expiry < now) {
INFO("host lease has expired, not retrying: lease_expiry = %" PRId64
" now = %" PRId64 " difference = %" PRId64, host->lease_expiry, now, host->lease_expiry - now);
delete_host(host);
return;
}
if (host->retry_interval == 0) {
host->retry_interval = MIN_HOST_RETRY_INTERVAL;
} else if (host->retry_interval < MAX_HOST_RETRY_INTERVAL) {
host->retry_interval *= 2;
}
INFO("waiting %d seconds...", host->retry_interval);
ioloop_add_wake_event(host->retry_wakeup, host, retry_callback, NULL, host->retry_interval * 1000);
}
static bool
setup_shared_registration_txn(srp_server_t *server_state)
{
if (server_state->shared_registration_txn == NULL) {
DNSServiceRef sdref;
int err = DNSServiceCreateConnection(&sdref);
if (err != kDNSServiceErr_NoError) {
return false;
}
server_state->shared_registration_txn = ioloop_dnssd_txn_add(sdref, NULL, NULL, NULL);
if (server_state->shared_registration_txn == NULL) {
ERROR("unable to create shared connection for registration.");
dns_service_op_not_to_be_freed = NULL;
DNSServiceRefDeallocate(sdref);
return false;
}
dns_service_op_not_to_be_freed = server_state->shared_registration_txn->sdref;
INFO("server_state->shared_registration_txn = %p sdref = %p", server_state->shared_registration_txn, sdref);
}
return true;
}
static void
record_txn_forget(adv_record_t *record, intptr_t affected_service_pointer,
const char *parent_type, const void *parent_pointer, const char *hostname)
{
if (record == NULL) {
return;
}
if (record->rref != NULL && record->shared_txn == affected_service_pointer) {
INFO("forgetting rref %p on " PUB_S_SRP " %p " PRI_S_SRP, record->rref, parent_type, parent_pointer, hostname);
record->rref = NULL;
}
}
static void
record_vec_txns_forget(adv_record_vec_t *records, intptr_t affected_service_pointer,
const char *parent_type, const void *parent_pointer, const char *hostname)
{
if (records == NULL) {
return;
}
for (int i = 0; i < records->num; i++) {
record_txn_forget(records->vec[i], affected_service_pointer, parent_type, parent_pointer, hostname);
}
}
static void
instance_vec_txns_forget(adv_instance_vec_t *instances, intptr_t affected_service_pointer,
const char *parent_type, const void *parent_pointer, const char *hostname)
{
if (instances == NULL) {
return;
}
for (int i = 0; i < instances->num; i++) {
adv_instance_t *instance = instances->vec[i];
if (instance != NULL && instance->txn != NULL && instance->txn->sdref != NULL &&
instance->shared_txn == affected_service_pointer)
{
INFO("forgetting sdref %p on " PUB_S_SRP " %p " PRI_S_SRP " instance " PRI_S_SRP " . " PRI_S_SRP,
instance->txn->sdref,
parent_type, parent_pointer, hostname, instance->instance_name, instance->service_type);
instance->txn->sdref = NULL;
}
}
}
static void
host_txns_forget(adv_host_t *host, intptr_t affected_service_pointer)
{
// We call this when the shared transaction fails for some reason. That failure invalidates all the subsidiary
// RecordRefs and ServiceRefs hanging off of the shared transaction; to avoid holding on to invalid pointers,
// we traverse the registration database and NULL out all the rrefs and sdrefs that relate to the subsidiary
// service pointer.
record_vec_txns_forget(host->addresses, affected_service_pointer, "host", host, host->name);
instance_vec_txns_forget(host->instances, affected_service_pointer, "host", host, host->name);
record_txn_forget(host->key_record, affected_service_pointer, "host key", host, host->name);
if (host->update != NULL) {
record_vec_txns_forget(host->update->remove_addresses, affected_service_pointer,
"host update remove addresses", host->update, host->name);
record_vec_txns_forget(host->update->add_addresses, affected_service_pointer,
"host update add addresses", host->update, host->name);
record_txn_forget(host->update->key, affected_service_pointer, "host update key", host->update, host->name);
instance_vec_txns_forget(host->update->update_instances, affected_service_pointer,
"host update update instances", host->update, host->name);
instance_vec_txns_forget(host->update->remove_instances, affected_service_pointer,
"host update remove instances", host->update, host->name);
instance_vec_txns_forget(host->update->renew_instances, affected_service_pointer,
"host update renew instances", host->update, host->name);
instance_vec_txns_forget(host->update->add_instances, affected_service_pointer,
"host update add instances", host->update, host->name);
}
}
static void
service_disconnected(srp_server_t *server_state, intptr_t service_pointer)
{
if (service_pointer == (intptr_t)server_state->shared_registration_txn &&
server_state->shared_registration_txn != NULL)
{
INFO("server_state->shared_registration_txn = %p sdref = %p",
server_state->shared_registration_txn, server_state->shared_registration_txn->sdref);
// For every host that's active right now that has transactions on this shared transaction, forget all those
// transactions. The txn_cancel following this will free all of the memory in the client stub.
for (adv_host_t *host = server_state->hosts; host != NULL; host = host->next) {
host_txns_forget(host, service_pointer);
}
dns_service_op_not_to_be_freed = NULL;
ioloop_dnssd_txn_cancel(server_state->shared_registration_txn);
ioloop_dnssd_txn_release(server_state->shared_registration_txn);
server_state->shared_registration_txn = NULL;
}
}
static void
client_free(client_update_t *client)
{
srp_update_free_parts(client->instances, NULL, client->services, client->removes, client->host);
if (client->parsed_message != NULL) {
dns_message_free(client->parsed_message);
}
if (client->message != NULL) {
ioloop_message_release(client->message);
}
if (client->connection != NULL) {
ioloop_comm_release(client->connection);
}
free(client);
}
static void
adv_record_vec_remove_update(adv_record_vec_t *vec, adv_update_t *update)
{
for (int i = 0; i < vec->num; i++) {
if (vec->vec[i] != NULL && vec->vec[i]->update != NULL && vec->vec[i]->update == update) {
RELEASE_HERE(vec->vec[i]->update, adv_update);
vec->vec[i]->update = NULL;
}
}
}
static void
adv_instance_vec_remove_update(adv_instance_vec_t *vec, adv_update_t *update)
{
for (int i = 0; i < vec->num; i++) {
if (vec->vec[i] != NULL && vec->vec[i]->update != NULL && vec->vec[i]->update == update) {
RELEASE_HERE(vec->vec[i]->update, adv_update);
vec->vec[i]->update = NULL;
}
}
}
static void
adv_instances_cancel(adv_instance_vec_t *instances)
{
for (int i = 0; i < instances->num; i++) {
adv_instance_t *instance = instances->vec[i];
if (instance != NULL && instance->txn != NULL) {
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
instance->txn = NULL;
}
}
}
static void
adv_update_free_instance_vectors(adv_update_t *NONNULL update)
{
if (update->update_instances != NULL) {
adv_instance_vec_remove_update(update->update_instances, update);
adv_instances_cancel(update->update_instances);
RELEASE_HERE(update->update_instances, adv_instance_vec);
update->update_instances = NULL;
}
if (update->remove_instances != NULL) {
adv_instance_vec_remove_update(update->remove_instances, update);
RELEASE_HERE(update->remove_instances, adv_instance_vec);
update->remove_instances = NULL;
}
if (update->renew_instances != NULL) {
adv_instance_vec_remove_update(update->renew_instances, update);
RELEASE_HERE(update->renew_instances, adv_instance_vec);
update->renew_instances = NULL;
}
if (update->add_instances != NULL) {
adv_instance_vec_remove_update(update->add_instances, update);
adv_instances_cancel(update->add_instances);
RELEASE_HERE(update->add_instances, adv_instance_vec);
update->add_instances = NULL;
}
}
static void
adv_update_finalize(adv_update_t *NONNULL update)
{
if (update->host != NULL) {
RELEASE_HERE(update->host, adv_host);
}
if (update->client != NULL) {
client_free(update->client);
update->client = NULL;
}
if (update->remove_addresses != NULL) {
adv_record_vec_remove_update(update->remove_addresses, update);
RELEASE_HERE(update->remove_addresses, adv_record_vec);
}
if (update->add_addresses != NULL) {
adv_record_vec_remove_update(update->add_addresses, update);
RELEASE_HERE(update->add_addresses, adv_record_vec);
}
adv_update_free_instance_vectors(update);
if (update->key != NULL) {
RELEASE_HERE(update->key, adv_record);
}
free(update);
}
static void
adv_update_cancel(adv_update_t *NONNULL update)
{
adv_host_t *host = update->host;
bool faulted = false;
RETAIN_HERE(update, adv_update); // ensure that update remains valid for the duration of this function call.
if (host != NULL) {
RETAIN_HERE(host, adv_host); // in case the update is holding the last reference to the host
RELEASE_HERE(update->host, adv_host);
update->host = NULL;
INFO("cancelling update %p for host " PRI_S_SRP, update, host->registered_name);
if (host->update == update) {
RELEASE_HERE(host->update, adv_update);
host->update = NULL;
}
// In case we needed to re-register some of the host's addresses, remove the update pointer from them.
if (host->addresses != NULL) {
for (int i = 0; i < host->addresses->num; i++) {
adv_record_t *record = host->addresses->vec[i];
if (record->update == update) {
RELEASE_HERE(host->addresses->vec[i]->update, adv_update);
record->update = NULL;
}
}
}
} else {
INFO("canceling update with no host.");
}
adv_update_free_instance_vectors(update);
if (update->add_addresses != NULL) {
// Any record that we attempted to add as part of this update should be removed because the update failed.
for (int i = 0; i < update->add_addresses->num; i++) {
adv_record_t *record = update->add_addresses->vec[i];
if (record != NULL) {
if (host == NULL) {
if (!faulted) {
FAULT("unable to clean up host address registration because host object is gone from update.");
faulted = true;
}
} else {
if (record->rref != NULL) {
remove_shared_record(host->server_state, record);
}
}
}
}
adv_record_vec_remove_update(update->add_addresses, update);
RELEASE_HERE(update->add_addresses, adv_record_vec);
update->add_addresses = NULL;
}
if (update->remove_addresses != NULL) {
adv_record_vec_remove_update(update->remove_addresses, update);
RELEASE_HERE(update->remove_addresses, adv_record_vec);
update->remove_addresses = NULL;
}
if (update->key != NULL) {
if (update->key->update != NULL) {
RELEASE_HERE(update->key->update, adv_update);
update->key->update = NULL;
}
// Any record that we attempted to add as part of this update should be removed because the update failed.
if (update->key->rref != NULL) {
if (host == NULL) {
if (!faulted) {
FAULT("unable to clean up host key registration because host object is gone from update.");
faulted = true;
}
} else {
remove_shared_record(host->server_state, update->key);
}
}
RELEASE_HERE(update->key, adv_record);
update->key = NULL;
}
if (host != NULL) {
RELEASE_HERE(host, adv_host);
}
RELEASE_HERE(update, adv_update);
}
static void
update_failed(adv_update_t *update, int rcode, bool expire, bool send_response)
{
// Retain the update for the life of this function call, since we may well release the last other reference to it.
RETAIN_HERE(update, adv_update);
// If we still have a client waiting for the result of this update, tell it we failed.
// Updates that have never worked are abandoned when the client is notified.
if (update->client != NULL) {
adv_host_t *host = update->host;
client_update_t *client = update->client;
adv_update_cancel(update);
advertise_finished(host, host->name, host->server_state, host->srpl_connection,
client->connection, client->message, rcode, NULL, send_response);
client_free(client);
update->client = NULL;
// If we don't have a lease yet, or the old lease has expired, remove the host.
// However, if the expire flag is false, it's because we're already finalizing the
// host, so doing an expiry here would double free the host. In this case, we leave
// it to the caller to do the expiry (really, to finalize the host).
if (expire && (host->lease_expiry == 0 || host->lease_expiry <= ioloop_timenow())) {
delete_host(host);
}
RELEASE_HERE(update, adv_update);
return;
}
adv_update_cancel(update);
RELEASE_HERE(update, adv_update);
}
static void
host_addr_free(adv_host_t *host)
{
int i;
// We can't actually deallocate the address vector until the host object is collected, so deallocate the address
// records.
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
if (host->addresses->vec[i] != NULL) {
INFO("Removing AAAA record for " PRI_S_SRP, host->registered_name);
remove_shared_record(host->server_state, host->addresses->vec[i]);
RELEASE_HERE(host->addresses->vec[i], adv_record);
host->addresses->vec[i] = NULL;
}
}
host->addresses->num = 0;
}
}
// Free just those parts that are no longer needed when the host is no longer valid.
static void
host_invalidate(adv_host_t *host)
{
int i;
// Get rid of the retry wake event.
if (host->retry_wakeup != NULL) {
ioloop_cancel_wake_event(host->retry_wakeup);
}
// Remove the address records.
host_addr_free(host);
// Remove the services.
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
if (host->instances->vec[i] != NULL) {
if (host->instances->vec[i] != NULL && host->instances->vec[i]->txn != NULL) {
ioloop_dnssd_txn_cancel(host->instances->vec[i]->txn);
ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
host->instances->vec[i]->txn = NULL;
}
}
}
RELEASE_HERE(host->instances, adv_instance_vec);
host->instances = NULL;
}
if (host->update != NULL) {
RELEASE_HERE(host->update, adv_update);
host->update = NULL;
}
if (host->key_record != NULL) {
remove_shared_record(host->server_state, host->key_record);
RELEASE_HERE(host->key_record, adv_record);
host->key_record = NULL;
}
host->update = NULL;
host->removed = true;
}
// Free everything associated with the host, including the host object.
static void
adv_host_finalize(adv_host_t *host)
{
// Just in case this hasn't happened yet, free the non-identifying host data and cancel any outstanding
// transactions.
host_invalidate(host);
if (host->addresses != NULL) {
RELEASE_HERE(host->addresses, adv_record_vec);
host->addresses = NULL;
}
if (host->key_rdata != NULL) {
free(host->key_rdata);
host->key_rdata = NULL;
}
if (host->key_record != NULL) {
RELEASE_HERE(host->key_record, adv_record);
host->key_record = NULL;
}
if (host->message != NULL) {
ioloop_message_release(host->message);
host->message = NULL;
}
// We definitely don't want a lease callback at this point.
if (host->lease_wakeup != NULL) {
ioloop_cancel_wake_event(host->lease_wakeup);
ioloop_wakeup_release(host->lease_wakeup);
}
// Get rid of the retry wake event.
if (host->retry_wakeup != NULL) {
ioloop_cancel_wake_event(host->retry_wakeup);
ioloop_wakeup_release(host->retry_wakeup);
}
INFO("removed " PRI_S_SRP ", key_id %x", host->name ? host->name : "<null>", host->key_id);
// In the default case, host->name and host->registered_name point to the same memory: we don't want a double free.
if (host->registered_name == host->name) {
host->registered_name = NULL;
}
if (host->name != NULL) {
free(host->name);
}
if (host->registered_name != NULL) {
free(host->registered_name);
}
free(host);
}
void
srp_adv_host_release_(adv_host_t *host, const char *file, int line)
{
RELEASE(host, adv_host);
}
void
srp_adv_host_retain_(adv_host_t *host, const char *file, int line)
{
RETAIN(host, adv_host);
}
bool
srp_adv_host_valid(adv_host_t *host)
{
// If the host has been removed, it's not valid.
if (host->removed) {
return false;
}
// If there is no key data, the host is invalid.
if (host->key_rdata == NULL) {
return false;
}
return true;
}
int
srp_current_valid_host_count(srp_server_t *server_state)
{
adv_host_t *host;
int count = 0;
for (host = server_state->hosts; host; host = host->next) {
if (srp_adv_host_valid(host)) {
count++;
}
}
return count;
}
int
srp_hosts_to_array(srp_server_t *server_state, adv_host_t **host_array, int max)
{
int count = 0;
for (adv_host_t *host = server_state->hosts; count < max && host != NULL; host = host->next) {
if (srp_adv_host_valid(host)) {
host_array[count] = host;
RETAIN_HERE(host_array[count], adv_host);
count++;
}
}
return count;
}
adv_host_t *
srp_adv_host_copy_(srp_server_t *server_state, dns_name_t *name, const char *file, int line)
{
for (adv_host_t *host = server_state->hosts; host; host = host->next) {
if (srp_adv_host_valid(host) && dns_names_equal_text(name, host->name)) {
RETAIN(host, adv_host);
return host;
}
}
return NULL;
}
static void
host_remove(adv_host_t *host)
{
// This host is no longer valid. Get rid of the associated transactions and other stuff that's not required to
// identify it, and then release the host list reference to it.
host_invalidate(host);
// Note that while adv_host_finalize calls host_invalidate, adv_host_finalize won't necessarily be called here because there
// may be outstanding references on the host. It's okay to call host_invalidate twice--the second time it should be
// a no-op.
RELEASE_HERE(host, adv_host);
}
static adv_host_t **
host_ready(adv_host_t *host)
{
adv_host_t **p_hosts;
// Find the host on the list of hosts.
for (p_hosts = &host->server_state->hosts; *p_hosts != NULL; p_hosts = &(*p_hosts)->next) {
if (*p_hosts == host) {
break;
}
}
if (*p_hosts == NULL) {
ERROR("called with nonexistent host.");
return NULL;
}
// It's possible that we got an update to this host, but haven't processed it yet. In this
// case, we don't want to get rid of the host, but we do want to get rid of it later if the
// update fails. So postpone the removal for a bit.
if (host->update != NULL) {
INFO("reached with pending updates on host " PRI_S_SRP ".", host->registered_name);
ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, 10 * 1000);
host->lease_expiry = ioloop_timenow() + 10 * 1000; // ten seconds
return NULL;
}
return p_hosts;
}
static void
lease_callback(void *context)
{
int64_t now = ioloop_timenow();
adv_host_t **p_hosts, *host = context;
int i, num_instances = 0;
p_hosts = host_ready(host);
if (p_hosts == NULL) {
INFO("host expired");
return;
}
INFO("host " PRI_S_SRP, host->name);
// If the host entry lease has expired, any instance leases have also.
if (host->lease_expiry < now) {
delete_host(host);
return;
}
INFO("host " PRI_S_SRP " is still alive", host->name);
if (host->instances == NULL) {
INFO("no instances");
return;
}
// Find instances that have expired and release them.
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *instance = host->instances->vec[i];
if (instance == NULL) {
continue;
}
if (instance->lease_expiry < now) {
INFO("host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP " has expired",
host->name, instance->instance_name, instance->service_type);
// We have to release the transaction so that we can release the reference the transaction has to the instance.
if (instance->txn != NULL) {
dnssd_txn_t *txn = instance->txn;
instance->txn = NULL;
ioloop_dnssd_txn_release(txn);
}
host->instances->vec[i] = NULL;
RELEASE_HERE(instance, adv_instance);
continue;
} else {
INFO("host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP " has not expired",
host->name, instance->instance_name, instance->service_type);
}
num_instances++;
}
int64_t next_lease_expiry = host->lease_expiry;
// Get rid of holes in the host instance vector and compute the next lease callback time
int j = 0;
for (i = 0; i < host->instances->num; i++) {
if (host->instances->vec[i] != NULL) {
adv_instance_t *instance = host->instances->vec[i];
host->instances->vec[j++] = instance;
if (next_lease_expiry > instance->lease_expiry) {
next_lease_expiry = instance->lease_expiry;
}
}
}
INFO("host " PRI_S_SRP " lost %d instances", host->name, host->instances->num - j);
host->instances->num = j;
// Now set a timer for the next lease expiry event
uint64_t when = next_lease_expiry - now;
if (when > INT32_MAX) {
when = INT32_MAX;
}
ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, (uint32_t)when);
}
// Called when we definitely want to make all the advertisements associated with a host go away.
static void
delete_host(void *context)
{
adv_host_t **p_hosts, *host = context;
p_hosts = host_ready(host);
if (p_hosts == NULL) {
return;
}
INFO("deleting host " PRI_S_SRP, host->name);
// De-link the host.
*p_hosts = host->next;
// Get rid of any transactions attached to the host, any timer events, and any other associated data.
host_remove(host);
}
// We remember the message that produced this instance so that if we get an update that doesn't update everything,
// we know which instances /were/ updated by this particular message. instance->recent_message is a copy of the pointer
// to the message that most recently updated this instance. When we set instance->recent_message, we don't yet know
// if the update is going to succeed; if it fails, we can't have changed update->message. If it succeeds, then when we
// get down to update_finished, we can compare the message that did the update to instance->recent_message; if they
// are the same, then we set the message on the instance.
// Note that we only set instance->recent_message during register_instance_completion, so there's no timing race that
// could happen as a result of receiving a second update to the same instance before the first has been processed.
static void
set_instance_message(adv_instance_t *instance, message_t *message)
{
if (message != NULL && (ptrdiff_t)message == instance->recent_message) {
if (instance->message != NULL) {
ioloop_message_release(instance->message);
}
instance->message = message;
ioloop_message_retain(instance->message);
instance->recent_message = 0;
}
}
static void
update_finished(adv_update_t *update)
{
adv_host_t *host = update->host;
client_update_t *client = update->client;
int num_addresses = 0;
adv_record_vec_t *addresses = NULL;
int num_instances = 0;
adv_instance_vec_t *instances = NULL;
int i, j;
int num_host_addresses = 0;
int num_add_addresses = 0;
int num_host_instances = 0;
int num_add_instances = 0;
message_t *message = NULL;
// Get the message that produced the update, if any
if (client != NULL) {
message = client->message;
}
// Once an update has finished, we need to apply all of the proposed changes to the host object.
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
if (host->addresses->vec[i] != NULL &&
(update->remove_addresses == NULL || update->remove_addresses->vec[i] == NULL))
{
num_host_addresses++;
}
}
}
if (update->add_addresses != NULL) {
for (i = 0; i < update->add_addresses->num; i++) {
if (update->add_addresses->vec[i] != NULL) {
num_add_addresses++;
}
}
}
num_addresses = num_host_addresses + num_add_addresses;
if (num_addresses > 0) {
addresses = adv_record_vec_create(num_addresses);
if (addresses == NULL) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
j = 0;
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
adv_record_t *rec = host->addresses->vec[i];
if (rec != NULL && (update->remove_addresses == NULL || update->remove_addresses->vec[i] == NULL))
{
#ifdef DEBUG_VERBOSE
uint8_t *rdp = rec->rdata;
if (rec->rrtype == dns_rrtype_aaaa) {
SEGMENTED_IPv6_ADDR_GEN_SRP(rdp, rdp_buf);
INFO("retaining " PRI_SEGMENTED_IPv6_ADDR_SRP "on host " PRI_S_SRP,
SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
} else {
IPv4_ADDR_GEN_SRP(rec->rdata, addr_buf);
INFO("retaining " PRI_IPv4_ADDR_SRP "on host " PRI_S_SRP,
IPv4_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
}
#endif
addresses->vec[j] = rec;
RETAIN_HERE(addresses->vec[j], adv_record);
j++;
}
}
}
if (update->add_addresses != NULL) {
for (i = 0; i < update->add_addresses->num; i++) {
adv_record_t *rec = update->add_addresses->vec[i];
if (rec != NULL) {
#ifdef DEBUG_VERBOSE
uint8_t *rdp = rec->rdata;
if (rec->rrtype == dns_rrtype_aaaa) {
SEGMENTED_IPv6_ADDR_GEN_SRP(rdp, rdp_buf);
INFO("adding " PRI_SEGMENTED_IPv6_ADDR_SRP "to host " PRI_S_SRP,
SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
} else {
IPv4_ADDR_GEN_SRP(rec->rdata, addr_buf);
INFO("adding " PRI_IPv4_ADDR_SRP "to host " PRI_S_SRP,
IPv4_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
}
#endif
addresses->vec[j] = rec;
RETAIN_HERE(addresses->vec[j], adv_record);
j++;
if (rec->update != NULL) {
RELEASE_HERE(update->add_addresses->vec[i]->update, adv_update);
update->add_addresses->vec[i]->update = NULL;
}
RELEASE_HERE(update->add_addresses->vec[i], adv_record);
update->add_addresses->vec[i] = NULL;
}
}
}
addresses->num = j;
}
// Do the same for instances.
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
// We're counting the number of non-NULL instances in the host instance vector, which is probably always
// going to be the same as host->instances->num, but we are not relying on this.
if (host->instances->vec[i] != NULL) {
num_host_instances++;
}
}
}
if (update->add_instances != NULL) {
for (i = 0; i < update->add_instances->num; i++) {
if (update->add_instances->vec[i] != NULL) {
num_add_instances++;
}
}
}
num_instances = num_add_instances + num_host_instances;
instances = adv_instance_vec_create(num_instances);
if (instances == NULL) {
if (addresses != NULL) {
RELEASE_HERE(addresses, adv_record_vec);
addresses = NULL;
}
update_failed(update, dns_rcode_servfail, true, true);
return;
}
j = 0;
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
if (j == num_instances) {
FAULT("j (%d) == num_instances (%d)", j, num_instances);
break;
}
if (update->update_instances != NULL && update->update_instances->vec[i] != NULL) {
adv_instance_t *instance = update->update_instances->vec[i];
if (update->remove_instances != NULL && update->remove_instances->vec[i] != NULL) {
adv_instance_t *removed_instance = update->remove_instances->vec[i];
INFO("removed instance " PRI_S_SRP " " PRI_S_SRP " %d",
removed_instance->instance_name, removed_instance->service_type, removed_instance->port);
INFO("added instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
} else {
INFO("updated instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
}
instances->vec[j] = instance;
RETAIN_HERE(instances->vec[j], adv_instance);
j++;
RELEASE_HERE(update->update_instances->vec[i], adv_instance);
update->update_instances->vec[i] = NULL;
if (instance->update != NULL) {
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
}
set_instance_message(instance, message);
} else {
if (update->remove_instances != NULL && update->remove_instances->vec[i] != NULL) {
adv_instance_t *instance = update->remove_instances->vec[i];
INFO("removed instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
instances->vec[j] = instance;
RETAIN_HERE(instances->vec[j], adv_instance);
j++;
instance->removed = true;
if (message != NULL) {
if (instance->message != NULL) {
ioloop_message_release(instance->message);
}
instance->message = message;
ioloop_message_retain(instance->message);
}
if (instance->txn == NULL) {
ERROR("instance " PRI_S_SRP "." PRI_S_SRP " for host " PRI_S_SRP " has no connection.",
instance->instance_name, instance->service_type, host->name);
} else {
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
instance->txn = NULL;
}
} else {
if (host->instances->vec[i] != NULL) {
adv_instance_t *instance = host->instances->vec[i];
INFO("kept instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
instances->vec[j] = instance;
RETAIN_HERE(instances->vec[j], adv_instance);
j++;
set_instance_message(instance, message);
}
}
}
}
}
// Set the message on all of the instances that were renewed to the current message.
if (update->renew_instances != NULL) {
for (i = 0; i < update->renew_instances->num; i++) {
adv_instance_t *instance = update->renew_instances->vec[i];
if (instance != NULL) {
if (message != NULL) { // Should never not be NULL for a renew, of course.
if (instance->message != NULL) {
ioloop_message_release(instance->message);
}
instance->message = message;
ioloop_message_retain(instance->message);
}
instance->recent_message = 0;
INFO("renewed instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
}
}
}
if (update->add_instances != NULL) {
for (i = 0; i < update->add_instances->num; i++) {
adv_instance_t *instance = update->add_instances->vec[i];
if (instance != NULL) {
INFO("added instance " PRI_S_SRP " " PRI_S_SRP " %d",
instance->instance_name, instance->service_type, instance->port);
instances->vec[j] = instance;
RETAIN_HERE(instances->vec[j], adv_instance);
j++;
RELEASE_HERE(update->add_instances->vec[i], adv_instance);
update->add_instances->vec[i] = NULL;
if (instance->update != NULL) {
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
}
set_instance_message(instance, message);
}
}
}
instances->num = j;
// At this point we can safely modify the host object because we aren't doing any more
// allocations.
if (host->addresses != NULL) {
RELEASE_HERE(host->addresses, adv_record_vec);
}
host->addresses = addresses; // Either NULL or else returned retained by adv_record_vec_create().
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *instance = host->instances->vec[i];
if (instance != NULL) {
INFO("old host instance %d " PRI_S_SRP "." PRI_S_SRP " for host " PRI_S_SRP " has ref_count %d",
i, instance->instance_name, instance->service_type, host->name, instance->ref_count);
} else {
INFO("old host instance %d is NULL", i);
}
}
RELEASE_HERE(host->instances, adv_instance_vec);
}
host->instances = instances;
if (host->key_record != NULL && update->key != NULL && host->key_record != update->key) {
remove_shared_record(host->server_state, host->key_record);
RELEASE_HERE(host->key_record, adv_record);
host->key_record = NULL;
}
if (host->key_record == NULL && update->key != NULL) {
host->key_record = update->key;
RETAIN_HERE(host->key_record, adv_record);
if (update->key->update != NULL) {
RELEASE_HERE(update->key->update, adv_update);
update->key->update = NULL;
}
}
// Remove any instances that are to be removed
if (update->remove_addresses != NULL) {
for (i = 0; i < update->remove_addresses->num; i++) {
adv_record_t *record = update->remove_addresses->vec[i];
if (record != NULL) {
remove_shared_record(host->server_state, record);
}
}
}
time_t lease_offset = 0;
if (client) {
// If this is an update from a client, do the serial number processing.
if (client->serial_sent) {
INFO("host " PRI_S_SRP " serial number %" PRIu32 "->%" PRIu32 " (from client)",
host->name, host->serial_number, client->serial_number);
host->serial_number = client->serial_number;
host->have_serial_number = true;
} else {
// When the client doesn't know its serial number, and we have a recorded serial number, we want to make up a new
// serial number that's enough ahead of the old one that it's unlikely there's a higher number elsewhere from recent
// communications between the client and a server we're not currently able to reach.
if (host->have_serial_number) {
INFO("host " PRI_S_SRP " serial number %" PRIu32 "->%" PRIu32 " (from history)",
host->name, host->serial_number, host->serial_number + 50);
client->serial_number = host->serial_number + 50;
host->have_serial_number = true;
} else {
host->serial_number = (uint32_t)time(NULL);
client->serial_number = host->serial_number;
INFO("host " PRI_S_SRP " serial number NONE->%" PRIu32 " (from time)",
host->name, client->serial_number);
host->have_serial_number = true;
}
}
if (host->message != NULL) {
ioloop_message_release(host->message);
}
host->message = client->message;
ioloop_message_retain(host->message);
advertise_finished(host, host->name, host->server_state, host->srpl_connection,
client->connection, client->message, dns_rcode_noerror, client, true);
client_free(client);
update->client = NULL;
if (host->message->received_time != 0) {
host->update_time = host->message->received_time;
lease_offset = srp_time() - host->update_time;
INFO("setting host update time based on message received time: %ld, lease offset = %ld",
host->update_time, lease_offset);
} else {
INFO("setting host update time based on current time: %ld", host->message->received_time);
host->update_time = srp_time();
}
// It would probably be harmless to set this for replications, since the value currently wouldn't change,
// but to avoid future issues we only set this if it's a direct SRP update and not a replicated update.
if (host->message->lease == 0) {
host->message->lease = host->lease_interval;
host->message->key_lease = host->key_lease;
}
} else {
INFO("lease offset = %ld", lease_offset);
lease_offset = srp_time() - host->update_time;
}
RETAIN_HERE(update, adv_update); // We need to hold a reference to the update since this might be the last.
// The update should still be on the host.
if (host->update == NULL) {
ERROR("p_update is null.");
} else {
RELEASE_HERE(host->update, adv_update);
host->update = NULL;
}
// Reset the retry interval, since we succeeded in updating.
host->retry_interval = 0;
// Set the lease time based on this update. Even if we scheduled an update for the next time we
// enter the dispatch loop, we still want to schedule a lease expiry here, because it's possible
// that in the process of returning to the dispatch loop, the scheduled update will be removed.
host->lease_interval = update->host_lease;
host->key_lease = update->key_lease;
// We want the lease expiry event to fire the next time the lease on any instance expires, or
// at the time the lease for the current update would expire, whichever is sooner.
int64_t next_lease_expiry = INT64_MAX;
int64_t now = ioloop_timenow();
#define LEASE_EXPIRY_DEBUGGING 1
// update->lease_expiry is nonzero if we are re-doing a previous registration.
if (update->lease_expiry != 0) {
if (update->lease_expiry < now) {
#ifdef LEASE_EXPIRY_DEBUGGING
ERROR("lease expiry for host " PRI_S_SRP " happened %" PRIu64 " milliseconds ago.",
host->registered_name, now - update->lease_expiry);
#endif
// Expire the lease when next we hit the run loop
next_lease_expiry = now;
} else {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (1) for host " PRI_S_SRP " set to %" PRId64, host->name,
(int64_t)(update->lease_expiry - now));
#endif
next_lease_expiry = update->lease_expiry;
}
host->lease_expiry = update->lease_expiry;
}
// This is the more usual case.
else {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (2) for host " PRI_S_SRP " set to %ld", host->name, (host->lease_interval - lease_offset) * 1000);
#endif
next_lease_expiry = now + (host->lease_interval - lease_offset) * 1000;
if (next_lease_expiry < now) {
next_lease_expiry = now;
}
host->lease_expiry = next_lease_expiry;
}
// We're doing two things here: setting the lease expiry on instances that were touched by the current
// update, and also finding the soonest update.
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *instance = host->instances->vec[i];
if (instance != NULL) {
// This instance was updated by the current update, so set its lease time to
// next_lease_expiry.
if (instance->message == message) {
if (instance->removed) {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (7) for host " PRI_S_SRP " removed instance " PRI_S_SRP "." PRI_S_SRP
" left at %" PRId64, host->name, instance->instance_name, instance->service_type,
(int64_t)(instance->lease_expiry - now));
#endif
} else {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (4) for host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP " set to %" PRId64,
host->name, instance->instance_name, instance->service_type,
(int64_t)(host->lease_expiry - now));
#endif
instance->lease_expiry = host->lease_expiry;
}
}
// Instance was not updated by this update, so see if it expires sooner than this update
// (which is likely).
else if (instance->lease_expiry > now && instance->lease_expiry < next_lease_expiry) {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (3) for host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP " set to %" PRId64,
host->name, instance->instance_name, instance->service_type,
(int64_t)(instance->lease_expiry - now));
#endif
next_lease_expiry = instance->lease_expiry;
} else {
if (instance->lease_expiry <= now) {
#ifdef LEASE_EXPIRY_DEBUGGING
INFO("lease_expiry (5) for host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP
" in the past at %" PRId64,
host->name, instance->instance_name, instance->service_type,
(int64_t)(now - instance->lease_expiry));
#endif
next_lease_expiry = now;
#ifdef LEASE_EXPIRY_DEBUGGING
} else {
INFO("lease_expiry (6) for host " PRI_S_SRP " instance " PRI_S_SRP "." PRI_S_SRP
" is later than next_lease_expiry by %" PRId64, host->name, instance->instance_name,
instance->service_type, (int64_t)(next_lease_expiry - instance->lease_expiry));
#endif
}
}
}
}
// Now set a timer for the next lease expiry.
uint64_t when = next_lease_expiry - now;
if (when > INT32_MAX) {
when = INT32_MAX;
}
if (next_lease_expiry == now) {
INFO("scheduling immediate call to lease_callback in the run loop for " PRI_S_SRP, host->name);
ioloop_run_async(lease_callback, host);
} else {
INFO("scheduling wakeup to lease_callback in %" PRIu64 " for host " PRI_S_SRP,
when / 1000, host->name);
ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, (uint32_t)when);
}
// Instance vectors can hold circular references to the update object, which won't get freed until we call
// adv_update_finalize, which we will never do because of the circular reference. So break any remaining
// circular references before releasing the update.
adv_update_free_instance_vectors(update);
// This is letting go of the reference we retained earlier in this function, not some outstanding reference retained elsewhere.
RELEASE_HERE(update, adv_update);
}
#ifdef USE_DNSSERVICE_QUEUING
static void
process_dnsservice_error(adv_update_t *update, int err)
{
if (err != kDNSServiceErr_NoError) {
update_failed(update, dns_rcode_servfail, true, true);
if (err == kDNSServiceErr_ServiceNotRunning || err == kDNSServiceErr_DefunctConnection || err == 1) {
if (err == 1) {
FAULT("bogus error code 1");
}
if (update->host != NULL) {
if (update->host->server_state != NULL) {
service_disconnected(update->host->server_state,
(intptr_t)update->host->server_state->shared_registration_txn);
}
wait_retry(update->host);
}
}
}
}
#endif // USE_DNSSERVICE_QUEUING
// When the host registration has completed, we get this callback. Completion either means that we succeeded in
// registering the record, or that something went wrong and the registration has failed.
static void
register_instance_completion(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType error_code,
const char *name, const char *regtype, const char *domain, void *context)
{
(void)flags;
(void)sdref;
adv_instance_t *instance = context;
adv_update_t *update = instance->update;
adv_host_t *host = instance->host;
// Retain the instance for the life of this function, just in case we release stuff that is holding the last reference to it.
RETAIN_HERE(instance, adv_instance);
// It's possible that we could restart a host update due to an error while a callback is still pending on a stale
// update. In this case, we just cancel all of the work that's been done on the stale update (it's probably already
// moot anyway.
if (update != NULL && host->update != update) {
INFO("registration for service " PRI_S_SRP "." PRI_S_SRP " completed with invalid state.", name, regtype);
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
RELEASE_HERE(instance, adv_instance);
return;
}
// We will generally get a callback on success or failure of the initial registration; this is what causes
// the update to complete or fail. We may get subsequent callbacks because of name conflicts. So the first
// time we get a callback, instance->update will always be valid; thereafter, it will not, so null it out.
if (update != NULL) {
RETAIN_HERE(update, adv_update); // We need to hold onto this until we are done with the update.
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
}
if (error_code == kDNSServiceErr_NoError) {
INFO("registration for service " PRI_S_SRP "." PRI_S_SRP "." PRI_S_SRP " -> "
PRI_S_SRP " has completed.", instance->instance_name, instance->service_type, domain,
host->registered_name);
INFO("registration is under " PRI_S_SRP "." PRI_S_SRP PRI_S_SRP, name, regtype,
domain);
// In principle update->instance should always be non-NULL here because a no-error response should
// only happen once or not at all. But just to be safe...
if (update != NULL) {
if (instance->update_pending) {
if (update->client != NULL) {
instance->recent_message = (ptrdiff_t)update->client->message; // for comparison later in update_finished
}
update->num_instances_completed++;
if (update->num_records_completed == update->num_records_started &&
update->num_instances_completed == update->num_instances_started)
{
update_finished(update);
}
RELEASE_HERE(update, adv_update);
instance->update_pending = false;
update = NULL;
}
} else {
ERROR("no error, but update is NULL for instance " PRI_S_SRP " (" PRI_S_SRP
" " PRI_S_SRP " " PRI_S_SRP ")", instance->instance_name, name, regtype, domain);
}
} else {
INFO("registration for service " PRI_S_SRP "." PRI_S_SRP "." PRI_S_SRP " -> "
PRI_S_SRP " failed with code %d", instance->instance_name, instance->service_type, domain,
host->registered_name, error_code);
// If the reason this failed is that we couldn't talk to mDNSResponder, or mDNSResponder disconnected, then we want to retry
// later on in the hopes that mDNSResponder will come back.
if (error_code == kDNSServiceErr_ServiceNotRunning || error_code == kDNSServiceErr_DefunctConnection) {
service_disconnected(host->server_state, instance->shared_txn);
instance->shared_txn = 0;
wait_retry(host);
} else {
if (update != NULL) {
update_failed(update, (error_code == kDNSServiceErr_NameConflict
? dns_rcode_yxdomain
: dns_rcode_servfail), true, true);
if (instance->update != NULL) {
RELEASE_HERE(instance->update, adv_update);
instance->update = NULL;
}
RELEASE_HERE(update, adv_update);
}
}
// The transaction still holds a reference to the instance. instance->txn should never be NULL here. When we cancel
// the transaction, the reference the transaction held on the instance will be released.
if (instance->txn == NULL) {
FAULT("instance->txn is NULL for instance %p!", instance);
} else {
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
instance->txn = NULL;
}
}
RELEASE_HERE(instance, adv_instance);
}
static bool
extract_instance_name(char *instance_name, size_t instance_name_max,
char *service_type, size_t service_type_max, service_instance_t *instance)
{
dns_name_t *end_of_service_type = instance->service->rr->name->next;
size_t service_index;
service_t *service, *base_type;
if (end_of_service_type != NULL) {
if (end_of_service_type->next != NULL) {
end_of_service_type = end_of_service_type->next;
}
}
dns_name_print_to_limit(instance->service->rr->name, end_of_service_type, service_type, service_type_max);
// It's possible that the registration might include subtypes. If so, we need to convert them to the
// format that DNSServiceRegister expects: service_type,subtype,subtype...
service_index = strlen(service_type);
base_type = instance->service->base_type;
for (service = instance->service->next; service != NULL && service->base_type == base_type; service = service->next)
{
if (service_index + service->rr->name->len + 2 > service_type_max) {
ERROR("service name: " PRI_S_SRP " is too long for additional subtype " PRI_S_SRP,
service_type, service->rr->name->data);
return false;
}
service_type[service_index++] = ',';
memcpy(&service_type[service_index], service->rr->name->data, service->rr->name->len + 1);
service_index += service->rr->name->len;
}
// Make a presentation-format version of the service instance name.
dns_name_print_to_limit(instance->name, instance->name != NULL ? instance->name->next : NULL,
instance_name, instance_name_max);
return true;
}
void
srp_format_time_offset(char *buf, size_t buf_len, time_t offset)
{
struct tm tm_now;
time_t when = time(NULL) - offset;
localtime_r(&when, &tm_now);
strftime(buf, buf_len, "%F %T", &tm_now);
}
static bool
register_instance(adv_instance_t *instance)
{
int err = kDNSServiceErr_Unknown;
bool exit_status = false;
srp_server_t *server_state = instance->host->server_state;
// If we don't yet have a shared connection, create one.
if (!setup_shared_registration_txn(server_state)) {
goto exit;
}
DNSServiceRef service_ref = server_state->shared_registration_txn->sdref;
INFO("DNSServiceRegister(%p, " PRI_S_SRP ", " PRI_S_SRP ", " PRI_S_SRP ", %d, %p)",
service_ref, instance->instance_name, instance->service_type, instance->host->registered_name, instance->port, instance);
if (0) {
#ifdef STUB_ROUTER
} else if (server_state->stub_router_enabled) {
DNSServiceAttributeRef attr = DNSServiceAttributeCreate();
if (attr == NULL) {
ERROR("Failed to create new DNSServiceAttributeRef");
err = kDNSServiceErr_NoMemory;
} else {
uint32_t offset = 0;
char time_buf[28];
if (instance->update->client != NULL && instance->update->client->message != NULL &&
instance->update->client->message->received_time != 0)
{
offset = (uint32_t)(srp_time() - instance->update->client->message->received_time);
srp_format_time_offset(time_buf, sizeof(time_buf), offset);
} else {
static char msg[] = "now";
memcpy(time_buf, msg, sizeof(msg));
}
DNSServiceAttributeSetTimestamp(attr, offset);
err = DNSServiceRegisterWithAttribute(&service_ref, (kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename),
server_state->advertise_interface,
instance->instance_name, instance->service_type, local_suffix,
instance->host->registered_name, htons(instance->port), instance->txt_length,
instance->txt_data, attr, register_instance_completion, instance);
DNSServiceAttributeDeallocate(attr);
if (err == kDNSServiceErr_NoError) {
INFO("DNSServiceRegister service_ref %p, TSR for " PRI_S_SRP " set to " PUB_S_SRP,
service_ref, instance->host == NULL ? "<null>" : instance->host->name, time_buf);
}
}
#endif // STUB_ROUTER
} else {
err = DNSServiceRegister(&service_ref,
kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename,
server_state->advertise_interface,
instance->instance_name, instance->service_type, local_suffix,
instance->host->registered_name, htons(instance->port), instance->txt_length,
instance->txt_data, register_instance_completion, instance);
}
// This would happen if we pass NULL for regtype, which we don't, or if we run out of memory, or if
// the server isn't running; in the second two cases, we can always try again later.
if (err != kDNSServiceErr_NoError) {
if (err == kDNSServiceErr_ServiceNotRunning || err == kDNSServiceErr_DefunctConnection ||
err == kDNSServiceErr_BadParam || err == kDNSServiceErr_BadReference || err == 1)
{
if (err == 1) {
FAULT("bogus error code 1");
}
INFO("DNSServiceRegister failed: " PUB_S_SRP ,
err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct");
service_disconnected(server_state, (intptr_t)server_state->shared_registration_txn);
} else {
INFO("DNSServiceRegister failed: %d", err);
}
goto exit;
}
if (instance->update != NULL) {
instance->update->num_instances_started++;
instance->update_pending = true;
}
// After DNSServiceRegister succeeds, it creates a copy of DNSServiceRef that indirectly uses the shared connection,
// so we update it here.
instance->txn = ioloop_dnssd_txn_add_subordinate(service_ref, instance, adv_instance_context_release, NULL);
if (instance->txn == NULL) {
ERROR("no memory for instance transaction.");
goto exit;
}
instance->shared_txn = (intptr_t)server_state->shared_registration_txn;
RETAIN_HERE(instance, adv_instance); // for the callback
exit_status = true;
exit:
return exit_status;
}
// When we get a late name conflict on the hostname, we need to update the host registration and all of the
// service registrations. To do this, we construct an update and then apply it. If there is already an update
// in progress, we put this update at the end of the list.
static void
update_from_host(adv_host_t *host)
{
adv_update_t *update = NULL;
int i;
if (host->update != NULL) {
ERROR("already have an update.");
}
// Allocate the update structure.
update = calloc(1, sizeof *update);
if (update == NULL) {
ERROR("no memory for update.");
goto fail;
}
RETAIN_HERE(update, adv_update);
if (host->addresses != NULL) {
update->add_addresses = adv_record_vec_copy(host->addresses);
if (update->add_addresses == NULL) {
ERROR("no memory for addresses");
goto fail;
}
for (i = 0; i < update->add_addresses->num; i++) {
if (update->add_addresses->vec[i] != NULL) {
update->add_addresses->vec[i]->update = update;
RETAIN_HERE(update, adv_update);
}
}
}
// We can never update more instances than currently exist for this host.
if (host->instances != NULL) {
update->update_instances = adv_instance_vec_copy(host->instances);
if (update->update_instances == NULL) {
ERROR("no memory for update_instances");
goto fail;
}
for (i = 0; i < update->update_instances->num; i++) {
if (update->update_instances->vec[i] != NULL) {
update->update_instances->vec[i]->update = update;
RETAIN_HERE(update, adv_update);
}
}
// We aren't actually adding or deleting any instances, but...
update->remove_instances = adv_instance_vec_create(host->instances->num);
if (update->remove_instances == NULL) {
ERROR("no memory for remove_instances");
goto fail;
}
update->remove_instances->num = host->instances->num;
update->add_instances = adv_instance_vec_create(host->instances->num);
if (update->add_instances == NULL) {
ERROR("no memory for add_instances");
goto fail;
}
update->add_instances->num = host->instances->num;
}
// At this point we have figured out all the work we need to do, so hang it off an update structure.
update->host = host;
RETAIN_HERE(update->host, adv_host);
update->host_lease = host->lease_interval;
update->key_lease = host->key_lease;
update->lease_expiry = host->lease_expiry;
// Stash the update on the host.
host->update = update;
RETAIN_HERE(host->update, adv_update); // host gets a reference
RELEASE_HERE(update, adv_update); // we're done with our reference.
start_host_update(host);
return;
fail:
if (update != NULL) {
adv_update_cancel(update);
RELEASE_HERE(update, adv_update);
}
wait_retry(host);
return;
}
// When the host registration has completed, we get this callback. Completion either means that we succeeded in
// registering the record, or that something went wrong and the registration has failed.
static void
register_host_record_completion(DNSServiceRef sdref, DNSRecordRef rref,
DNSServiceFlags flags, DNSServiceErrorType error_code, void *context)
{
adv_record_t *record = context;
adv_host_t *host = NULL;
adv_update_t *update = NULL;
(void)sdref;
(void)rref;
(void)error_code;
(void)flags;
// This can happen if for some reason DNSServiceRemoveRecord returns something other than success. In this case, all
// the cleanup that can be done has already been done, and all we can do is ignore the problem.
if (record->rref == NULL) {
ERROR("null rref");
return;
}
// For analyzer, can't actually happen.
if (record == NULL) {
ERROR("null record");
return;
}
host = record->host;
if (host == NULL) {
ERROR("no host");
return;
}
// Make sure record remains valid for the duration of this call.
RETAIN_HERE(record, adv_record);
// It's possible that we could restart a host update due to an error while a callback is still pending on a stale
// update. In this case, we just cancel all of the work that's been done on the stale update (it's probably already
// moot anyway.
if (record->update != NULL && host->update != record->update) {
INFO("registration for host record completed with invalid state.");
adv_update_cancel(record->update);
RELEASE_HERE(record->update, adv_update);
record->update = NULL;
remove_shared_record(host->server_state, record); // This will prevent further callbacks and release the reference held by the transaction.
RELEASE_HERE(record, adv_record); // The callback has a reference to the record.
RELEASE_HERE(record, adv_record); // Release the reference to the record that we retained at the beginning
return;
}
update = record->update;
if (update == NULL) {
// We shouldn't ever get a callback with update==NULL (which means that the update completed successfully) that's not an
// error.
if (error_code == kDNSServiceErr_NoError) {
FAULT("update is NULL, registration for host record completed with invalid state.");
}
} else {
RETAIN_HERE(update, adv_update);
}
if (error_code == kDNSServiceErr_NoError) {
// If the update is pending, it means that we just finished registering this record for the first time,
// so we can count it as complete and check to see if there is any work left to do; if not, we call
// update_finished to apply the update to the host object.
const char *note = " has completed.";
if (record->update_pending) {
record->update_pending = false;
if (update != NULL) {
update->num_records_completed++;
if (update->num_records_completed == update->num_records_started &&
update->num_instances_completed == update->num_instances_started)
{
update_finished(update);
}
}
} else {
note = " got spurious success callback after completion.";
}
if (record->rrtype == dns_rrtype_a) {
IPv4_ADDR_GEN_SRP(record->rdata, addr_buf);
INFO("registration for host " PRI_S_SRP " address " PRI_IPv4_ADDR_SRP PUB_S_SRP,
host->registered_name, IPv4_ADDR_PARAM_SRP(record->rdata, addr_buf), note);
} else if (record->rrtype == dns_rrtype_aaaa) {
SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, addr_buf);
INFO("registration for host " PRI_S_SRP " address " PRI_SEGMENTED_IPv6_ADDR_SRP PUB_S_SRP,
host->registered_name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, addr_buf), note);
} else if (record->rrtype == dns_rrtype_key) {
INFO("registration for host " PRI_S_SRP " key" PUB_S_SRP, host->registered_name, note);
} else {
INFO("registration for host " PRI_S_SRP " unknown record type %d " PUB_S_SRP, host->registered_name, record->rrtype, note);
}
} else {
if (record->rrtype == dns_rrtype_a) {
IPv4_ADDR_GEN_SRP(record->rdata, addr_buf);
INFO("registration for host " PRI_S_SRP " address " PRI_IPv4_ADDR_SRP " failed, error code = %d.",
host->registered_name, IPv4_ADDR_PARAM_SRP(record->rdata, addr_buf), error_code);
} else if (record->rrtype == dns_rrtype_aaaa) {
SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, addr_buf);
INFO("registration for host " PRI_S_SRP " address " PRI_SEGMENTED_IPv6_ADDR_SRP " failed, error code = %d.",
host->registered_name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, addr_buf), error_code);
} else if (record->rrtype == dns_rrtype_key) {
INFO("registration for host " PRI_S_SRP " key failed, error code = %d.", host->registered_name, error_code);
} else {
INFO("registration for host " PRI_S_SRP " unknown record type %d failed, error code = %d.",
host->registered_name, record->rrtype, error_code);
}
// If the reason this failed is that we couldn't talk to mDNSResponder, or mDNSResponder disconnected, then we want to retry
// later on in the hopes that mDNSResponder will come back.
if (error_code == kDNSServiceErr_ServiceNotRunning || error_code == kDNSServiceErr_DefunctConnection) {
service_disconnected(host->server_state, record->shared_txn);
if (update != NULL) {
wait_retry(host);
}
} else {
// The other error we could get is a name conflict. This means that some other advertising proxy or host on
// the network is advertising the hostname we chose, and either got there first with no TSR record, or got
// its copy of the host information later than ours. So if we get a name conflict, it's up to the client or
// the replication peer to make the next move.
if (update != NULL) {
update_failed(update, (error_code == kDNSServiceErr_NameConflict
? dns_rcode_yxdomain
: dns_rcode_servfail), true, true);
}
}
// Regardless of what else happens, this transaction is dead, so get rid of our references to it.
remove_shared_record(host->server_state, record);
}
if (update != NULL) {
RELEASE_HERE(update, adv_update);
}
RELEASE_HERE(record, adv_record); // Release the reference to the record that we retained at the beginning
}
static adv_instance_t *
adv_instance_create(service_instance_t *raw, adv_host_t *host, adv_update_t *update)
{
char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2]; // sizeof '.' + sizeof '\0'.
char instance_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
char *txt_data;
// Allocate the raw registration
adv_instance_t *instance = calloc(1, sizeof *instance);
if (instance == NULL) {
ERROR("adv_instance:create: unable to allocate raw registration struct.");
return NULL;
}
RETAIN_HERE(instance, adv_instance);
instance->host = host;
RETAIN_HERE(instance->host, adv_host);
instance->update = update;
RETAIN_HERE(instance->update, adv_update);
// SRV records have priority, weight and port, but DNSServiceRegister only uses port.
instance->port = (raw->srv == NULL) ? 0 : raw->srv->data.srv.port;
// Make a presentation-format version of the service name.
if (!extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, raw)) {
RELEASE_HERE(instance, adv_instance);
return NULL;
}
instance->instance_name = strdup(instance_name);
if (instance->instance_name == NULL) {
ERROR("adv_instance:create: unable to allocate instance name.");
RELEASE_HERE(instance, adv_instance);
return NULL;
}
instance->service_type = strdup(service_type);
if (instance->service_type == NULL) {
ERROR("adv_instance:create: unable to allocate instance type.");
RELEASE_HERE(instance, adv_instance);
return NULL;
}
// Allocate the text record buffer
if (raw->txt != NULL) {
txt_data = malloc(raw->txt->data.txt.len);
if (txt_data == NULL) {
RELEASE_HERE(instance, adv_instance);
ERROR("adv_instance:create: unable to allocate txt_data buffer");
return NULL;
}
// Format the txt buffer as required by DNSServiceRegister().
memcpy(txt_data, raw->txt->data.txt.data, raw->txt->data.txt.len);
instance->txt_data = txt_data;
instance->txt_length = raw->txt->data.txt.len;
} else {
instance->txt_data = NULL;
instance->txt_length = 0;
}
return instance;
}
#define adv_record_create(rrtype, rdlen, rdata, host) \
adv_record_create_(rrtype, rdlen, rdata, host, __FILE__, __LINE__)
static adv_record_t *
adv_record_create_(uint16_t rrtype, uint16_t rdlen, uint8_t *rdata, adv_host_t *host, const char *file, int line)
{
adv_record_t *new_record = calloc(1, sizeof(*new_record) + rdlen - 1);
if (new_record == NULL) {
ERROR("no memory for new_record");
return NULL;
}
new_record->rdata = malloc(rdlen);
if (new_record->rdata == NULL) {
ERROR("no memory for new_record->rdata");
free(new_record);
return NULL;
}
new_record->host = host;
RETAIN(host, adv_host);
new_record->rrtype = rrtype;
new_record->rdlen = rdlen;
memcpy(new_record->rdata, rdata, rdlen);
RETAIN(new_record, adv_record);
return new_record;
}
// Given a pair of service types which may or may not have subtypes, e.g. _foo._tcp, which doesn't have subtypes, or
// _foo.tcp,bar, which does, return true if type1 matches the type2 for the base type, ignoring subtypes.
static bool
service_types_equal(const char *type1, const char *type2)
{
size_t len1;
char *comma1 = strchr(type1, ',');
if (comma1 == NULL) {
len1 = strlen(type1);
} else {
len1 = comma1 - type1;
}
char *comma2 = strchr(type2, ',');
size_t len2;
if (comma2 != NULL) {
len2 = comma2 - type2;
} else {
len2 = strlen(type2);
}
if (len1 != len2) {
return false;
}
if (memcmp(type2, type1, len1)) {
return false;
}
return true;
}
static bool
register_host_record(adv_host_t *host, adv_record_t *record)
{
int err;
// If this record is already registered, get rid of the old transaction.
if (record->rref != NULL) {
remove_shared_record(host->server_state, record);
}
// If we don't yet have a shared connection, create one.
if (!setup_shared_registration_txn(host->server_state)) {
return false;
}
const DNSServiceRef service_ref = host->server_state->shared_registration_txn->sdref;
if (record->rrtype == dns_rrtype_a) {
IPv4_ADDR_GEN_SRP(record->rdata, rdata_buf);
INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_IPv4_ADDR_SRP " %d %p %p)",
service_ref, &record->rref, kDNSServiceFlagsShared, host->server_state->advertise_interface,
host->registered_name, record->rrtype, dns_qclass_in, record->rdlen,
IPv4_ADDR_PARAM_SRP(record->rdata, rdata_buf),
ADDRESS_RECORD_TTL, register_host_record_completion, record);
} else if (record->rrtype == dns_rrtype_aaaa) {
SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf);
INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_SEGMENTED_IPv6_ADDR_SRP " %d %p %p)",
service_ref, &record->rref, kDNSServiceFlagsShared, host->server_state->advertise_interface,
host->registered_name, record->rrtype, dns_qclass_in, record->rdlen,
SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf),
ADDRESS_RECORD_TTL, register_host_record_completion, record);
} else {
INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d %p %d %p %p)",
service_ref, &record->rref,
kDNSServiceFlagsShared,
host->server_state->advertise_interface, host->registered_name,
record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL,
register_host_record_completion, record);
}
if (0) {
#if STUB_ROUTER
} else if (host->server_state->stub_router_enabled) {
DNSServiceAttributeRef attr = DNSServiceAttributeCreate();
if (attr == NULL) {
ERROR("Failed to create new DNSServiceAttributeRef");
return false;
} else {
uint32_t offset = 0;
char time_buf[28];
if (host->update != NULL && host->update->client != NULL && host->update->client->message != NULL &&
host->update->client->message->received_time != 0)
{
offset = (uint32_t)(srp_time() - host->update->client->message->received_time);
srp_format_time_offset(time_buf, sizeof(time_buf), offset);
} else {
static char msg[] = "now";
memcpy(time_buf, msg, sizeof(msg));
}
DNSServiceAttributeSetTimestamp(attr, offset);
err = DNSServiceRegisterRecordWithAttribute(service_ref, &record->rref,
kDNSServiceFlagsUnique,
host->server_state->advertise_interface, host->registered_name,
record->rrtype, dns_qclass_in, record->rdlen, record->rdata,
ADDRESS_RECORD_TTL, attr, register_host_record_completion,
record);
DNSServiceAttributeDeallocate(attr);
if (err == kDNSServiceErr_NoError) {
INFO("DNSServiceRegisterRecord rref = %p, TSR for " PRI_S_SRP " set to " PUB_S_SRP, record->rref, host->name, time_buf);
}
}
#endif
} else {
err = DNSServiceRegisterRecord(service_ref, &record->rref, kDNSServiceFlagsUnique,
host->server_state->advertise_interface, host->registered_name,
record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL,
register_host_record_completion, record);
}
if (err != kDNSServiceErr_NoError) {
if (err == kDNSServiceErr_ServiceNotRunning || err == kDNSServiceErr_DefunctConnection ||
err == kDNSServiceErr_BadParam || err == kDNSServiceErr_BadReference || err == 1)
{
if (err == 1) { // This is for an old bug that probably doesn't happen anymore.
FAULT("bogus error code 1");
}
INFO("DNSServiceRegisterRecord failed on host " PUB_S_SRP ": " PUB_S_SRP, host->name,
err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct");
service_disconnected(host->server_state, (intptr_t)host->server_state->shared_registration_txn);
} else {
INFO("DNSServiceRegisterRecord failed: %d", err);
}
return false;
}
record->shared_txn = (intptr_t)host->server_state->shared_registration_txn;
RETAIN_HERE(record, adv_record); // for the callback
record->update_pending = true;
return true;
}
static bool
update_instance_tsr(adv_instance_t *instance, bool rdata_changed)
{
int err = kDNSServiceErr_NoError;
bool success = false;
if (instance->txn == NULL) {
ERROR("txn is NULL updating instance TSR.");
goto out;
}
if (instance->txn->sdref == NULL) {
ERROR("sdref is NULL when updating instance TSR.");
goto out;
}
// Currently if we want to update the rdata, we need to do that separately from the TSR.
if (rdata_changed) {
err = DNSServiceUpdateRecord(instance->txn->sdref,
NULL, 0, instance->txt_length, instance->txt_data, 0);
if (err != kDNSServiceErr_NoError) {
INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP " TXT record failed: %d", instance->instance_name, err);
goto out;
} else {
INFO("updated TXT record for " PRI_S_SRP ".", instance->instance_name);
success = true;
}
}
#if STUB_ROUTER
DNSServiceAttributeRef attr;
if (instance->host->server_state->stub_router_enabled) {
success = false;
attr = DNSServiceAttributeCreate();
if (attr == NULL) {
ERROR("failed to create new DNSServiceAttributeRef");
} else {
uint32_t offset = 0;
char time_buf[28];
if (instance->update != NULL && instance->update->client != NULL && instance->update->client->message != NULL &&
instance->update->client->message->received_time != 0)
{
offset = (uint32_t)(srp_time() - instance->update->client->message->received_time);
srp_format_time_offset(time_buf, sizeof(time_buf), offset);
} else {
static char msg[] = "now";
memcpy(time_buf, msg, sizeof(msg));
}
DNSServiceAttributeSetTimestamp(attr, offset);
err = DNSServiceUpdateRecordWithAttribute(instance->txn->sdref, NULL, 0, 0, NULL, 0, attr);
DNSServiceAttributeDeallocate(attr);
if (err == kDNSServiceErr_NoError) {
INFO("DNSServiceRegisterUpdateRecord TSR for " PRI_S_SRP " set to " PUB_S_SRP,
instance->host == NULL ? "<null>" : instance->host->name, time_buf);
success = true;
} else {
INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP " TSR failed: %d", instance->instance_name, err);
}
}
}
#endif // STUB_ROUTER
out:
if (success == false) {
if (instance->txn != NULL) {
// We should never get a bad reference error; if we do, it's likely the result of a previous
// mDNSResponder crash. In this case, the sdref is no longer valid (that's what the error is
// saying). So we should NULL it out.
if (err == kDNSServiceErr_BadReference || err == kDNSServiceErr_BadParam) {
instance->txn->sdref = NULL;
}
// For all errors, we should cancel and release the transaction.
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
instance->txn = NULL;
}
}
return success;
}
static void
update_host_tsr(adv_record_t *record, adv_update_t *update)
{
#if STUB_ROUTER
DNSServiceAttributeRef attr;
int err;
dnssd_txn_t *shared_txn;
if (record->host == NULL || record->rref == NULL) {
ERROR("record->host[%p], record->rref[%p] when we update host TSR.", record->host, record->rref);
return;
}
shared_txn = record->host->server_state->shared_registration_txn;
if (shared_txn == NULL) {
ERROR("shared_txn is NULL when we update host TSR.");
return;
}
if (shared_txn->sdref == NULL) {
ERROR("shared_txn->sdref is NULL when we update host TSR.");
return;
}
if (record->host->server_state->stub_router_enabled) {
attr = DNSServiceAttributeCreate();
if (attr == NULL) {
ERROR("failed to create new DNSServiceAttributeRef");
} else {
uint32_t offset = 0;
char time_buf[28];
if (update->client != NULL && update->client->message != NULL && update->client->message->received_time != 0) {
offset = (uint32_t)(srp_time() - update->client->message->received_time);
srp_format_time_offset(time_buf, sizeof(time_buf), offset);
} else {
static char msg[] = "now";
memcpy(time_buf, msg, sizeof(msg));
}
DNSServiceAttributeSetTimestamp(attr, offset);
err = DNSServiceUpdateRecordWithAttribute(shared_txn->sdref, record->rref, 0, 0, NULL, 0, attr);
DNSServiceAttributeDeallocate(attr);
if (err == kDNSServiceErr_NoError) {
INFO("DNSServiceUpdateRecord TSR for " PRI_S_SRP " set to " PUB_S_SRP,
record->host == NULL ? "<null>" : record->host->name, time_buf);
} else {
INFO("DNSServiceUpdateRecordWithAttribute for host tsr failed: %d", err);
}
}
}
#else
(void)record; (void)update;
#endif
}
// When we need to register a host with mDNSResponder, start_host_update is called. This can be either because
// we just got a new registration for a host, or if the daemon dies and we need to re-do the host registration.
// This just registers the host; if that succeeds, then we register the service instances.
static void
start_host_update(adv_host_t *host)
{
adv_update_t *update = host->update;
#ifdef USE_DNSSERVICE_QUEUING
int err;
#endif
int i;
// No work to do?
if (update == NULL) {
ERROR("start_host_update: no work to do for host " PRI_S_SRP, host->registered_name);
return;
}
update->num_records_started = 0;
// Add all of the addresses that have been registered.
if (update->add_addresses != NULL) {
for (i = 0; i < update->add_addresses->num; i++) {
if (update->add_addresses->vec[i] != NULL) {
if (!register_host_record(host, update->add_addresses->vec[i])) {
update_failed(update, dns_rcode_servfail, true, true);
return;
} else {
update->num_records_started++;
}
}
}
}
// It's possible that some existing addresses are no longer registered because of a service disconnect. Check all the
// existing addresses for this situation: if an existing address has no rref, and does not appear in update->remove_addrs,
// then re-register it.
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
adv_record_t *record = host->addresses->vec[i];
if (update->remove_addresses->vec[i] == NULL && record != NULL && record->rref == NULL) {
host->addresses->vec[i]->update = update;
RETAIN_HERE(host->addresses->vec[i]->update, adv_update);
if (!register_host_record(host, record)) {
update_failed(update, dns_rcode_servfail, true, true);
return;
} else {
update->num_records_started++;
}
}
}
}
if (update->key != NULL) {
if (!register_host_record(host, update->key)) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
update->num_records_started++;
}
// If the shared transaction has changed since the key record was added, add it again.
if (update->key == NULL && host->key_record != NULL &&
(host->key_record->shared_txn != (intptr_t)host->server_state->shared_registration_txn ||
host->key_record->rref == NULL))
{
update->key = host->key_record;
RETAIN_HERE(update->key, adv_record);
RELEASE_HERE(host->key_record, adv_record);
host->key_record = NULL;
update->key->rref = NULL;
update->key->update = update;
RETAIN_HERE(update, adv_update);
if (!register_host_record(host, update->key)) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
update->num_records_started++;
}
if (update->num_records_started == 0) {
adv_record_t *record = update->key != NULL ? update->key : (host->key_record != NULL ? host->key_record : NULL);
if (record == NULL) {
} else {
if (record->rref == NULL) {
if (!register_host_record(host, record)) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
update->num_records_started++;
} else {
update_host_tsr(record, update);
}
}
}
if (host->instances != NULL) {
// For each service instance that's being added, register it.
if (update->add_instances != NULL) {
for (i = 0; i < update->add_instances->num; i++) {
if (update->add_instances->vec[i] != NULL) {
if (!register_instance(update->add_instances->vec[i])) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
}
}
}
// For each service instance that's being renewed, update its TSR if the original registration still exist,
// Otherwise re-register the instance.
if (update->renew_instances != NULL) {
for (i = 0; i < update->renew_instances->num; i++) {
if (update->renew_instances->vec[i] != NULL) {
adv_instance_t *instance = update->renew_instances->vec[i];
bool must_update = true;
if (instance->txn != NULL) {
bool must_remove = false;
// Make sure the instance is still registered and is registered on the current shared connection.
if (instance->txn->sdref != NULL) {
if (((intptr_t)host->server_state->shared_registration_txn == instance->shared_txn)) {
#if STUB_ROUTER
if (!host->server_state->stub_router_enabled || update_instance_tsr(instance, false)) {
#endif
must_remove = false;
must_update = false;
#if STUB_ROUTER
} else {
INFO("instance " PRI_S_SRP " (%p) tsr update failed, re-registering",
instance->instance_name, instance);
must_remove = true;
}
#endif
} else {
// If the shared transaction has changed, then the registration no longer exists, and
// the sdref is no longer valid.
INFO("instance " PRI_S_SRP " (%p) shared connection (%" PRIxPTR ") is stale, re-registering",
instance->instance_name, instance, instance->shared_txn);
instance->txn->sdref = NULL;
must_remove = true;
}
}
if (must_remove) {
// If not, dispose of the transaction and re-register.
if (instance->txn != NULL) {
ioloop_dnssd_txn_cancel(instance->txn);
ioloop_dnssd_txn_release(instance->txn);
instance->txn = NULL;
}
}
}
if (must_update) {
INFO(PRI_S_SRP " (%p): failed to update TSR, re-registering", instance->instance_name, instance);
if (!register_instance(update->renew_instances->vec[i])) {
update_failed(update, dns_rcode_servfail, true, true);
return;
}
}
}
}
}
// Sanity check that the instance vector sizes match between host and update.
if (update->update_instances != NULL && update->update_instances->num != host->instances->num) {
FAULT("update instance count %d differs from host instance count %d",
update->update_instances->num, host->instances->num);
update_failed(update, dns_rcode_servfail, true, true);
return;
}
if (update->remove_instances != NULL && update->remove_instances->num != host->instances->num) {
FAULT("delete instance count %d differs from host instance count %d",
update->remove_instances->num, host->instances->num);
update_failed(update, dns_rcode_servfail, true, true);
return;
}
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *update_instance = update->update_instances->vec[i];
if (update_instance != NULL && !update_instance->removed) {
adv_instance_t *host_instance = host->instances->vec[i];
bool must_register = true;
// Check to see if just the TXT record changes; in this case use DNSServiceUpdateRecord rather than re-registering
// the instance. If we can't update, we have to remove and then add. We could do this as a pair of atomic transactions
// if we used DNSServiceRegisterRecord rather than DNSServiceRegister, but currently we don't do that.
// Of course if the previous registration is no longer valid, re-register.
if (host_instance->txn != NULL && host_instance->txn->sdref != NULL && host->server_state != NULL &&
((intptr_t)host->server_state->shared_registration_txn == update_instance->shared_txn))
{
if (update_instance->port == host_instance->port &&
update_instance->txt_length != 0 &&
memcmp(update_instance->txt_data, host_instance->txt_data, update_instance->txt_length))
{
update_instance_tsr(update_instance, true);
must_register = false;
}
}
if (must_register) {
if (host_instance->txn != NULL) {
ioloop_dnssd_txn_cancel(host->instances->vec[i]->txn);
ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
host->instances->vec[i]->txn = NULL;
}
if (!register_instance(update->update_instances->vec[i])) {
INFO("register instance failed.");
return;
}
}
}
}
}
if (update->num_instances_started == 0 && update->num_records_started == 0) {
INFO("no service or record updates, so we're finished.");
update_finished(update);
return;
}
}
// When a host has no update in progress, and there is a client update ready to process, we need to analyze
// the client update to see what work needs to be done. This work is constructed as an translation from the
// raw update sent by the client (host->clients) into a prepared update that can be used directly to
// register the information with mDNSResponder.
//
// Normally a host will only have one prepared update in progress; however, if we lose our connection to
// mDNSResponder, then we need to re-create the host advertisement. If there was an update in progress when
// this happened, we then need to reapply that as well. In this case an update is constructed from the host, to
// get the host into the intended state, and the in-progress update is pushed below that; when the host has
// been re-created on the daemon, the pending update is popped back off the stack and restarted.
static void
prepare_update(adv_host_t *host, client_update_t *client_update)
{
host_addr_t *addr;
int i, j;
service_instance_t *instance;
adv_record_vec_t *remove_addrs = NULL;
int num_remove_addrs = 0;
adv_record_vec_t *add_addrs = NULL;
int num_add_addrs = 0;
int num_update_instances = 0;
int num_add_instances = 0;
int num_remove_instances = 0;
int num_renew_instances = 0;
adv_instance_vec_t *update_instances = NULL, *add_instances = NULL;
adv_instance_vec_t *remove_instances = NULL, *renew_instances = NULL;
adv_update_t *update = NULL;
// Work to do:
// - Figure out what address records to add and what address records to delete.
// - Because we can only have one address record at a time currently, figure out which address record we want
// - If we already have an address record published, and it's the same, do nothing
// - else if we already have an address record published, and it's changed to a different address, do an update
// - else if we have a new address record, publish it
// - else publish the key to hold the name
// - Go through the set of service instances, identifying deletes, changes and adds
// - We don't currently allow deletes, but what that would look like would be an instance with no SRV or TXT
// record.
// - What about a delete that keeps the name but un-advertises the service? How would we indicate that?
// Maybe if there's no service PTR for the service?
// - Changes means that the contents of the text record changed, or the contents of the SRV record
// changed (but not the hostname) or both.
// - New means that we don't have a service with that service instance name on the host (and we previously
// eliminated the possibility that it exists on some other host).
// Allocate the update structure.
update = calloc(1, sizeof *update);
if (update == NULL) {
ERROR("no memory for update.");
goto fail;
}
RETAIN_HERE(update, adv_update); // For the lifetime of this function
update->start_time = srp_time();
// The maximum number of addresses we could be deleting is all the ones the host currently has.
if (host->addresses == NULL || host->addresses->num == 0) {
num_remove_addrs = 0;
remove_addrs = NULL;
} else {
num_remove_addrs = host->addresses->num;
if (num_remove_addrs != 0) {
remove_addrs = adv_record_vec_create(num_remove_addrs);
// If we can't allocate space, just wait a bit.
if (remove_addrs == NULL) {
ERROR("no memory for remove_addrs");
goto fail;
}
remove_addrs->num = num_remove_addrs;
}
}
num_add_addrs = 0;
for (addr = client_update->host->addrs; addr != NULL; addr = addr->next) {
num_add_addrs++;
}
add_addrs = adv_record_vec_create(num_add_addrs);
if (add_addrs == NULL) {
ERROR("no memory for add_addrs");
goto fail;
}
// Copy all of the addresses in the update into add_addresses
num_add_addrs = 0;
for (addr = client_update->host->addrs; addr; addr = addr->next) {
bool add = true;
for (i = 0; i < num_add_addrs; i++) {
// If the client sends duplicate addresses, only add one of them.
if (add_addrs->vec[i] != NULL &&
add_addrs->vec[i]->rrtype == addr->rr.type &&
add_addrs->vec[i]->rdlen == (addr->rr.type == dns_rrtype_a ? 4 : 16) &&
!memcmp(add_addrs->vec[i]->rdata, (uint8_t *)&addr->rr.data, add_addrs->vec[i]->rdlen))
{
add = false;
}
}
if (add) {
adv_record_t *prepared_address = adv_record_create(addr->rr.type, addr->rr.type == dns_rrtype_a ? 4 : 16,
(uint8_t *)&addr->rr.data, host);
if (prepared_address == NULL) {
ERROR("No memory for prepared address");
goto fail;
}
add_addrs->vec[num_add_addrs++] = prepared_address;
}
}
add_addrs->num = num_add_addrs;
for (i = 0; i < add_addrs->num; i++) {
if (add_addrs->vec[i] != NULL) {
add_addrs->vec[i]->update = update;
RETAIN_HERE(add_addrs->vec[i]->update, adv_update);
}
}
#ifdef DEBUG_HOST_RECORDS_VERBOSE
for (i = 0; i < 2; i++) {
for (j = 0; j < (i ? num_add_addrs : num_remove_addrs); j++) {
adv_record_t *address = i ? add_addrs->vec[j] : (host->addresses != NULL ? host->addresses->vec[j] : NULL);
if (address == NULL) {
INFO(PUB_S_SRP " before: %d NULL", i ? "add" : "rmv", j);
} else {
char foobuf[385], *foop = foobuf;
for (unsigned long k = 0; k < address->rdlen && k * 3 + 1 < sizeof(foobuf); k++) {
snprintf(foop, 4, k ? ":%02x" : "%02x", address->rdata[k]);
foop += k ? 3 : 2;
}
*foop = 0;
INFO(PUB_S_SRP " before: %d rrtype %d rdlen %d rdata " PRI_S_SRP, i ? "add" : "rmv", j,
address->rrtype, address->rdlen, foobuf);
}
}
}
#endif // DEBUG_HOST_RECORDS_VERBOSE
// For every host address, see if it's in add_addresses. If it's not, it needs to be removed.
// If it is, it doesn't need to be added.
if (num_remove_addrs != 0) {
for (i = 0; i < num_remove_addrs; i++) {
if (host->addresses != NULL && host->addresses->vec[i] != NULL) {
remove_addrs->vec[i] = host->addresses->vec[i];
RETAIN_HERE(remove_addrs->vec[i], adv_record);
}
for (j = 0; j < num_add_addrs; j++) {
// If the address is present in both places, and has a valid registration, remove it from the list of
// addresses to add, and also remove it from the list of addresses to remove. When we're done, all that
// will be remaining in the list to remove will be addresses that weren't present in the add list.
if (remove_addrs->vec[i] != NULL && add_addrs->vec[j] != NULL &&
remove_addrs->vec[i]->rref != NULL && host->server_state != NULL &&
(intptr_t)host->server_state->shared_registration_txn == remove_addrs->vec[i]->shared_txn &&
add_addrs->vec[j]->rrtype == remove_addrs->vec[i]->rrtype &&
add_addrs->vec[j]->rdlen == remove_addrs->vec[i]->rdlen &&
!memcmp(add_addrs->vec[j]->rdata, remove_addrs->vec[i]->rdata, remove_addrs->vec[i]->rdlen))
{
RELEASE_HERE(remove_addrs->vec[i], adv_record);
remove_addrs->vec[i] = NULL;
RELEASE_HERE(add_addrs->vec[j], adv_record);
add_addrs->vec[j] = NULL;
}
}
}
remove_addrs->num = num_remove_addrs;
}
#ifdef DEBUG_HOST_RECORDS_VERBOSE
for (i = 0; i < 2; i++) {
for (j = 0; j < (i ? num_add_addrs : num_remove_addrs); j++) {
adv_record_t *address = i ? add_addrs->vec[j] : (remove_addrs != NULL ? remove_addrs->vec[j] : NULL);
if (address == NULL) {
INFO(PUB_S_SRP " after: %d NULL", i ? "add" : "rmv", j);
} else {
char foobuf[385], *foop = foobuf;
for (unsigned long k = 0; k < address->rdlen && k * 3 + 1 < sizeof(foobuf); k++) {
snprintf(foop, 4, k ? ":%02x" : "%02x", address->rdata[k]);
foop += k ? 3 : 2;
}
*foop = 0;
INFO(PUB_S_SRP " after: %d rrtype %d rdlen %d rdata " PRI_S_SRP, i ? "add" : "rmv", j,
address->rrtype, address->rdlen, foobuf);
}
}
}
#endif // DEBUG_HOST_RECORDS_VERBOSE
// Make a key record
if (host->key_record == NULL) {
update->key = adv_record_create(dns_rrtype_key, host->key_rdlen, host->key_rdata, host);
if (update->key == NULL) {
ERROR("no memory for key record");
goto fail;
}
update->key->update = update;
RETAIN_HERE(update->key->update, adv_update);
}
// We can never update more instances than currently exist for this host.
num_update_instances = host->instances->num;
num_remove_instances = host->instances->num;
num_renew_instances = host->instances->num;
update_instances = adv_instance_vec_create(num_update_instances);
if (update_instances == NULL) {
ERROR("no memory for update_instances");
goto fail;
}
update_instances->num = num_update_instances;
remove_instances = adv_instance_vec_create(num_remove_instances);
if (remove_instances == NULL) {
ERROR("no memory for remove_instances");
goto fail;
}
remove_instances->num = num_remove_instances;
renew_instances = adv_instance_vec_create(num_renew_instances);
if (renew_instances == NULL) {
ERROR("no memory for renew_instances");
goto fail;
}
renew_instances->num = num_renew_instances;
// Handle removes. Service instance removes have to remove the whole service instance, not some subset.
for (delete_t *dp = client_update->removes; dp; dp = dp->next) {
// Removes can be for services or service instances. Because we're acting as an
// Advertising Proxy and not a regular name server, we don't track service instances,
// and so we don't need to match them here. This if statement checks to see if the
// name could possibly be a service instance name followed by a service type. We
// can then extract the putative service instance name and service type and compare;
// if they match, they are in fact those things, and if they don't, we don't care.
if (dp->name != NULL && dp->name->next != NULL && dp->name->next->next != NULL) {
char instance_name[DNS_MAX_LABEL_SIZE_ESCAPED + 1];
char service_type[DNS_MAX_LABEL_SIZE_ESCAPED + 2];
dns_name_print_to_limit(dp->name, dp->name->next, instance_name, sizeof(instance_name));
dns_name_print_to_limit(dp->name->next, dp->name->next->next->next, service_type, sizeof(service_type));
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *remove_instance = host->instances->vec[i];
if (remove_instance != NULL) {
if (!strcmp(instance_name, remove_instance->instance_name) &&
service_types_equal(service_type, remove_instance->service_type))
{
remove_instances->vec[i] = remove_instance;
RETAIN_HERE(remove_instances->vec[i], adv_instance);
break;
}
}
}
}
}
// The number of instances to add can be as many as there are instances in the update.
num_add_instances = 0;
for (instance = client_update->instances; instance; instance = instance->next) {
num_add_instances++;
}
add_instances = adv_instance_vec_create(num_add_instances);
if (add_instances == NULL) {
ERROR("prepare_update: no memory for add_instances");
goto fail;
}
// Convert all of the instances in the client update to adv_instance_t structures for easy comparison.
// Any that are unchanged will have to be freed--oh well.
i = 0;
for (instance = client_update->instances; instance != NULL; instance = instance->next) {
adv_instance_t *prepared_instance = adv_instance_create(instance, host, update);
if (prepared_instance == NULL) {
// prepare_instance logs.
goto fail;
}
if (i >= num_add_instances) {
FAULT("while preparing client update instances, i >= num_add_instances");
RELEASE_HERE(prepared_instance, adv_instance);
prepared_instance = NULL;
goto fail;
}
prepared_instance->anycast = false;
if (client_update != NULL && client_update->connection != NULL) {
const struct sockaddr *server_addr = connection_get_local_address(client_update->connection);
if (server_addr && server_addr->sa_family == AF_INET6) {
const struct in6_addr *const ipv6_address = &(((const struct sockaddr_in6 *)server_addr)->sin6_addr);
uint16_t server_port = ntohs(((const struct sockaddr_in6 *)server_addr)->sin6_port);
SEGMENTED_IPv6_ADDR_GEN_SRP(ipv6_address, addr_buf);
INFO("server address " PRI_SEGMENTED_IPv6_ADDR_SRP "; server port %d",
SEGMENTED_IPv6_ADDR_PARAM_SRP(ipv6_address, addr_buf), server_port);
if (is_thread_mesh_anycast_address(ipv6_address) && server_port == 53) {
prepared_instance->anycast = true;
}
}
}
add_instances->vec[i++] = prepared_instance;
}
add_instances->num = i;
// The instances in the update are now in add_instances. If they are updates, move them to update_instances. If
// they are unchanged, free them and null them out, and remember the current instance in renew_instances. If they
// are adds, leave them.
for (i = 0; i < num_add_instances; i++) {
adv_instance_t *add_instance = add_instances->vec[i];
if (add_instance != NULL) {
for (j = 0; j < host->instances->num; j++) {
adv_instance_t *host_instance = host->instances->vec[j];
// See if the instance names match.
if (host_instance != NULL &&
!strcmp(add_instance->instance_name, host_instance->instance_name) &&
service_types_equal(add_instance->service_type, host_instance->service_type))
{
// If the rdata is the same, and the service type is the same (including subtypes), and it's not
// deleted, it's not an add or an update.
if (!host_instance->removed && add_instance->txt_length == host_instance->txt_length &&
add_instance->port == host_instance->port &&
!strcmp(add_instance->service_type, host_instance->service_type) &&
(add_instance->txt_length == 0 ||
!memcmp(add_instance->txt_data, host_instance->txt_data, add_instance->txt_length)))
{
RELEASE_HERE(add_instances->vec[i], adv_instance);
add_instances->vec[i] = NULL;
renew_instances->vec[j] = host_instance;
RETAIN_HERE(host_instance, adv_instance);
renew_instances->vec[j]->update = update;
RETAIN_HERE(renew_instances->vec[j]->update, adv_update);
INFO(PRI_S_SRP "." PRI_S_SRP " renewed for host " PRI_S_SRP,
host_instance->instance_name, host_instance->service_type, host->name);
} else {
update_instances->vec[j] = add_instance;
RETAIN_HERE(update_instances->vec[j], adv_instance);
RELEASE_HERE(add_instances->vec[i], adv_instance);
add_instances->vec[i] = NULL;
}
break;
}
}
}
}
// At this point we have figured out all the work we need to do, so hang it off an update structure.
update->host = host;
RETAIN_HERE(update->host, adv_host);
update->client = client_update;
update->remove_addresses = remove_addrs;
update->add_addresses = add_addrs;
update->remove_instances = remove_instances;
update->add_instances = add_instances;
update->update_instances = update_instances;
update->renew_instances = renew_instances;
update->host_lease = client_update->host_lease;
update->key_lease = client_update->key_lease;
host->update = update;
RETAIN_HERE(host->update, adv_update);
RELEASE_HERE(update, adv_update);
update = NULL;
start_host_update(host);
return;
fail:
if (remove_addrs != NULL) {
// Addresses in remove_addrs are owned by the host and don't need to be freed.
RELEASE_HERE(remove_addrs, adv_record_vec);
remove_addrs = NULL;
}
if (add_addrs != NULL) {
RELEASE_HERE(add_addrs, adv_record_vec);
add_addrs = NULL;
}
if (add_instances != NULL) {
RELEASE_HERE(add_instances, adv_instance_vec);
add_instances = NULL;
}
if (remove_instances != NULL) {
RELEASE_HERE(remove_instances, adv_instance_vec);
remove_instances = NULL;
}
if (update_instances != NULL) {
RELEASE_HERE(update_instances, adv_instance_vec);
update_instances = NULL;
}
if (update) {
RELEASE_HERE(update, adv_update);
}
}
typedef enum { missed, match, conflict } instance_outcome_t;
static instance_outcome_t
compare_instance(adv_instance_t *instance,
dns_host_description_t *new_host, adv_host_t *host,
char *instance_name, char *service_type)
{
if (instance == NULL) {
return missed;
}
if (host->removed) {
return missed;
}
if (!strcmp(instance_name, instance->instance_name) && service_types_equal(service_type, instance->service_type)) {
if (!dns_names_equal_text(new_host->name, host->name)) {
return conflict;
}
return match;
}
return missed;
}
bool
srp_update_start(comm_t *connection, srp_server_t *server_state, srpl_connection_t *srpl_connection, dns_message_t *parsed_message, message_t *raw_message,
dns_host_description_t *new_host, service_instance_t *instances, service_t *services,
delete_t *removes, dns_name_t *update_zone, uint32_t lease_time, uint32_t key_lease_time,
uint32_t serial_number, bool found_serial)
{
adv_host_t *host, **p_hosts = NULL;
char pres_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
service_instance_t *new_instance;
instance_outcome_t outcome = missed;
client_update_t *client_update;
char instance_name[DNS_MAX_LABEL_SIZE_ESCAPED + 1];
char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2];
uint32_t key_id = 0;
char new_host_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
host_addr_t *addr;
const bool remove = lease_time == 0;
const char *updatestr = lease_time == 0 ? "remove" : "update";
delete_t *dp;
dns_name_print(new_host->name, new_host_name, sizeof new_host_name);
// Compute a checksum on the key, ignoring up to three bytes at the end.
for (unsigned i = 0; i < new_host->key->data.key.len; i += 4) {
key_id += ((new_host->key->data.key.key[i] << 24) | (new_host->key->data.key.key[i + 1] << 16) |
(new_host->key->data.key.key[i + 2] << 8) | (new_host->key->data.key.key[i + 3]));
}
// Log the update info.
if (found_serial) {
INFO("host update for " PRI_S_SRP ", key id %" PRIx32 ", serial number %" PRIu32 " " PUB_S_SRP,
new_host_name, key_id, serial_number, srpl_connection == NULL ? "" : srpl_connection->name);
} else {
INFO("host update for " PRI_S_SRP ", key id %" PRIx32 " " PUB_S_SRP, new_host_name, key_id,
srpl_connection == NULL ? "" : srpl_connection->name);
}
for (addr = new_host->addrs; addr != NULL; addr = addr->next) {
if (addr->rr.type == dns_rrtype_a) {
IPv4_ADDR_GEN_SRP(&addr->rr.data.a.s_addr, addr_buf);
INFO("host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_IPv4_ADDR_SRP " " PUB_S_SRP, updatestr,
new_host_name, IPv4_ADDR_PARAM_SRP(&addr->rr.data.a.s_addr, addr_buf), srpl_connection == NULL ? "" : srpl_connection->name);
} else {
SEGMENTED_IPv6_ADDR_GEN_SRP(addr->rr.data.aaaa.s6_addr, addr_buf);
INFO("host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_SEGMENTED_IPv6_ADDR_SRP " " PUB_S_SRP,
updatestr, new_host_name, SEGMENTED_IPv6_ADDR_PARAM_SRP(addr->rr.data.aaaa.s6_addr, addr_buf),
srpl_connection == NULL ? "" : srpl_connection->name);
}
}
for (new_instance = instances; new_instance != NULL; new_instance = new_instance->next) {
extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, new_instance);
INFO("host " PUB_S_SRP " for " PRI_S_SRP ", instance name " PRI_S_SRP ", type " PRI_S_SRP
", port %d " PUB_S_SRP, updatestr, new_host_name, instance_name, service_type,
new_instance->srv != NULL ? new_instance->srv->data.srv.port : -1,
srpl_connection == NULL ? "" : srpl_connection->name);
if (new_instance->txt != NULL) {
char txt_buf[DNS_DATA_SIZE];
dns_txt_data_print(txt_buf, DNS_DATA_SIZE, new_instance->txt->data.txt.len, new_instance->txt->data.txt.data);
INFO("text data for instance " PRI_S_SRP ": " PRI_S_SRP, instance_name, txt_buf);
}
}
// Look for matching service instance names. A service instance name that matches, but has a different
// hostname, means that there is a conflict. We have to look through all the entries; the presence of
// a matching hostname doesn't mean we are done UNLESS there's a matching service instance name pointing
// to that hostname.
for (host = server_state->hosts; host; host = host->next) {
// If a host has been removed, it won't have any instances to compare against. Later on, if we find that
// there is no matching host for this update, we look through the host list again and remove the
// "removed" host if it has the same name, so we don't need to do anything further here.
if (host->removed) {
continue;
}
// We need to look for matches both in the registered instances for this registration, and also in
// the list of new instances, in case we get a duplicate update while a previous update is in progress.
for (new_instance = instances; new_instance; new_instance = new_instance->next) {
extract_instance_name(instance_name, sizeof instance_name, service_type, sizeof service_type, new_instance);
// First check for a match or conflict in the host itself.
for (int i = 0; i < host->instances->num; i++) {
outcome = compare_instance(host->instances->vec[i], new_host, host,
instance_name, service_type);
if (outcome != missed) {
goto found_something;
}
}
// Then look for the same thing in any subsequent updates that have been baked.
if (host->update != NULL) {
if (host->update->add_instances != NULL) {
for (int i = 0; i < host->update->add_instances->num; i++) {
outcome = compare_instance(host->update->add_instances->vec[i], new_host, host,
instance_name, service_type);
if (outcome != missed) {
goto found_something;
}
}
}
}
}
}
found_something:
if (outcome == conflict) {
ERROR("service instance name " PRI_S_SRP "/" PRI_S_SRP " already pointing to host "
PRI_S_SRP ", not host " PRI_S_SRP, instance_name, service_type, host->name, new_host_name);
advertise_finished(NULL, host->name,
server_state, srpl_connection, connection, raw_message, dns_rcode_yxdomain, NULL, true);
goto cleanup;
}
// We may have received removes for individual records. In this case, we need to make sure they only remove
// records that have been added to the host that matches.
for (adv_host_t *rhp = server_state->hosts; rhp != NULL; rhp = rhp->next) {
if (rhp->removed) {
continue;
}
// Look for removes that conflict
for (dp = removes; dp != NULL; dp = dp->next) {
// We only need to do this for service instance names. We don't really know what is and isn't a
// service instance name, but if it /could/ be a service instance name, we compare; if it matches,
// it is a service instance name, and if not, no problem.
if (dp->name != NULL && dp->name->next != NULL && dp->name->next->next != NULL) {
dns_name_print_to_limit(dp->name, dp->name->next, instance_name, sizeof(instance_name));
dns_name_print_to_limit(dp->name->next, dp->name->next->next->next, service_type, sizeof(service_type));
// See if the delete deletes an instance on the host
for (int i = 0; i < rhp->instances->num; i++) {
adv_instance_t *instance = rhp->instances->vec[i];
if (instance != NULL) {
if (!strcmp(instance_name, instance->instance_name) &&
service_types_equal(service_type, instance->service_type))
{
if (!strcmp(new_host_name, rhp->name)) {
ERROR("remove for " PRI_S_SRP "." PRI_S_SRP " matches instance on host " PRI_S_SRP,
instance_name, service_type, rhp->name);
dp->consumed = true;
} else {
ERROR("remove for " PRI_S_SRP "." PRI_S_SRP " conflicts with instance on host " PRI_S_SRP,
instance_name, service_type, rhp->name);
advertise_finished(NULL, rhp->name, server_state, srpl_connection,
connection, raw_message, dns_rcode_formerr, NULL, true);
goto cleanup;
}
}
}
}
// See if the remove removes an instance on an update on the host
if (rhp->update) {
if (rhp->update->add_instances != NULL) {
for (int i = 0; i < rhp->update->add_instances->num; i++) {
adv_instance_t *instance = rhp->update->add_instances->vec[i];
if (instance != NULL) {
if (!strcmp(instance_name, instance->instance_name) &&
service_types_equal(service_type, instance->service_type))
{
if (!strcmp(new_host_name, rhp->name)) {
dp->consumed = true;
} else {
ERROR("remove for " PRI_S_SRP " conflicts with instance on update to host " PRI_S_SRP,
instance->instance_name, rhp->name);
advertise_finished(NULL, rhp->name, server_state, srpl_connection,
connection, raw_message, dns_rcode_formerr, NULL, true);
goto cleanup;
}
}
}
}
}
}
}
}
}
// Log any unmatched deletes, but we don't consider these to be errors.
for (dp = removes; dp != NULL; dp = dp->next) {
if (!dp->consumed) {
DNS_NAME_GEN_SRP(dp->name, name_buf);
INFO("remove for " PRI_DNS_NAME_SRP " doesn't match any instance on any host.",
DNS_NAME_PARAM_SRP(dp->name, name_buf));
}
}
// If we fall off the end looking for a matching service instance, there isn't a matching
// service instance, but there may be a matching host, so look for that.
if (outcome == missed) {
// Search for the new hostname in the list of hosts, which is sorted.
for (p_hosts = &server_state->hosts; *p_hosts; p_hosts = &host->next) {
host = *p_hosts;
int comparison = strcasecmp(new_host_name, host->name);
if (comparison == 0) {
// If we get an update for a host that was removed, and it's not also a remove,
// remove the host entry that's marking the remove. If this is a remove, just flag
// it as a miss.
if (host->removed) {
outcome = missed;
if (remove) {
break;
}
*p_hosts = host->next;
host_invalidate(host);
RELEASE_HERE(host, adv_host);
host = NULL;
break;
}
if (key_id == host->key_id && dns_keys_rdata_equal(new_host->key, &host->key)) {
outcome = match;
break;
}
ERROR("update for host " PRI_S_SRP " has key id %" PRIx32
" which doesn't match host key id %" PRIx32 ".",
host->name, key_id, host->key_id);
advertise_finished(NULL, host->name, server_state, srpl_connection,
connection, raw_message, dns_rcode_yxdomain, NULL, true);
goto cleanup;
} else if (comparison < 0) {
break;
}
}
} else {
if (key_id != host->key_id || !dns_keys_rdata_equal(new_host->key, &host->key)) {
ERROR("new host with name " PRI_S_SRP " and key id %" PRIx32
" conflicts with existing host " PRI_S_SRP " with key id %" PRIx32,
new_host_name, key_id, host->name, host->key_id);
advertise_finished(NULL, host->name, server_state, srpl_connection,
connection, raw_message, dns_rcode_yxdomain, NULL, true);
goto cleanup;
}
}
// If we didn't find a matching host, we can make a new one. When we create it, it just has
// a name and no records. The update that we then construct will have the missing records.
// We don't want to do this for a remove, obviously.
if (outcome == missed) {
if (remove) {
ERROR("Remove for host " PRI_S_SRP " which doesn't exist.", new_host_name);
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_noerror, NULL, true);
goto cleanup;
}
host = calloc(1, sizeof *host);
if (host == NULL) {
ERROR("no memory for host data structure.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
goto cleanup;
}
RETAIN_HERE(host, adv_host);
host->server_state = server_state;
host->instances = adv_instance_vec_create(0);
if (host->instances == NULL) {
ERROR("no memory for host instance vector.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
RELEASE_HERE(host, adv_host);
host = NULL;
goto cleanup;
}
host->addresses = adv_record_vec_create(0);
if (host->addresses == NULL) {
ERROR("no memory for host address vector.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
RELEASE_HERE(host, adv_host);
host = NULL;
goto cleanup;
}
host->retry_wakeup = ioloop_wakeup_create();
if (host->retry_wakeup != NULL) {
host->lease_wakeup = ioloop_wakeup_create();
}
if (host->lease_wakeup == NULL) {
ERROR("no memory for wake event on host");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
RELEASE_HERE(host, adv_host);
host = NULL;
goto cleanup;
}
dns_name_print(new_host->name, pres_name, sizeof pres_name);
host->name = strdup(pres_name);
if (host->name == NULL) {
RELEASE_HERE(host, adv_host);
host = NULL;
ERROR("no memory for hostname.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
goto cleanup;
}
host->key = *new_host->key;
#ifndef __clang_analyzer__
// Normally this would be invalid, but we never use the name of the key record.
host->key.name = NULL;
#endif
host->key_rdlen = new_host->key->data.key.len + 4;
host->key_rdata = malloc(host->key_rdlen);
if (host->key_rdata == NULL) {
RELEASE_HERE(host, adv_host);
host = NULL;
ERROR("no memory for host key.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
goto cleanup;
}
memcpy(host->key_rdata, &new_host->key->data.key.flags, 2);
host->key_rdata[2] = new_host->key->data.key.protocol;
host->key_rdata[3] = new_host->key->data.key.algorithm;
memcpy(&host->key_rdata[4], new_host->key->data.key.key, new_host->key->data.key.len);
host->key.data.key.key = &host->key_rdata[4];
host->key_id = key_id;
// Insert this in the list where it would have sorted. The if test is because the optimizer doesn't notice that
// p_hosts can never be null here--it will always be pointing to the end of the list of hosts if we get here.
if (p_hosts != NULL) {
host->next = *p_hosts;
*p_hosts = host;
}
p_hosts = NULL;
}
// If we are already updating this host, either this is a retransmission, or it's a new transaction. In the case
// of a retransmission, we need to keep doing the work we've been asked to do, and hopefully we'll reply before the
// client gives up. In the case of a new request, we aren't ready for it yet; the client really shouldn't have sent
// it so quickly, but if it's behaving correctly, we should be done with the current update before it retransmits,
// so we can safely ignore it. If we're getting a replication update, it can't be newer than the current update.
// So we can ignore it--we'll send a replication update when we're done processing the client update.
if (host->update != NULL) {
#ifdef SRP_DETECT_STALLS
time_t now = srp_time();
// It's possible that we could get an update that stalls due to a problem communicating with mDNSResponder
// and that a timing race prevents this from being detected correctly. In this case, cancel the update and
// let the retry go through. We don't want to do this unless there's a clear stall, so we're allowing ten
// seconds.
if (now - host->update->start_time > 10) {
INFO("update has stalled, failing it silently.");
update_failed(host->update, dns_rcode_servfail, false, false);
service_disconnected(server_state, (intptr_t)server_state->shared_registration_txn);
} else {
#endif // SRP_DETECT_STALLS
INFO("dropping retransmission of in-progress update for host " PRI_S_SRP, host->name);
#if SRP_FEATURE_REPLICATION
srp_replication_advertise_finished(host, host->name, server_state, srpl_connection,
connection, dns_rcode_servfail);
#endif
cleanup:
srp_update_free_parts(instances, NULL, services, removes, new_host);
dns_message_free(parsed_message);
return true;
#ifdef SRP_DETECT_STALLS
}
#endif
}
// If this is a remove, remove the host registrations and mark the host removed. We keep it around until the
// lease expires to prevent replication accidentally re-adding a removed host as a result of a bad timing
// coincidence.
if (remove) {
host_invalidate(host);
// We need to propagate the remove message.
if (host->message != NULL) {
ioloop_message_release(host->message);
}
host->message = raw_message;
ioloop_message_retain(host->message);
advertise_finished(host, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_noerror, NULL, true);
goto cleanup;
}
// At this point we have an update and a host to which to apply it. We may already be doing an earlier
// update, or not. Create a client update structure to hold the communication, so that when we are done,
// we can respond.
client_update = calloc(1, sizeof *client_update);
if (client_update == NULL) {
ERROR("no memory for host data structure.");
advertise_finished(NULL, new_host_name, server_state, srpl_connection,
connection, raw_message, dns_rcode_servfail, NULL, true);
goto cleanup;
}
if (outcome == missed) {
INFO("New host " PRI_S_SRP ", key id %" PRIx32 , host->name, host->key_id);
} else {
if (host->registered_name != host->name) {
INFO("Renewing host " PRI_S_SRP ", alias " PRI_S_SRP ", key id %" PRIx32,
host->name, host->registered_name, host->key_id);
} else {
INFO("Renewing host " PRI_S_SRP ", key id %" PRIx32, host->name, host->key_id);
}
}
if (host->registered_name == NULL) {
host->registered_name = host->name;
}
if (connection != NULL) {
client_update->connection = connection;
ioloop_comm_retain(client_update->connection);
}
client_update->parsed_message = parsed_message;
client_update->message = raw_message;
ioloop_message_retain(client_update->message);
client_update->host = new_host;
client_update->instances = instances;
client_update->services = services;
client_update->removes = removes;
client_update->update_zone = update_zone;
// We have to take the lease from the SRP update--the original registrar negotiated it, and if it's out
// of our range, that's too bad (ish).
if (raw_message->lease != 0) {
client_update->host_lease = raw_message->lease;
client_update->key_lease = raw_message->key_lease;
} else {
if (lease_time < server_state->max_lease_time) {
if (lease_time < server_state->min_lease_time) {
client_update->host_lease = server_state->min_lease_time;
} else {
client_update->host_lease = lease_time;
}
} else {
client_update->host_lease = server_state->max_lease_time;
}
if (key_lease_time < server_state->key_max_lease_time) {
if (key_lease_time < server_state->key_min_lease_time) {
client_update->key_lease = server_state->key_min_lease_time;
} else {
client_update->key_lease = key_lease_time;
}
} else {
client_update->key_lease = server_state->key_max_lease_time;
}
}
client_update->serial_number = serial_number;
client_update->serial_sent = found_serial;
#if SRP_FEATURE_REPLICATION
if (srpl_connection != NULL) {
host->srpl_connection = srpl_connection;
srpl_connection_retain(host->srpl_connection);
}
#endif // SRP_FEATURE_REPLICATION
// Apply the update.
prepare_update(host, client_update);
return true;
}
void
srp_mdns_flush(srp_server_t *server_state)
{
adv_host_t *host, *host_next;
INFO("flushing all host entries.");
for (host = server_state->hosts; host; host = host_next) {
INFO("Flushing services and host entry for " PRI_S_SRP " (" PRI_S_SRP ")",
host->name, host->registered_name);
// Get rid of the updates before calling delete_host, which will fail if update is not NULL.
if (host->update != NULL) {
update_failed(host->update, dns_rcode_refused, false, true);
}
host_next = host->next;
host_remove(host);
}
server_state->hosts = NULL;
}
static void
usage(void)
{
ERROR("srp-mdns-proxy [--max-lease-time <seconds>] [--min-lease-time <seconds>] [--log-stderr]");
ERROR(" [--enable-replication | --disable-replication]");
#if SRP_FEATURE_NAT64
ERROR(" [--enable-nat64 | --disable-nat64]");
#endif
exit(1);
}
srp_server_t *
server_state_create(const char *name, int interface_index, int max_lease_time, int min_lease_time,
int key_max_lease_time, int key_min_lease_time)
{
srp_server_t *server_state = calloc(1, sizeof(*server_state));
if (server_state == NULL || (server_state->name = strdup(name)) == NULL) {
ERROR("no memory for server state");
free(server_state);
return NULL;
}
server_state->max_lease_time = max_lease_time;
server_state->min_lease_time = min_lease_time;
server_state->key_max_lease_time = key_max_lease_time;
server_state->key_min_lease_time = key_min_lease_time;
server_state->advertise_interface = interface_index;
return server_state;
}
static void
object_allocation_stats_dump_callback(void *context)
{
srp_server_t *server_state = context;
ioloop_dump_object_allocation_stats();
// Do the next object memory allocation statistics dump in five minutes
ioloop_add_wake_event(server_state->object_allocation_stats_dump_wakeup, server_state,
object_allocation_stats_dump_callback, NULL, 5 * 60 * 1000);
}
int
main(int argc, char **argv)
{
int i;
char *end;
int log_stderr = false;
srp_servers = server_state_create("srp-mdns-proxy", PLATFORM_ADVERTISE_INTERFACE,
3600 * 27, // max lease time one day plus 20%
30, // min lease time 30 seconds
3600 * 24 * 7, // max key lease 7 days
30); // min key lease time 30s
if (srp_servers == NULL) {
return 1;
}
srp_servers->srp_replication_enabled = true;
# if SRP_FEATURE_NAT64
srp_servers->srp_nat64_enabled = true;
# endif
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--max-lease-time")) {
if (i + 1 == argc) {
usage();
}
srp_servers->max_lease_time = (uint32_t)strtoul(argv[i + 1], &end, 10);
if (end == argv[i + 1] || end[0] != 0) {
usage();
}
i++;
} else if (!strcmp(argv[i], "--min-lease-time")) {
if (i + 1 == argc) {
usage();
}
srp_servers->min_lease_time = (uint32_t)strtoul(argv[i + 1], &end, 10);
if (end == argv[i + 1] || end[0] != 0) {
usage();
}
i++;
} else if (!strcmp(argv[i], "--log-stderr")) {
log_stderr = true;
} else if (!strcmp(argv[i], "--enable-replication")) {
srp_servers->srp_replication_enabled = true;
} else if (!strcmp(argv[i], "--disable-replication")) {
srp_servers->srp_replication_enabled = false;
} else if (!strcmp(argv[i], "--fake-xpanid")) {
if (i + 1 == argc) {
usage();
}
srp_servers->xpanid = strtoul(argv[i + 1], &end, 16);
if (end == argv[i + 1] || end[0] != 0) {
usage();
}
#if SRP_FEATURE_NAT64
} else if (!strcmp(argv[i], "--enable-nat64")) {
srp_servers->srp_nat64_enabled = true;
} else if (!strcmp(argv[i], "--disable-nat64")) {
srp_servers->srp_nat64_enabled = false;
#endif
} else {
usage();
}
}
// Setup log category for srp-mdns-prox and dnssd-proxy.
OPENLOG("srp-mdns-proxy", log_stderr);
INFO("--------------------------------"
"srp-mdns-proxy starting, compiled on " PUB_S_SRP ", " PUB_S_SRP
"--------------------------------", __DATE__, __TIME__);
if (!ioloop_init()) {
return 1;
}
#if STUB_ROUTER
if (stub_router_enabled) {
srp_servers->route_state = route_state_create(srp_servers, "srp-mdns-proxy");
if (srp_servers->route_state == NULL) {
return 1;
}
}
#endif // STUB_ROUTER
srp_servers->shared_registration_txn = dnssd_txn_create_shared();
if (srp_servers->shared_registration_txn == NULL) {
return 1;
}
dns_service_op_not_to_be_freed = srp_servers->shared_registration_txn->sdref;
#if STUB_ROUTER
if (stub_router_enabled) {
// Set up the ULA early just in case we get an early registration, nat64 will use the ula
route_ula_setup(srp_servers->route_state);
}
#endif
#if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
# if STUB_ROUTER || !THREAD_DEVICE
if (0) {
# if STUB_ROUTER
} else if (!stub_router_enabled) {
# endif
} else {
if (!init_dnssd_proxy(srp_servers)) {
ERROR("main: failed to setup dnssd-proxy");
return 1;
}
}
# endif // STUB_ROUTER
#endif // #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
#if STUB_ROUTER
if (stub_router_enabled) {
if (!start_icmp_listener()) {
return 1;
}
}
#endif
infrastructure_network_startup(srp_servers->route_state);
if (adv_ctl_init(srp_servers) != kDNSServiceErr_NoError) {
ERROR("Can't start advertising proxy control server.");
return 1;
}
// We require one open file per service and one per instance.
struct rlimit limits;
if (getrlimit(RLIMIT_NOFILE, &limits) < 0) {
ERROR("getrlimit failed: " PUB_S_SRP, strerror(errno));
return 1;
}
if (limits.rlim_cur < 1024) {
if (limits.rlim_max < 1024) {
INFO("file descriptor hard limit is %llu", (unsigned long long)limits.rlim_max);
if (limits.rlim_cur != limits.rlim_max) {
limits.rlim_cur = limits.rlim_max;
}
} else {
limits.rlim_cur = 1024;
}
if (setrlimit(RLIMIT_NOFILE, &limits) < 0) {
ERROR("setrlimit failed: " PUB_S_SRP, strerror(errno));
}
}
srp_proxy_init("local");
srp_servers->object_allocation_stats_dump_wakeup = ioloop_wakeup_create();
if (srp_servers->object_allocation_stats_dump_wakeup == NULL) {
INFO("no memory for srp_servers->object_allocation_stats_dump_wakeup");
} else {
// Do an object memory allocation statistics dump every five minutes
object_allocation_stats_dump_callback(srp_servers);
}
ioloop();
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 120
// indent-tabs-mode: nil
// End: