/* test-api.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. * * srp host API test harness */ #include #include #include #include #include #include #include #include #include "srp.h" #include "srp-api.h" #include "dns-msg.h" #include "srp-crypto.h" #include "ioloop.h" #include "dso-utils.h" #include "dso.h" #include "cti-services.h" #include "test-api.h" #include "srp-proxy.h" #include "srp-mdns-proxy.h" #include "dnssd-proxy.h" #include "route.h" #define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL // BEES! Everybody gets BEES! typedef struct io_context { uint64_t magic_cookie1; wakeup_t *wakeup; void *NONNULL srp_context; void *NONNULL host_context; comm_t *NULLABLE connection; srp_wakeup_callback_t wakeup_callback; srp_datagram_callback_t datagram_callback; bool deactivated, closed; uint64_t magic_cookie2; } io_context_t; // For testing signature with a time that's out of range. static bool test_bad_sig_time; // For testing a signature that doesn't validate static bool invalidate_signature; bool configure_dnssd_proxy(void) { extern srp_server_t *srp_servers; if (srp_servers->test_state != NULL && srp_servers->test_state->dnssd_proxy_configurer != NULL) { return srp_servers->test_state->dnssd_proxy_configurer(); } else { dnssd_proxy_udp_port= 53; dnssd_proxy_tcp_port = 53; dnssd_proxy_tls_port = 853; return true; } } int srp_test_getifaddrs(srp_server_t *server_state, struct ifaddrs **ifaddrs, void *context) { if (server_state->test_state != NULL && server_state->test_state->getifaddrs != NULL) { return server_state->test_state->getifaddrs(server_state, ifaddrs, context); } return getifaddrs(ifaddrs); } void srp_test_freeifaddrs(srp_server_t *server_state, struct ifaddrs *ifaddrs, void *context) { if (server_state->test_state != NULL && server_state->test_state->freeifaddrs != NULL) { server_state->test_state->freeifaddrs(server_state, ifaddrs, context); return; } freeifaddrs(ifaddrs); } void srp_test_state_add_timeout(test_state_t *state, int timeout) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timeout), dispatch_get_main_queue(), ^{ if (!state->test_complete) { TEST_FAIL(state, "test failed: timeout"); exit(1); } }); } void srp_test_state_next(test_state_t *state) { if (state->next != NULL) { test_state_t *next_state = state->next; next_state->test_complete = true; next_state->finished_tests = state; if (next_state->continue_testing == NULL) { TEST_FAIL(next_state, "no continue function"); } next_state->continue_testing(next_state); } else { exit(0); } } void srp_test_state_explain(test_state_t *state) { if (state != NULL) { if (state->variant_title != NULL) { fprintf(stderr, "\n%s (%s variant)\n", state->title, state->variant_title); } else { fprintf(stderr, "\n%s\n", state->title); } fprintf(stderr, "\n%s\n\n", state->explanation); if (state->variant_info != NULL) { fprintf(stderr, "Variant: %s\n\n", state->variant_info); } } } test_state_t * test_state_create(srp_server_t *primary, const char *title, const char *variant_title, const char *explanation, const char *variant_info) { test_state_t *ret = calloc(1, sizeof(*ret)); TEST_FAIL_CHECK(NULL, ret != NULL, "no memory for test state"); ret->primary = primary; primary->test_state = ret; ret->title = title; ret->variant_title = variant_title; ret->explanation = explanation; // Explanation is assumed to be a compile-time constant string. ret->variant_info = variant_info; return ret; } void srp_test_set_local_example_address(test_state_t *UNUSED state) { static const uint8_t ifaddr[] = { 0x20, 1, 0xd, 0xb8, // 2001:0db8: 0, 0, 0, 0, // /64 prefix 0, 0, 0, 0, 0, 0, 0, 1, // 2001:db8::1 }; srp_add_interface_address(dns_rrtype_aaaa, ifaddr, sizeof(ifaddr)); } void srp_test_network_localhost_start(test_state_t *UNUSED state) { static const uint8_t localhost[] = { 0, 0, 0, 0, 0, 0, 0, 0, // /64 prefix 0, 0, 0, 0, 0, 0, 0, 1, // ::1 }; static const uint8_t port[] = { 0, 53 }; srp_add_server_address(port, dns_rrtype_aaaa, localhost, sizeof(localhost)); srp_test_set_local_example_address(state); srp_network_state_stable(NULL); } bool srp_get_last_server(uint16_t *NONNULL UNUSED rrtype, uint8_t *NONNULL UNUSED rdata, uint16_t UNUSED rdlim, uint8_t *NONNULL UNUSED port, void *NULLABLE UNUSED host_context) { return false; } bool srp_save_last_server(uint16_t UNUSED rrtype, uint8_t *UNUSED rdata, uint16_t UNUSED rdlength, uint8_t *UNUSED port, void *UNUSED host_context) { return false; } static int validate_io_context(io_context_t **dest, void *src) { io_context_t *context = src; if (context->magic_cookie1 == SRP_IO_CONTEXT_MAGIC && context->magic_cookie2 == SRP_IO_CONTEXT_MAGIC) { *dest = context; return kDNSServiceErr_NoError; } return kDNSServiceErr_BadState; } int srp_deactivate_udp_context(void *host_context, void *in_context) { io_context_t *io_context; int err; (void)host_context; err = validate_io_context(&io_context, in_context); if (err == kDNSServiceErr_NoError) { if (io_context->wakeup != NULL) { ioloop_cancel_wake_event(io_context->wakeup); ioloop_wakeup_release(io_context->wakeup); } // Deactivate can be called with a connection still active; in this case, we need to wait for the // cancel event before freeing the structure. Otherwise, we can free it immediately. if (io_context->connection != NULL) { ioloop_comm_cancel(io_context->connection); io_context->deactivated = true; io_context->closed = true; } else { free(io_context); } } return err; } int srp_disconnect_udp(void *context) { io_context_t *io_context; int err; err = validate_io_context(&io_context, context); if (err == kDNSServiceErr_NoError) { if (io_context->wakeup != NULL) { ioloop_cancel_wake_event(io_context->wakeup); } if (io_context->connection) { io_context->connection = NULL; } io_context->closed = true; } return err; } static bool srp_test_send_intercept(comm_t *connection, message_t *UNUSED responding_to, struct iovec *iov, int iov_len, bool UNUSED final, bool send_length) { bool send_length_real = send_length; srp_server_t *srp_server = connection->test_context; test_state_t *test_state = srp_server->test_state; io_context_t *current_io_context = test_state->current_io_context; TEST_FAIL_CHECK(test_state, test_state != NULL, "no test state"); TEST_FAIL_CHECK(test_state, current_io_context != NULL, "no I/O state"); TEST_FAIL_CHECK(test_state, current_io_context->datagram_callback != NULL, "no datagram callback"); // Don't copy if we don't have to. if (!send_length && iov_len == 1) { size_t len = iov[0].iov_len; uint8_t *data = calloc(1, len); memcpy(data, iov[0].iov_base, len); dispatch_async(dispatch_get_main_queue(), ^{ current_io_context->datagram_callback(current_io_context, data, len); free(data); }); return true; } // send_length indicates whether we should send a length over TCP, not whether we should send a length. if (!connection->tcp_stream) { send_length_real = false; } // We have an actual iov, or need to prepend a length, so we have to allocate and copy. uint8_t *message; size_t length = send_length_real ? 2 : 0; uint8_t *mp; for (int i = 0; i < iov_len; i++) { length += iov[i].iov_len; } message = malloc(length); TEST_FAIL_CHECK(test_state, message != NULL, "no memory for message"); mp = message; // Marshal all the data into a single buffer. if (send_length_real) { *mp++ = length >> 8; *mp++ = length & 0xff; } for (int i = 0; i < iov_len; i++) { memcpy(mp, iov[i].iov_base, iov[i].iov_len); mp += iov[i].iov_len; } dispatch_async(dispatch_get_main_queue(), ^{ current_io_context->datagram_callback(current_io_context->srp_context, message, length); }); return true; } int srp_connect_udp(void *context, const uint8_t *UNUSED port, uint16_t UNUSED address_type, const uint8_t *UNUSED address, uint16_t UNUSED addrlen) { io_context_t *io_context; int err; err = validate_io_context(&io_context, context); if (err == kDNSServiceErr_NoError) { if (io_context->connection) { ERROR("srp_connect_udp called with non-null I/O context."); return kDNSServiceErr_Invalid; } srp_server_t *server_state = io_context->host_context; test_state_t *test_state = server_state->test_state; if (test_state == NULL || test_state->srp_listener == NULL) { return kDNSServiceErr_NotInitialized; } io_context->connection = test_state->srp_listener; test_state->current_io_context = io_context; io_context->connection->test_send_intercept = srp_test_send_intercept; io_context->connection->test_context = server_state; } return err; } int srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context) { io_context_t *io_context = calloc(1, sizeof *io_context); if (io_context == NULL) { return kDNSServiceErr_NoMemory; } io_context->magic_cookie1 = io_context->magic_cookie2 = SRP_IO_CONTEXT_MAGIC; io_context->datagram_callback = callback; io_context->srp_context = context; io_context->host_context = host_context; io_context->wakeup = ioloop_wakeup_create(); if (io_context->wakeup == NULL) { free(io_context); return kDNSServiceErr_NoMemory; } *p_context = io_context; return kDNSServiceErr_NoError; } static void wakeup_callback(void *context) { io_context_t *io_context; if (validate_io_context(&io_context, context) == kDNSServiceErr_NoError) { INFO("wakeup on context %p srp_context %p", io_context, io_context->srp_context); INFO("setting wakeup callback %p wakeup %p", io_context->wakeup_callback, io_context->wakeup); if (!io_context->deactivated) { io_context->wakeup_callback(io_context->srp_context); } } else { INFO("wakeup with invalid context: %p", context); } } int srp_set_wakeup(void *host_context, void *context, int milliseconds, srp_wakeup_callback_t callback) { int err; io_context_t *io_context; (void)host_context; err = validate_io_context(&io_context, context); if (err == kDNSServiceErr_NoError) { INFO("setting wakeup callback %p wakeup %p", callback, io_context->wakeup); io_context->wakeup_callback = callback; ioloop_add_wake_event(io_context->wakeup, io_context, wakeup_callback, NULL, milliseconds); } return err; } int srp_cancel_wakeup(void *host_context, void *context) { int err; io_context_t *io_context; (void)host_context; err = validate_io_context(&io_context, context); if (err == kDNSServiceErr_NoError) { ioloop_cancel_wake_event(io_context->wakeup); } return err; } int srp_send_datagram(void *host_context, void *context, void *message, size_t message_length) { int err; io_context_t *io_context; srp_server_t *srp_server = host_context; test_state_t *test_state = srp_server->test_state; if (invalidate_signature) { ((uint8_t *)message)[message_length - 10] = ~(((uint8_t *)message)[message_length - 10]); } err = validate_io_context(&io_context, context); if (err == kDNSServiceErr_NoError) { if (io_context->connection == NULL) { return kDNSServiceErr_DefunctConnection; } TEST_FAIL_CHECK(test_state, io_context->connection->datagram_callback != NULL, "srp listener has no datagram callback"); message_t *actual = ioloop_message_create(message_length); TEST_FAIL_CHECK(test_state, actual != NULL, "no memory for message"); memcpy(&actual->wire, message, message_length); io_context->connection->datagram_callback(io_context->connection, actual, io_context->connection->context); ioloop_message_release(actual); } return err; } uint32_t srp_timenow(void) { time_t now = time(NULL); if (test_bad_sig_time) { return (uint32_t)(now - 10000); } return (uint32_t)now; } void srp_test_enable_stub_router(test_state_t *state, srp_server_t *NONNULL server_state) { server_state->route_state = route_state_create(server_state, "srp-mdns-proxy"); TEST_FAIL_CHECK(state, server_state->route_state != NULL, "no memory for route state"); server_state->stub_router_enabled = true; } // Local Variables: // mode: C // tab-width: 4 // c-file-style: "bsd" // c-basic-offset: 4 // fill-column: 108 // indent-tabs-mode: nil // End: