/* 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 #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 : "") 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: