blob: d313b6127b4c969c0aaaa040839eee7a764db401 [file] [log] [blame] [edit]
/* test-dnssd.c
*
* Copyright (c) 2023-2024 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* DNSSD intercept API for testing srp-mdns-proxy
*/
#include <dns_sd.h>
#include "srp.h"
#include "srp-test-runner.h"
#include "srp-api.h"
#include "dns-msg.h"
#include "ioloop.h"
#include "srp-proxy.h"
#include "srp-dnssd.h"
#include "srp-mdns-proxy.h"
#include "test-api.h"
#include "test-dnssd.h"
static char *
dns_service_rdata_to_text(test_state_t *state, int rrtype, const uint8_t *rdata, uint16_t rdlen, char *outbuf, size_t buflen)
{
dns_rr_t rr;
unsigned offp = 0;
rr.type = rrtype;
TEST_FAIL_CHECK(state, dns_rdata_parse_data(&rr, rdata, &offp, rdlen, rdlen, 0), "rr parse failed");
dns_rdata_dump_to_buf(&rr, outbuf, buflen);
return outbuf;
}
static void
dns_service_dump_event(test_state_t *state, dns_service_event_t *event, dns_service_event_t *events)
{
char rrbuf[1024];
char *rr_printed;
int rrtype = 0;
uint16_t rdlen;
uint8_t *rdata;
#define STR_OR_NULL(str) ((str) != NULL ? str : "<null>")
switch(event->event_type) {
case dns_service_event_type_register:
rr_printed = dns_service_rdata_to_text(state, dns_rrtype_txt, event->rdata, event->rdlen, rrbuf, sizeof(rrbuf));
INFO(" register: %s.%s.%s port %d IN %s (%p %p) -> %d", STR_OR_NULL(event->name),
STR_OR_NULL(event->regtype), STR_OR_NULL(event->domain), event->port, STR_OR_NULL(rr_printed),
(char *)event->sdref, (char *)event->parent_sdref, event->status);
break;
case dns_service_event_type_register_record:
rr_printed = dns_service_rdata_to_text(state, event->rrtype, event->rdata, event->rdlen, rrbuf, sizeof(rrbuf));
INFO(" register record: %s IN %s (%p %p) -> %d", STR_OR_NULL(event->name),
STR_OR_NULL(rr_printed), (char *)event->rref, (char *)event->sdref, event->status);
break;
case dns_service_event_type_remove_record:
INFO(" remove record: (%p %p) -> %d", (char *)event->rref, (char *)event->sdref, event->status);
break;
case dns_service_event_type_update_record:
// Get the rrtype from the previous event.
rdata = event->rdata;
rdlen = event->rdlen;
for (dns_service_event_t *ep = events; ep != NULL; ep = ep->next) {
if (ep->event_type == dns_service_event_type_register_record && ep->rref == event->rref) {
rrtype = ep->rrtype;
// When updating the TSR record, we send rdlen=0 and no rdata, which means just update the
// TSR and don't change the RR. But that would cause a parse failure, so we need the data
// as well as the rrtype from the RegisterRecord event.
if (rdlen == 0 && ep->rdlen != 0) {
rdata = ep->rdata;
rdlen = ep->rdlen;
}
break;
}
}
rr_printed = dns_service_rdata_to_text(state, rrtype, rdata, rdlen, rrbuf, sizeof(rrbuf));
INFO(" update record: %s (%p %p) -> %d", STR_OR_NULL(rr_printed), (char *)event->rref, (char *)event->sdref,
event->status);
break;
case dns_service_event_type_ref_deallocate:
INFO(" ref deallocate: (%p %p) -> %d", (char *)event->sdref, (char *)event->parent_sdref, event->status);
break;
case dns_service_event_type_register_callback:
INFO(" reg callback: (%p %p) -> %d", (char *)event->sdref, (char *)event->parent_sdref, event->status);
break;
case dns_service_event_type_register_record_callback:
INFO(" regrec callback: (%p %p) -> %d", (char *)event->rref, (char *)event->sdref, event->status);
break;
}
}
bool
dns_service_dump_unexpected_events(test_state_t *test_state, srp_server_t *server_state)
{
bool ret = true;
for (dns_service_event_t *event = server_state->dns_service_events; event; event = event->next) {
if (!event->consumed) {
dns_service_dump_event(test_state, event, server_state->dns_service_events);
ret = false;
}
}
return ret;
}
dns_service_event_t *
dns_service_find_first_register_event_by_name_and_type(srp_server_t *state, const char *name,
const char *regtype)
{
for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
if (event->event_type == dns_service_event_type_register &&
event->name != NULL && event->regtype != NULL && !event->consumed)
{
INFO("event->name %s name %s event->regtype %s regtype %s",
event->name, name, event->regtype, regtype);
if (!strcmp(event->name, name) && !strcmp(event->regtype, regtype)) {
return event;
}
}
}
return NULL;
}
dns_service_event_t *
dns_service_find_first_register_record_event_by_name(srp_server_t *state, const char *name)
{
for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
if (event->event_type == dns_service_event_type_register_record &&
event->name != NULL && !event->consumed && !strcmp(name, event->name))
{
return event;
}
}
return NULL;
}
dns_service_event_t *
dns_service_find_callback_for_registration(srp_server_t *state, dns_service_event_t *register_event)
{
for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
if (event->event_type == dns_service_event_type_register_callback &&
register_event->event_type == dns_service_event_type_register &&
register_event->sdref == event->sdref && !event->consumed)
{
return event;
}
if (event->event_type == dns_service_event_type_register_record_callback &&
register_event->event_type == dns_service_event_type_register_record &&
register_event->rref == event->rref && !event->consumed)
{
return event;
}
}
return NULL;
}
dns_service_event_t *
dns_service_find_ref_deallocate_event(srp_server_t *state)
{
for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
if (event->event_type == dns_service_event_type_ref_deallocate) {
return event;
}
}
return NULL;
}
// Find a DNSServiceUpdateRecord that corresponds to a DNSServiceRegister or DNSServiceRegisterRecord event.
// For a DNSServiceRegister update, event->rref will be NULL.
dns_service_event_t *
dns_service_find_update_for_register_event(srp_server_t *state, dns_service_event_t *register_event,
dns_service_event_t *after_event)
{
bool after_event_matched = after_event == NULL ? true : false;
for (dns_service_event_t *event = state->dns_service_events; event != NULL; event = event->next) {
// If we are past any after_event and this is an update_record event, see if it's for the same
// registration.
if (after_event_matched && event->event_type == dns_service_event_type_update_record &&
((register_event->rref == 0 && event->sdref == register_event->sdref) ||
(register_event->rref != 0 && event->rref == register_event->rref)))
{
return event;
}
// Don't match events that are prior to after_event (so that we can skip an event if it's not the right one
if (event == after_event) {
after_event_matched = true;
}
}
return NULL;
}
// Find a DNSServiceRemoveRecord
dns_service_event_t *
dns_service_find_remove_for_register_event(srp_server_t *state, dns_service_event_t *register_event,
dns_service_event_t *after_event)
{
bool after_event_matched = after_event == NULL ? true : false;
for (dns_service_event_t *event = state->dns_service_events; event != NULL; event = event->next) {
// If we are past any after_event and this is an update_record event, see if it's for the same
// registration.
if (after_event_matched && event->event_type == dns_service_event_type_remove_record &&
((register_event->rref == 0 && event->sdref == register_event->sdref) ||
(register_event->rref != 0 && event->rref == register_event->rref)))
{
return event;
}
// Don't match events that are prior to after_event (so that we can skip an event if it's not the right one)
if (event == after_event) {
after_event_matched = true;
}
}
return NULL;
}
static dns_service_ref_t *
dns_service_ref_create(srp_server_t *server_state,
DNSServiceRef *target, int flags, void *context)
{
dns_service_ref_t *ret = calloc(1, sizeof(*ret));
TEST_FAIL_CHECK(server_state->test_state, ret != NULL, "no memory for dns_service_ref_t");
if (flags & kDNSServiceFlagsShareConnection) {
ret->sdref = *target;
}
ret->server_state = server_state;
ret->context = context;
return ret;
}
static dns_service_ref_t *
dns_service_ref_create_for_register(srp_server_t *server_state,
DNSServiceRef *target, int flags, void *context, DNSServiceRegisterReply callback)
{
dns_service_ref_t *ret = dns_service_ref_create(server_state, target, flags, context);
if (ret != NULL) {
ret->callback.register_reply = callback;
}
return ret;
}
static dns_service_ref_t *
dns_service_ref_create_for_query_record(srp_server_t *server_state,
DNSServiceRef *target, int flags, void *context, DNSServiceQueryRecordReply callback)
{
dns_service_ref_t *ret = dns_service_ref_create(server_state, target, flags, context);
if (ret != NULL) {
ret->callback.query_record_reply = callback;
}
return ret;
}
static dns_record_ref_t *
dns_record_ref_create(srp_server_t *server_state, void *context, DNSServiceRegisterRecordReply callback)
{
dns_record_ref_t *ret = calloc(1, sizeof(*ret));
TEST_FAIL_CHECK(server_state->test_state, ret != NULL, "no memory for dns_record_ref_t");
ret->server_state = server_state;
ret->context = context;
ret->callback = callback;
return ret;
}
static char *
dns_service_string_dup(const char *src)
{
if (src == NULL) {
return NULL;
}
return strdup(src);
}
static dns_service_event_t *
dns_service_event_append(srp_server_t *state, dns_service_event_type_t event_type, DNSServiceRef sdref,
DNSServiceRef in_sdref, DNSRecordRef rref, DNSServiceFlags flags, uint32_t interfaceIndex,
const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
uint16_t rdlen, const void *rdata, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, void *attr,
void *callBack, void *context, DNSServiceErrorType status)
{
TEST_FAIL_CHECK(NULL, state != NULL, "invalid server state");
dns_service_event_t **ep = &state->dns_service_events;
while (*ep) {
ep = &(*ep)->next;
}
dns_service_event_t *event = calloc(1, sizeof(*event));
TEST_FAIL_CHECK(state->test_state, event != NULL, "no memory for dns service event");
*ep = event;
event->server_state = state;
event->event_type = event_type;
event->sdref = (intptr_t)sdref;
if (in_sdref != NULL && (flags & kDNSServiceFlagsShareConnection)) {
event->parent_sdref = (intptr_t)in_sdref;
}
event->rref = (intptr_t)rref;
event->flags = flags;
event->interface_index = interfaceIndex;
event->name = dns_service_string_dup(name);
event->regtype = dns_service_string_dup(regtype);
event->domain = dns_service_string_dup(domain);
event->host = dns_service_string_dup(host);
event->port = port;
event->rdlen = rdlen;
if (rdata != NULL) {
event->rdata = malloc(rdlen);
TEST_FAIL_CHECK(state->test_state, event->rdata != NULL, "no memory to save rdata");
memcpy(event->rdata, rdata, rdlen);
}
event->rrclass = rrclass;
event->rrtype = rrtype;
event->ttl = ttl;
event->attr = (intptr_t)attr;
event->callBack = (intptr_t)callBack;
event->context = (intptr_t)context;
event->status = status;
return event;
}
static void
dns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context)
{
dns_service_ref_t *ref = context;
dns_service_event_append(ref->server_state, dns_service_event_type_register_callback, sdRef, NULL, NULL, flags, 0,
name, regtype, domain, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, context, errorCode);
DNSServiceRegisterReply callback = ref->callback.register_reply;
if (callback != NULL) {
callback(ref, flags, errorCode, name, regtype, domain, ref->context);
}
}
DNSServiceErrorType
dns_service_register(srp_server_t *state, DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
uint16_t txtLen, const void *txtRecord, DNSServiceRegisterReply callBack, void *context)
{
return dns_service_register_wa(state, sdRef, flags, interfaceIndex, name, regtype, domain, host, port, txtLen,
txtRecord, NULL, callBack, context);
}
DNSServiceErrorType
dns_service_register_wa(srp_server_t *state, DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
uint16_t txtLen, const void *txtRecord, DNSServiceAttributeRef attr,
DNSServiceRegisterReply callBack, void *context)
{
#undef DNSServiceRegisterWithAttribute
dns_service_ref_t *ret = dns_service_ref_create_for_register(state, sdRef, flags, context, callBack);
char updated_name[DNS_MAX_NAME_SIZE + 1];
snprintf(updated_name, sizeof(updated_name), "%d-%s", state->server_id, name);
int status = DNSServiceRegisterWithAttribute(&ret->sdref, flags, interfaceIndex, updated_name, regtype, domain, host, port,
txtLen, txtRecord, attr, dns_service_register_callback, ret);
dns_service_event_append(state, dns_service_event_type_register, ret->sdref, *sdRef, NULL, flags,
interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord,
0, 0, 0, attr, callBack, context, status);
if (status != kDNSServiceErr_NoError) {
free(ret);
}
*sdRef = ret;
return status;
}
static void
dns_service_register_record_callback(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
DNSServiceErrorType errorCode, void *context)
{
dns_record_ref_t *ref = context;
dns_service_event_append(ref->server_state, dns_service_event_type_register_record_callback, sdRef, NULL,
RecordRef, flags, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL,
context, errorCode);
if (ref->callback != NULL) {
ref->callback(sdRef, ref, flags, errorCode, ref->context);
}
}
DNSServiceErrorType
dns_service_register_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags,
uint32_t interfaceIndex, const char *fullname, uint16_t rrtype, uint16_t rrclass,
uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceRegisterRecordReply callBack,
void *context)
{
return dns_service_register_record_wa(state, sdRef, RecordRef, flags, interfaceIndex, fullname, rrtype, rrclass, rdlen,
rdata, ttl, NULL, callBack, context);
}
DNSServiceErrorType
dns_service_register_record_wa(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags,
uint32_t interfaceIndex, const char *fullname, uint16_t rrtype, uint16_t rrclass,
uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceAttributeRef attr,
DNSServiceRegisterRecordReply callBack,
void *context)
{
#undef DNSServiceRegisterRecordWithAttribute
dns_record_ref_t *ret = dns_record_ref_create(state, context, callBack);
// Since in testing multiple srp servers share the same mDNSResponder,
// we intentionally rename the record by prepending the server_id
// to the record name when registering with mDNSResponder, so that
// it will not generate conflict for replicated records.
char updated_name[DNS_MAX_NAME_SIZE + 1];
snprintf(updated_name, sizeof(updated_name), "%d-%s", state->server_id, fullname);
int status = DNSServiceRegisterRecordWithAttribute(sdRef, &ret->rref, flags, interfaceIndex, updated_name, rrtype,
rrclass, rdlen, rdata, ttl, attr,
dns_service_register_record_callback, ret);
dns_service_event_append(state, dns_service_event_type_register_record, sdRef, NULL, ret->rref, flags,
interfaceIndex, fullname, NULL, NULL, NULL, 0, rdlen, rdata, rrtype, rrclass, ttl, attr,
callBack, context, status);
if (status != kDNSServiceErr_NoError) {
free(ret);
}
*RecordRef = ret;
return status;
}
// Note that this will not work with a recordref returned by DNSServiceAddRecord(), which we aren't currently intercepting
// because we don't use it.
DNSServiceErrorType
dns_service_remove_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags)
{
#undef DNSServiceRemoveRecord
int ret = DNSServiceRemoveRecord(sdRef, RecordRef->rref, flags);
dns_service_event_append(state, dns_service_event_type_remove_record, sdRef->sdref, NULL, RecordRef->rref,
flags, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, NULL, ret);
return ret;
}
DNSServiceErrorType
dns_service_update_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
uint16_t rdlen, const void *rdata, uint32_t ttl)
{
return dns_service_update_record_wa(state, sdRef, RecordRef, flags, rdlen, rdata, ttl, NULL);
}
DNSServiceErrorType
dns_service_update_record_wa(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceAttributeRef attr)
{
#undef DNSServiceUpdateRecordWithAttribute
int ret;
if (RecordRef != NULL) {
ret = DNSServiceUpdateRecordWithAttribute(sdRef, RecordRef->rref, flags, rdlen, rdata, ttl, attr);
dns_service_event_append(state, dns_service_event_type_update_record, sdRef, NULL, RecordRef->rref, flags, 0,
NULL, NULL, NULL, NULL, 0, rdlen, rdata, 0, 0, ttl, NULL, NULL, NULL, ret);
} else {
ret = DNSServiceUpdateRecordWithAttribute(sdRef->sdref, NULL, flags, rdlen, rdata, ttl, attr);
dns_service_event_append(state, dns_service_event_type_update_record, sdRef->sdref, NULL, NULL, flags, 0,
NULL, NULL, NULL, NULL, 0, rdlen, rdata, 0, 0, ttl, NULL, NULL, NULL, ret);
}
return ret;
}
static void
dns_service_query_record_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype,
uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
{
dns_service_ref_t *ref = context;
srp_server_t *server_state = ref->server_state;
test_state_t *state = server_state->test_state;
bool call_callback = true;
dns_service_query_record_callback_intercept_t intercept = state->dns_service_query_callback_intercept;
if (intercept != NULL) {
call_callback = intercept(ref, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, ref->context);
}
if (call_callback) {
DNSServiceQueryRecordReply callback = ref->callback.query_record_reply;
callback(ref, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, ref->context);
}
}
DNSServiceErrorType
dns_service_query_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply NONNULL callBack,
void *NULLABLE context)
{
return dns_service_query_record_wa(srp_server, sdRef, flags, interfaceIndex, fullname, rrtype, rrclass,
NULL, callBack, context);
}
DNSServiceErrorType
dns_service_query_record_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
uint16_t rrtype, uint16_t rrclass, DNSServiceAttribute const *NULLABLE attr,
DNSServiceQueryRecordReply NONNULL callBack, void *NULLABLE context)
{
dns_service_ref_t *ret = dns_service_ref_create_for_query_record(srp_server, sdRef, flags, context, callBack);
test_state_t *state = srp_server->test_state;
int status;
if (state->query_record_intercept != NULL) {
status = state->query_record_intercept(state, &ret->sdref, flags, interfaceIndex, fullname, rrtype, rrclass,
attr, dns_service_query_record_callback, ret);
} else {
status = DNSServiceQueryRecordWithAttribute(&ret->sdref, flags, interfaceIndex, fullname, rrtype, rrclass,
attr, dns_service_query_record_callback, ret);
}
if (status != kDNSServiceErr_NoError) {
free(ret);
}
*sdRef = ret;
return status;
}
dnssd_txn_t *NULLABLE
dns_service_ioloop_txn_add(srp_server_t UNUSED *NULLABLE srp_server, DNSServiceRef NONNULL sdref,
void *NULLABLE context, dnssd_txn_finalize_callback_t NULLABLE finalize_callback,
dnssd_txn_failure_callback_t NULLABLE failure_callback)
{
return ioloop_dnssd_txn_add(sdref->sdref, context, finalize_callback, failure_callback);
}
void
dns_service_ref_deallocate(srp_server_t *state, DNSServiceRef sdRef)
{
#undef DNSServiceRefDeallocate
dns_service_event_append(state, dns_service_event_type_ref_deallocate, sdRef, NULL, NULL,
0, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, NULL, 0);
DNSServiceRefDeallocate(sdRef->sdref);
free(sdRef);
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: