blob: 4c7e76bf5426fcdc6869a44325e5a3e425ab9cb0 [file] [log] [blame]
/* srp-ioloop.c
*
* Copyright (c) 2019-2022 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 implementation for Posix using ioloop primitives.
*/
#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"
static int lease_time = 0;
static bool random_leases = false;
static bool delete_registrations = false;
static bool use_thread_services = false;
static bool change_txt_record = false;
static bool new_ip_dup = false;
static bool dup_instance_name = false;
static bool random_txt_record = false;
static bool remove_added_service = false;
static bool let_added_service_expire = false;
static bool expecting_second_add = true;
static int num_clients = 1;
static int bogusify_signatures = false;
static int bogus_remove = false;
static int push_query = false;
static int push_unsubscribe = false;
static int push_exhaust = false;
static bool test_subtypes = false;
static bool test_renew_subtypes = false;
static bool test_diff_subtypes = false;
static uint8_t first_bogus_address[] = { 198, 51, 100, 1 }; // RFC 5737 documentation prefix TEST-NET-2
static uint8_t second_bogus_address[] = { 203, 0, 113, 1 }; // RFC 5737 documentation prefix TEST-NET-3
static bool host_only = false;
extern bool zero_addresses;
static bool expire_instance = false;
static bool test_bad_sig_time = false;
const uint64_t thread_enterprise_number = 52627;
cti_connection_t thread_service_context;
static const char *interface_name = NULL;
static wakeup_t *wait_for_remote_disconnect = NULL;
static dso_state_t *disconnect_expected = NULL;
#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;
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;
wakeup_t *remove_wakeup;
typedef struct srp_client {
DNSServiceRef sdref;
int index;
wakeup_t *wakeup;
char *name;
bool updated_txt_record;
} srp_client_t;
static void start_push_query(void);
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;
}
static void
datagram_callback(comm_t *connection, message_t *message, void *context)
{
(void)connection;
io_context_t *io_context = context;
if (!io_context->deactivated) {
io_context->datagram_callback(io_context->srp_context,
&message->wire, message->length);
}
}
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);
if (!io_context->deactivated) {
io_context->wakeup_callback(io_context->srp_context);
}
} else {
INFO("wakeup with invalid context: %p", context);
}
}
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->connection) {
ioloop_comm_cancel(io_context->connection);
}
io_context->closed = true;
}
return err;
}
static void
srp_udp_context_canceled(comm_t *UNUSED NONNULL comm, void *NULLABLE context, int UNUSED error)
{
io_context_t *io_context = context;
if (io_context->connection) {
ioloop_comm_release(io_context->connection);
io_context->connection = NULL;
}
if (io_context->deactivated) {
free(io_context);
}
}
int
srp_connect_udp(void *context, const uint8_t *port, uint16_t address_type, const uint8_t *address, uint16_t addrlen)
{
io_context_t *io_context;
addr_t remote;
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;
}
if (address_type == dns_rrtype_a) {
if (addrlen != 4) {
return kDNSServiceErr_Invalid;
}
remote.sa.sa_family = AF_INET;
memcpy(&remote.sin.sin_addr, address, addrlen);
#ifndef NOT_HAVE_SA_LEN
remote.sa.sa_len = sizeof remote.sin;
#endif
memcpy(&remote.sin.sin_port, port, 2);
} else {
if (addrlen != 16) {
return kDNSServiceErr_Invalid;
}
remote.sa.sa_family = AF_INET6;
memcpy(&remote.sin6.sin6_addr, address, addrlen);
#ifndef NOT_HAVE_SA_LEN
remote.sa.sa_len = sizeof remote.sin;
#endif
memcpy(&remote.sin6.sin6_port, port, 2);
}
io_context->connection = ioloop_connection_create(&remote, false, false, false, true, datagram_callback,
NULL, srp_udp_context_canceled, NULL, io_context);
if (io_context->connection == NULL) {
return kDNSServiceErr_NoMemory;
}
}
return err;
}
int
srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context)
{
(void)host_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->wakeup = ioloop_wakeup_create();
if (io_context->wakeup == NULL) {
free(io_context);
return kDNSServiceErr_NoMemory;
}
*p_context = io_context;
return kDNSServiceErr_NoError;
}
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) {
io_context->wakeup_callback = callback;
INFO("srp_set_wakeup on context %p, srp_context %p", io_context, io_context->srp_context);
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;
struct iovec iov;
io_context_t *io_context;
(void)host_context;
memset(&iov, 0, sizeof iov);
iov.iov_base = message;
iov.iov_len = message_length;
if (bogusify_signatures) {
((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;
}
if (!ioloop_send_message(io_context->connection, message, &iov, 1)) {
return kDNSServiceErr_Unknown;
}
}
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;
}
static void
interface_callback(void *context, const char *NONNULL name,
const addr_t *NONNULL address, const addr_t *NONNULL netmask,
uint32_t flags, enum interface_address_change event_type)
{
bool drop = false;
uint8_t *rdata;
uint16_t rdlen;
uint16_t rrtype;
cti_service_vec_t *cti_services = context;
(void)netmask;
(void)index;
(void)event_type;
if (address->sa.sa_family == AF_INET) {
rrtype = dns_rrtype_a;
rdata = (uint8_t *)&address->sin.sin_addr;
rdlen = 4;
// Should use IN_LINKLOCAL and IN_LOOPBACK macros here, but for some reason they are not present on
// OpenWRT.
if (rdata[0] == 127) {
drop = true;
} else if (rdata[0] == 169 && rdata[1] == 254) {
drop = true;
}
} else if (address->sa.sa_family == AF_INET6) {
rrtype = dns_rrtype_aaaa;
rdata = (uint8_t *)&address->sin6.sin6_addr;
rdlen = 16;
if (IN6_IS_ADDR_LOOPBACK(&address->sin6.sin6_addr)) {
drop = true;
} else if (IN6_IS_ADDR_LINKLOCAL(&address->sin6.sin6_addr)) {
drop = true;
}
} else {
return;
}
if (drop) {
if (address->sa.sa_family == AF_INET) {
IPv4_ADDR_GEN_SRP(rdata, ipv4_rdata_buf);
DEBUG("interface_callback: ignoring " PUB_S_SRP " " PRI_IPv4_ADDR_SRP, name,
IPv4_ADDR_PARAM_SRP(rdata, ipv4_rdata_buf));
} else if (address->sa.sa_family == AF_INET6) {
SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, ipv6_rdata_buf);
DEBUG("interface_callback: ignoring " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP, name,
SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, ipv6_rdata_buf));
} else {
INFO("ignoring with non-v4/v6 address" PUB_S_SRP, name);
}
return;
}
if (address->sa.sa_family == AF_INET) {
IPv4_ADDR_GEN_SRP(rdata, ipv4_rdata_buf);
DEBUG("interface_callback: " PUB_S_SRP " " PRI_IPv4_ADDR_SRP " %x", name,
IPv4_ADDR_PARAM_SRP(rdata, ipv4_rdata_buf), flags);
} else if (address->sa.sa_family == AF_INET6) {
SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, ipv6_rdata_buf);
DEBUG("interface_callback: " PUB_S_SRP " " PRI_SEGMENTED_IPv6_ADDR_SRP " %x", name,
SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, ipv6_rdata_buf), flags);
} else {
DEBUG("interface_callback: " PUB_S_SRP "<none IPv4/IPv6 address> %x", name, flags);
}
// This is a workaround for a bug in the utun0 code, where packets sent to the IP address of the local
// thread interface are dropped and do not reach the SRP server. To address this, if we find a service
// that is on a local IPv6 address, we replace the address with ::1.
if (cti_services != NULL && rrtype == dns_rrtype_aaaa) {
size_t i;
for (i = 0; i < cti_services->num; i++) {
cti_service_t *cti_service = cti_services->services[i];
// Look for SRP service advertisements only.
if (IS_SRP_SERVICE(cti_service)) {
// Local IP address?
if (!memcmp(cti_service->server, rdata, 16)) {
// ::
memset(cti_service->server, 0, 15);
// 1
cti_service->server[15] = 1;
}
}
}
}
srp_add_interface_address(rrtype, rdata, rdlen);
}
static void
remove_callback(void *context)
{
srp_client_t *client = context;
if (bogus_remove) {
srp_set_hostname("bogus-api-test", NULL);
}
srp_deregister(client);
}
static void
second_register_callback(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context)
{
srp_client_t *client = context;
(void)regtype;
(void)flags;
(void)name;
(void)regtype;
(void)domain;
INFO("Second Register Reply for %s: %d", client->name, errorCode);
if (errorCode == kDNSServiceErr_NoError) {
if (expecting_second_add) {
expecting_second_add = false;
if (remove_added_service) {
srp_deregister_instance(sdref);
srp_network_state_stable(NULL);
} else if (let_added_service_expire) {
DNSServiceRefDeallocate(sdref);
}
} else {
// Test succeeded
exit(0);
}
} else {
// Test failed
exit(1);
}
}
static void
register_callback(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context)
{
srp_client_t *client = context;
(void)regtype;
(void)flags;
(void)name;
(void)regtype;
(void)domain;
INFO("Register Reply for %s: %d", client->name, errorCode);
if (errorCode == kDNSServiceErr_NoError) {
if ((change_txt_record && !client->updated_txt_record) || expire_instance) {
TXTRecordRef txt;
const void *txt_data = NULL;
uint16_t txt_len = 0;
char txt_buf[128];
TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
TXTRecordSetValue(&txt, "foo", 1, "1");
TXTRecordSetValue(&txt, "bar", 3, "1.1");
txt_data = TXTRecordGetBytesPtr(&txt);
txt_len = TXTRecordGetLength(&txt);
if (expire_instance) {
char hnbuf[128];
ioloop_strcpy(hnbuf, name, sizeof(hnbuf));
// silently let the first expire. This is just going to leak the data, but since this is a one-shot test
// that's not an actual problem.
DNSServiceRefDeallocate(sdref);
DNSServiceRef nsdref;
expecting_second_add = true;
// register a second instance
int err = DNSServiceRegister(&nsdref, 0, 0, hnbuf, "_second._tcp,foo", 0, 0, htons(1234),
txt_len, txt_data, second_register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("second DNSServiceRegister failed: %d", err);
exit(1);
}
} else {
(void)DNSServiceUpdateRecord(sdref, NULL, 0, txt_len, txt_data, 0);
client->updated_txt_record = true;
}
srp_network_state_stable(NULL);
return;
}
if (delete_registrations) {
client->wakeup = ioloop_wakeup_create();
if (client->wakeup == NULL) {
ERROR("Unable to allocate a wakeup for %s.", client->name);
exit(1);
}
// Do a remove in five seconds.
ioloop_add_wake_event(client->wakeup, client, remove_callback, NULL, 5000);
return;
}
if (test_renew_subtypes) {
int err = srp_update_service_type(sdref, "_ipps._tcp,othersub", second_register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("srp_update_service_type failed: %d", err);
exit(1);
}
srp_network_state_stable(NULL);
return;
}
}
// We get this with the duplicate instance name. In this case, we change the host IP address. This validates
// the bit of code in srp-mdns-proxy that removes the added address when the update fails--when we look up the
// registered address, we should see only the second bogus address, not the first.
if (errorCode == kDNSServiceErr_NameConflict) {
char nnbuf[128];
char rtbuf[128];
srp_delete_interface_address(dns_rrtype_a, first_bogus_address, sizeof(first_bogus_address));
srp_add_interface_address(dns_rrtype_a, second_bogus_address, sizeof(second_bogus_address));
ioloop_strcpy(nnbuf, name, sizeof(nnbuf));
ioloop_strcpy(rtbuf, regtype, sizeof(rtbuf));
nnbuf[0] = 'a';
INFO("changing service instance name from " PRI_S_SRP " to " PRI_S_SRP " type " PRI_S_SRP, name, nnbuf, rtbuf);
DNSServiceRefDeallocate(sdref);
int err = DNSServiceRegister(&sdref, kDNSServiceFlagsNoAutoRename, 0, nnbuf, rtbuf, 0, 0, htons(1234),
0, NULL, register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("DNSServiceRegister rename failed: %d", err);
exit(1);
}
srp_network_state_stable(NULL);
}
}
comm_t *dso_connection;
typedef struct connection_list connection_list_t;
struct connection_list {
connection_list_t *next;
comm_t *connection;
};
connection_list_t *dso_connection_list;
uint16_t subscribe_xid;
uint16_t keepalive_xid;
static void
send_push_unsubscribe(void)
{
struct iovec iov;
INFO("unsubscribe");
dns_wire_t dns_message;
uint8_t *buffer = (uint8_t *)&dns_message;
dns_towire_state_t towire;
dso_message_t message;
dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, true, false, 0, 0, NULL);
memset(&towire, 0, sizeof(towire));
towire.p = &buffer[DNS_HEADER_SIZE];
towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
towire.message = &dns_message;
dns_u16_to_wire(&towire, kDSOType_DNSPushUnsubscribe);
dns_rdlength_begin(&towire);
dns_full_name_to_wire(NULL, &towire, "_hap._udp.openthread.thread.home.arpa");
dns_u16_to_wire(&towire, dns_rrtype_ptr);
dns_u16_to_wire(&towire, dns_qclass_in);
dns_rdlength_end(&towire);
memset(&iov, 0, sizeof(iov));
iov.iov_len = towire.p - buffer;
iov.iov_base = buffer;
ioloop_send_message(dso_connection, NULL, &iov, 1);
subscribe_xid = dns_message.id; // We need this to identify the response.
// Send a keepalive message so that we can get the response, since the unsubscribe is not a response-requiring request.
dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, false, false, 0, 0, NULL);
memset(&towire, 0, sizeof(towire));
towire.p = &buffer[DNS_HEADER_SIZE];
towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
towire.message = &dns_message;
dns_u16_to_wire(&towire, kDSOType_Keepalive);
dns_rdlength_begin(&towire);
dns_u32_to_wire(&towire, 600);
dns_u32_to_wire(&towire, 600);
dns_rdlength_end(&towire);
keepalive_xid = dns_message.id;
memset(&iov, 0, sizeof(iov));
iov.iov_len = towire.p - buffer;
iov.iov_base = buffer;
ioloop_send_message(dso_connection, NULL, &iov, 1);
}
static void
dso_remote_disconnect_didnt_happen(void *UNUSED context)
{
INFO("remote disconnect didn't happen");
exit(1);
}
static void
handle_retry_delay(dso_state_t *dso, uint32_t delay)
{
INFO("Got our retry delay, %ums...", delay);
wait_for_remote_disconnect = ioloop_wakeup_create();
if (!wait_for_remote_disconnect) {
INFO("can't wait for remote disconnect.");
exit(1);
}
// Wait six seconds for remote disconnect, which should happen in five.
ioloop_add_wake_event(wait_for_remote_disconnect, NULL, dso_remote_disconnect_didnt_happen, NULL, 6 * 1000);
disconnect_expected = dso;
}
static void
dso_message(message_t *message, dso_state_t *dso, bool response)
{
#if PRINT_TO_STDERR
char name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
char ptrname[DNS_MAX_NAME_SIZE_ESCAPED + 1];
#endif
unsigned offset, max;
dns_rr_t rr;
uint8_t *message_bytes;
switch(dso->primary.opcode) {
case kDSOType_RetryDelay:
if (response) {
INFO("server sent a retry delay TLV as a response.");
exit(1);
}
dso_retry_delay(dso, &message->wire);
break;
case kDSOType_Keepalive:
if (response) {
INFO("Keepalive response from server, rcode = %d", dns_rcode_get(&message->wire));
exit(0);
} else {
INFO("Keepalive from server");
}
break;
case kDSOType_DNSPushSubscribe:
if (response) {
// This is a protocol error--the response isn't supposed to contain a primary TLV.
INFO("DNS Push response from server, rcode = %d", dns_rcode_get(&message->wire));
exit(1);
} else {
INFO("Unexpected DNS Push request from server, rcode = %d", dns_rcode_get(&message->wire));
}
break;
case kDSOType_DNSPushUpdate:
// DNS Push Updates are never responses.
// DNS Push updates are compressed, so we can't just parse data out of the primary--we need to align
// our parse with the start of the message data.
message_bytes = (uint8_t *)message->wire.data;
offset = (unsigned)(dso->primary.payload - message_bytes); // difference can never be greater than sizeof(message->wire).
max = offset + dso->primary.length;
while (offset < max) {
if (!dns_rr_parse(&rr, message_bytes, max, &offset, true, true)) {
// Should have emitted an error earlier
break;
}
#if PRINT_TO_STDERR
dns_name_print(rr.name, name, sizeof(name));
if (rr.type != dns_rrtype_ptr) {
fprintf(stderr, "%s: type %u class %u ttl %" PRIu32 "\n", name, rr.type, rr.qclass, rr.ttl);
} else {
dns_name_print(rr.data.ptr.name, ptrname, sizeof(ptrname));
fprintf(stderr, "%s IN PTR %s\n", name, ptrname);
}
#endif
}
if (push_unsubscribe) {
send_push_unsubscribe();
} else if (!push_exhaust) {
exit(0);
}
break;
case kDSOType_NoPrimaryTLV: // No Primary TLV
if (response) {
if (message->wire.id == subscribe_xid) {
int rcode = dns_rcode_get(&message->wire);
INFO("DNS Push Subscribe response from server, rcode = %d", rcode);
if (rcode != dns_rcode_noerror) {
exit(0);
}
if (push_exhaust) {
start_push_query();
}
} else if (message->wire.id == keepalive_xid) {
int rcode = dns_rcode_get(&message->wire);
INFO("DNS Keepalive response from server, rcode = %d", rcode);
exit(0);
} else {
int rcode = dns_rcode_get(&message->wire);
INFO("Unexpected DSO response from server, rcode = %d", rcode);
}
} else {
INFO("DSO request with no primary TLV.");
exit(1);
}
break;
default:
INFO("dso_message: unexpected primary TLV %d", dso->primary.opcode);
dso_simple_response(dso_connection, NULL, &message->wire, dns_rcode_dsotypeni);
break;
}
}
static void
dso_event_callback(void *UNUSED context, void *event_context, dso_state_t *dso, dso_event_type_t eventType)
{
message_t *message;
dso_query_receive_context_t *response_context;
dso_disconnect_context_t *disconnect_context;
switch(eventType)
{
case kDSOEventType_DNSMessage:
// We shouldn't get here because we already handled any DNS messages
message = event_context;
INFO("DNS Message (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
dso->remote_name);
break;
case kDSOEventType_DNSResponse:
// We shouldn't get here because we already handled any DNS messages
message = event_context;
INFO("DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
dso->remote_name);
break;
case kDSOEventType_DSOMessage:
INFO("DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
dso->primary.opcode, dso->remote_name);
message = event_context;
dso_message(message, dso, false);
break;
case kDSOEventType_DSOResponse:
INFO("DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
dso->primary.opcode, dso->remote_name);
response_context = event_context;
message = response_context->message_context;
dso_message(message, dso, true);
break;
case kDSOEventType_Finalize:
INFO("Finalize");
break;
case kDSOEventType_Connected:
INFO("Connected to " PRI_S_SRP, dso->remote_name);
break;
case kDSOEventType_ConnectFailed:
INFO("Connection to " PRI_S_SRP " failed", dso->remote_name);
break;
case kDSOEventType_Disconnected:
INFO("Connection to " PRI_S_SRP " disconnected", dso->remote_name);
if (dso == disconnect_expected) {
INFO("remote end disconnected as expected.");
exit(0);
}
break;
case kDSOEventType_ShouldReconnect:
INFO("Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
break;
case kDSOEventType_Inactive:
INFO("Inactivity timer went off, closing connection.");
break;
case kDSOEventType_Keepalive:
INFO("should send a keepalive now.");
break;
case kDSOEventType_KeepaliveRcvd:
INFO("keepalive received.");
break;
case kDSOEventType_RetryDelay:
disconnect_context = event_context;
INFO("retry delay received, %d seconds", disconnect_context->reconnect_delay);
handle_retry_delay(dso, disconnect_context->reconnect_delay);
break;
}
}
static void
dso_connected(comm_t *connection, void *UNUSED context)
{
struct iovec iov;
INFO("connected");
connection->dso = dso_state_create(false, 1, connection->name, dso_event_callback,
dso_connection, NULL, dso_connection);
if (connection->dso == NULL) {
ERROR("can't create dso state object.");
exit(1);
}
dns_wire_t dns_message;
uint8_t *buffer = (uint8_t *)&dns_message;
dns_towire_state_t towire;
dso_message_t message;
dso_make_message(&message, buffer, sizeof(dns_message), connection->dso, false, false, 0, 0, NULL);
memset(&towire, 0, sizeof(towire));
towire.p = &buffer[DNS_HEADER_SIZE];
towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
towire.message = &dns_message;
dns_u16_to_wire(&towire, kDSOType_DNSPushSubscribe);
dns_rdlength_begin(&towire);
dns_full_name_to_wire(NULL, &towire, "_hap._udp.openthread.thread.home.arpa");
dns_u16_to_wire(&towire, dns_rrtype_ptr);
dns_u16_to_wire(&towire, dns_qclass_in);
dns_rdlength_end(&towire);
memset(&iov, 0, sizeof(iov));
iov.iov_len = towire.p - buffer;
iov.iov_base = buffer;
ioloop_send_message(dso_connection, NULL, &iov, 1);
subscribe_xid = dns_message.id; // We need this to identify the response.
}
static void
dso_disconnected(comm_t *UNUSED connection, void *UNUSED context, int UNUSED error)
{
fprintf(stderr, "disconnected.");
exit(0);
}
static void
dso_datagram_callback(comm_t *connection, message_t *message, void *UNUSED context)
{
// If this is a DSO message, see if we have a session yet.
switch(dns_opcode_get(&message->wire)) {
case dns_opcode_dso:
if (connection->dso == NULL) {
INFO("dso message received with no DSO object on connection " PRI_S_SRP, connection->name);
exit(1);
}
dso_message_received(connection->dso, (uint8_t *)&message->wire, message->length, message);
return;
break;
}
INFO("datagram on connection " PRI_S_SRP " not handled, type = %d.",
connection->name, dns_opcode_get(&message->wire));
}
static void
start_push_query(void)
{
// If we can (should always be able to) remember the list of connections we've created.
if (dso_connection != NULL) {
connection_list_t *connection = calloc(1, sizeof (*connection));
if (connection != NULL) {
connection->connection = dso_connection;
connection->next = dso_connection_list;
dso_connection_list = connection;
}
}
addr_t address;
memset(&address, 0, sizeof(address));
address.sa.sa_family = AF_INET;
address.sin.sin_port = htons(853);
address.sin.sin_addr.s_addr = htonl(0x7f000001); // localhost.
// tls, stream, stable, opportunistic
dso_connection = ioloop_connection_create(&address, true, true, true, true,
dso_datagram_callback, dso_connected, dso_disconnected, NULL, NULL);
if (dso_connection == NULL) {
ERROR("Unable to create dso connection.");
exit(1);
}
}
static void
usage(void)
{
fprintf(stderr,
"srp-client [--lease-time <seconds>] [--client-count <client count>] [--server <address>%%<port>]\n"
" [--push-query] [--push-unsubscribe]\n"
" [--bogus-server-test] [--change-txt-record] [--service-type] [--test-renew-subtypes]\n"
" [--random-leases] [--delete-registrations] [--use-thread-services] [--log-stderr]\n"
" [--interface <interface name>] [--bogusify-signatures] [--remove-added-service]\n"
" [--dup-instance-name] [--service-port <port number>] [--expire-added-service]\n"
" [--random-txt-record] [--bogus-remove] [--test-subtypes] [--test-diff-subtypes]\n"
" [--new-ip-dup] [--push-exhaust] [--test-bad-sig-time] [--zero-addresses]\n"
" [--host-only] [--expire-instance]");
exit(1);
}
static void
cti_service_list_callback(void *UNUSED context, cti_service_vec_t *services, cti_status_t status)
{
size_t i;
if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
INFO("disconnected");
exit(1);
}
if (!new_ip_dup && !zero_addresses) {
srp_start_address_refresh();
ioloop_map_interface_addresses(interface_name, services, interface_callback);
}
for (i = 0; i < services->num; i++) {
cti_service_t *cti_service = services->services[i];
// Look for SRP service advertisements only.
if (IS_SRP_SERVICE(cti_service)) {
srp_add_server_address(&cti_service->server[16], dns_rrtype_aaaa, cti_service->server, 16);
}
}
if (!new_ip_dup && !zero_addresses) {
srp_finish_address_refresh(NULL);
}
srp_network_state_stable(NULL);
}
int
main(int argc, char **argv)
{
uint8_t server_address[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1 };
uint8_t bogus_address[16] = { 0xfc,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1 };
// { 0x26, 0x20, 0x01, 0x49, 0x00, 0x0f, 0x1a, 0x4d, 0x04, 0xff, 0x61, 0x5a, 0xa2, 0x2a, 0xab, 0xe8 };
int err;
DNSServiceRef sdref;
int *nump;
char *end;
(void)argc;
(void)argv;
int i;
bool have_server_address = false;
bool log_stderr = false;
char instance_name[128];
const char *service_type = "_ipps._tcp";
uint16_t service_port = 0;
bool bogus_server = false;
ioloop_init();
for (i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--lease-time")) {
nump = &lease_time;
number:
if (i + 1 == argc) {
usage();
}
*nump = (uint32_t)strtoul(argv[i + 1], &end, 10);
if (end == argv[i + 1] || end[0] != 0) {
usage();
}
i++;
} else if (!strcmp(argv[i], "--client-count")) {
nump = &num_clients;
goto number;
} else if (!strcmp(argv[i], "--interface")) {
if (i + 1 == argc) {
usage();
}
interface_name = argv[i + 1];
i++;
} else if (!strcmp(argv[i], "--server")) {
char *percent;
uint8_t addrbuf[16];
uint16_t addrtype = dns_rrtype_aaaa;
int addrlen = 16;
if (i + 1 == argc) {
usage();
}
percent = strchr(argv[i + 1], '%');
if (percent == NULL || percent[1] == 0) {
usage();
}
*percent = 0;
percent++;
const unsigned long in_server_port = strtoul(percent, &end, 10);
if (in_server_port > UINT16_MAX || end == percent || end[0] != 0) {
usage();
}
uint8_t server_port[2];
server_port[0] = ((uint16_t)in_server_port) >> 8;
server_port[1] = ((uint16_t)in_server_port) & 255;
if (inet_pton(AF_INET6, argv[i + 1], addrbuf) < 1) {
if (inet_pton(AF_INET, argv[i + 1], addrbuf) < 1) {
usage();
} else {
addrtype = dns_rrtype_a;
addrlen = 4;
}
}
srp_add_server_address(server_port, addrtype, addrbuf, addrlen);
have_server_address = true;
i++;
} else if (!strcmp(argv[i], "--random-leases")) {
random_leases = true;
} else if (!strcmp(argv[i], "--delete-registrations")) {
delete_registrations = true;
} else if (!strcmp(argv[i], "--use-thread-services")) {
use_thread_services = true;
} else if (!strcmp(argv[i], "--dup-instance-name")) {
dup_instance_name = true;
} else if (!strcmp(argv[i], "--new-ip-dup")) {
new_ip_dup = true;
} else if (!strcmp(argv[i], "--log-stderr")) {
log_stderr = true;
OPENLOG("srp-client", true);
} else if (!strcmp(argv[i], "--change-txt-record")) {
change_txt_record = true;
} else if (!strcmp(argv[i], "--random-txt-record")) {
random_txt_record = true;
} else if (!strcmp(argv[i], "--remove-added-service")) {
remove_added_service = true;
} else if (!strcmp(argv[i], "--expire-added-service")) {
let_added_service_expire = true;
} else if (!strcmp(argv[1], "--bogus-server-test")) {
bogus_server = true;
} else if (!strcmp(argv[i], "--bogusify-signatures")) {
bogusify_signatures = true;
} else if (!strcmp(argv[i], "--bogus-remove")) {
bogus_remove = true;
} else if (!strcmp(argv[i], "--push-query")) {
push_query = true;
} else if (!strcmp(argv[i], "--push-unsubscribe")) {
push_unsubscribe = true;
} else if (!strcmp(argv[i], "--push-exhaust")) {
push_exhaust = true;
} else if (!strcmp(argv[i], "--test-subtypes")) {
test_subtypes = true;
} else if (!strcmp(argv[i], "--test-diff-subtypes")) {
test_diff_subtypes = true;
} else if (!strcmp(argv[i], "--test-renew-subtypes")) {
test_renew_subtypes = true;
} else if (!strcmp(argv[i], "--host-only")) {
host_only = true;
} else if (!strcmp(argv[i], "--zero-addresses")) {
zero_addresses = true;
} else if (!strcmp(argv[i], "--expire-instance")) {
expire_instance = true;
} else if (!strcmp(argv[i], "--test-bad-sig-time")) {
test_bad_sig_time = true;
} else if (!strcmp(argv[i], "--service-type")) {
if (i + 1 == argc) {
usage();
}
service_type = argv[i + 1];
i++;
} else if (!strcmp(argv[i], "--service-port")) {
if (i + 1 == argc) {
usage();
}
const int in_service_port = atoi(argv[i + 1]);
if (in_service_port == 0 || in_service_port > UINT16_MAX) {
fprintf(stderr, "Service port number %d is out of range or invalid, should be in (0, 65535].\n",
in_service_port);
usage();
}
service_port = (uint16_t)in_service_port;
i++;
} else {
usage();
}
}
if (!log_stderr) {
OPENLOG("srp-client", false);
}
// If we're asked to do a push query, we're not actually going to act as an SRP client, just do the push query.
if (push_query || push_exhaust) {
start_push_query();
ioloop();
exit(1);
}
if (!use_thread_services && !new_ip_dup && !zero_addresses) {
ioloop_map_interface_addresses(interface_name, NULL, interface_callback);
}
if (!have_server_address && !use_thread_services) {
uint8_t port[] = { 0, 53 };
if (bogus_server) {
srp_add_server_address(port, dns_rrtype_aaaa, bogus_address, 16);
}
srp_add_server_address(port, dns_rrtype_aaaa, server_address, 16);
}
if (dup_instance_name) {
num_clients = 2;
ioloop_strcpy(instance_name, "dup-name-test", sizeof(instance_name));
}
if (new_ip_dup || zero_addresses) {
// Set up the test to validate the "failed update removes address" code in srp-mdns-proxy.
srp_add_interface_address(dns_rrtype_a, first_bogus_address, sizeof(first_bogus_address));
if (zero_addresses) {
srp_delete_interface_address(dns_rrtype_a, first_bogus_address, sizeof(first_bogus_address));
}
}
for (i = 0; i < num_clients; i++) {
srp_client_t *client;
char hnbuf[128];
TXTRecordRef txt;
const void *txt_data = NULL;
uint16_t txt_len = 0;
char txt_buf[128];
client = calloc(1, sizeof(*client));
if (client == NULL) {
ERROR("no memory for client %d", i);
exit(1);
}
if (num_clients == 1) {
strcpy(hnbuf, "srp-api-test");
} else {
snprintf(hnbuf, sizeof(hnbuf), "srp-api-test-%d", i);
}
client->name = strdup(hnbuf);
if (client->name == NULL) {
ERROR("No memory for client name %s", hnbuf);
exit(1);
}
client->index = i;
srp_host_init(client);
srp_set_hostname(hnbuf, NULL);
if (random_leases) {
int random_lease_time = 30 + srp_random16() % 1800; // random
INFO("Client %d, lease time = %d", i, random_lease_time);
srp_set_lease_times(random_lease_time, 7 * 24 * 3600); // random host lease, 7 day key lease
} else if (lease_time > 0) {
srp_set_lease_times(lease_time, 7 * 24 * 3600); // specified host lease, 7 day key lease
} else if (let_added_service_expire) {
srp_set_lease_times(30, 30); // Use short lease times so the lease expires quickly.
}
if (change_txt_record) {
TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
TXTRecordSetValue(&txt, "foo", 1, "0");
TXTRecordSetValue(&txt, "bar", 3, "1.1");
txt_data = TXTRecordGetBytesPtr(&txt);
txt_len = TXTRecordGetLength(&txt);
}
if (random_txt_record) {
char rbuf[6];
snprintf(rbuf, sizeof(rbuf), "%u", srp_random16());
TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
TXTRecordSetValue(&txt, "foo", strlen(rbuf), rbuf);
INFO("TXTRecordSetValue(..., \"foo\", %zd, %s)", strlen(rbuf), rbuf);
txt_data = TXTRecordGetBytesPtr(&txt);
txt_len = TXTRecordGetLength(&txt);
}
if (service_port == 0) {
// If no service port is specified (0 indicates that port is unspecified), the index i will be used to
// generate the port number.
service_port = (i % UINT16_MAX) == 0 ? 1 : (i % UINT16_MAX);
}
if (!test_subtypes && !test_diff_subtypes && !test_renew_subtypes && !host_only) {
err = DNSServiceRegister(&sdref, new_ip_dup ? kDNSServiceFlagsNoAutoRename : 0, 0,
dup_instance_name ? instance_name : hnbuf, service_type, 0, 0, htons(service_port),
txt_len, txt_data, register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("DNSServiceRegister failed: %d", err);
exit(1);
}
}
if (remove_added_service || let_added_service_expire) {
expecting_second_add = true;
err = DNSServiceRegister(&sdref, 0, 0, hnbuf, "_second._tcp,foo", 0, 0, htons(service_port),
txt_len, txt_data, second_register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("second DNSServiceRegister failed: %d", err);
exit(1);
}
}
// Here we register two services with subtypes. The idea is to see that the srp parsing code does not
// associate the second subtype with the first service instance and report an error. In order to
// attempt to trigger the error, we need the service instance name of the second service instance
// to be different.
if (test_subtypes || test_diff_subtypes || test_renew_subtypes) {
expecting_second_add = true;
err = DNSServiceRegister(&sdref, 0, 0, hnbuf, "_ipps._tcp,subtype",
0, 0, htons(service_port), txt_len, txt_data, register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("DNSServiceRegister failed: %d", err);
exit(1);
}
if (test_diff_subtypes) {
err = DNSServiceRegister(&sdref, 0, 0, hnbuf, "_second._tcp,othersub",
0, 0, htons(service_port), txt_len, txt_data, second_register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("DNSServiceRegister failed: %d", err);
exit(1);
}
} else if (!test_renew_subtypes) {
char shnbuf[132];
snprintf(shnbuf, sizeof(shnbuf), "foo-%s", hnbuf);
err = DNSServiceRegister(&sdref, 0, 0, shnbuf, "_ipps._tcp,othersub",
0, 0, htons(service_port), txt_len, txt_data, second_register_callback, client);
if (err != kDNSServiceErr_NoError) {
ERROR("DNSServiceRegister failed: %d", err);
exit(1);
}
}
}
}
if (use_thread_services) {
cti_get_service_list(NULL, &thread_service_context, NULL, cti_service_list_callback, NULL);
} else {
srp_network_state_stable(NULL);
}
ioloop();
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: