blob: 0d4a6d475069904f7a9766b08880e5aeced8db18 [file] [log] [blame] [edit]
/* 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 <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dns_sd.h>
#include <errno.h>
#include <fcntl.h>
#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: