blob: bea9f4d22c4eeb076de0ab077ff54e5e1126ac5c [file] [log] [blame]
/* adv-ctl-proxy.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.
*
* This file contains the SRP Advertising Proxy control interface, which allows clients to control the advertising proxy
* and discover information about its internal state. This is largely used for testing.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <time.h>
#include <dns_sd.h>
#include <net/if.h>
#include <inttypes.h>
#include <sys/resource.h>
#include "srp.h"
#include "dns-msg.h"
#include "ioloop.h"
#include "srp-gw.h"
#include "srp-proxy.h"
#include "srp-mdns-proxy.h"
#include "cti-services.h"
#include "route.h"
#include "adv-ctl-server.h"
#include "srp-replication.h"
#include "dnssd-proxy.h"
#include "cti-proto.h"
#include "adv-ctl-common.h"
#include "advertising_proxy_services.h"
static int
adv_ctl_block_service(bool enable, void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if THREAD_BORDER_ROUTER
srp_server_t *server_state = context;
if (enable) {
if (server_state->route_state->srp_listener != NULL) {
srp_proxy_listener_cancel(server_state->route_state->srp_listener);
server_state->route_state->srp_listener = NULL;
} else {
status = kDNSSDAdvertisingProxyStatus_UnknownErr;
}
} else {
if (server_state->route_state->srp_listener == NULL) {
partition_start_srp_listener(server_state->route_state);
} else {
status = kDNSSDAdvertisingProxyStatus_UnknownErr;
}
}
#else
(void)enable;
(void)context;
#endif // THREAD_BORDER_ROUTER
return status;
}
static bool
adv_ctl_regenerate_ula(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
srp_server_t *server_state = context;
#if THREAD_BORDER_ROUTER
partition_stop_advertising_pref_id(server_state->route_state);
infrastructure_network_shutdown(server_state->route_state);
route_ula_generate(server_state->route_state);
infrastructure_network_startup(server_state->route_state);
#endif
return status;
}
static int
adv_ctl_advertise_prefix(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
srp_server_t *server_state = context;
#if THREAD_BORDER_ROUTER
partition_publish_my_prefix(server_state->route_state);
#endif
return status;
}
static int
adv_ctl_prefix_add_remove(void *context, xpc_object_t request, bool add)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
srp_server_t *server_state = context;
const uint8_t *data;
size_t data_len;
data = xpc_dictionary_get_data(request, "data", &data_len);
if (data != NULL && data_len == 16) {
SEGMENTED_IPv6_ADDR_GEN_SRP(data, prefix_buf);
INFO("got prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(data, prefix_buf));
#if THREAD_BORDER_ROUTER
if (add) {
adv_ctl_add_prefix(server_state->route_state, data);
} else {
adv_ctl_remove_prefix(server_state->route_state, data);
}
#endif
} else {
ERROR("invalid request, data[%p], data_len[%ld]", data, data_len);
status = kDNSSDAdvertisingProxyStatus_BadParam;
}
return status;
}
static int
adv_ctl_stop_advertising_service(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
srp_server_t *server_state = context;
#if THREAD_BORDER_ROUTER
partition_discontinue_srp_service(server_state->route_state);
#endif
return status;
}
static int
adv_ctl_disable_replication(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if SRP_FEATURE_REPLICATION
srp_server_t *server_state = context;
srpl_disable(server_state);
#else
(void)context;
#endif
return status;
}
static int
adv_ctl_drop_srpl_connection(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if SRP_FEATURE_REPLICATION
srp_server_t *server_state = context;
srpl_drop_srpl_connection(server_state);
#else
(void)context;
#endif
return status;
}
static int
adv_ctl_undrop_srpl_connection(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if SRP_FEATURE_REPLICATION
srp_server_t *server_state = context;
srpl_undrop_srpl_connection(server_state);
#else
(void)context;
#endif
return status;
}
static int
adv_ctl_drop_srpl_advertisement(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if SRP_FEATURE_REPLICATION
srp_server_t *server_state = context;
srpl_drop_srpl_advertisement(server_state);
#else
(void)context;
#endif
return status;
}
static int
adv_ctl_undrop_srpl_advertisement(void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
#if SRP_FEATURE_REPLICATION
srp_server_t *server_state = context;
srpl_undrop_srpl_advertisement(server_state);
#else
(void)context;
#endif
return status;
}
static void
adv_ctl_fd_finalize(void *context)
{
advertising_proxy_conn_ref connection = context;
connection->io_context = NULL;
RELEASE_HERE(connection, cti_connection_finalize);
}
static bool
adv_ctl_list_services(advertising_proxy_conn_ref connection, void *context)
{
srp_server_t *server_state = context;
adv_host_t *host;
int i;
int64_t now = ioloop_timenow();
int num_hosts = 0;
for (host = hosts; host != NULL; host = host->next) {
num_hosts++;
}
if (!cti_connection_message_create(connection, kDNSSDAdvertisingProxyResponse, 200) ||
!cti_connection_u32_put(connection, (uint32_t)kDNSSDAdvertisingProxyStatus_NoError) ||
!cti_connection_u32_put(connection, num_hosts))
{
ERROR("adv_ctl_list_services: error starting response");
cti_connection_close(connection);
return false;
}
for (host = server_state->hosts; host != NULL; host = host->next) {
int num_addresses = 0;
int num_instances = 0;
if (!cti_connection_string_put(connection, host->name) ||
!cti_connection_string_put(connection, host->registered_name) ||
!cti_connection_u32_put(connection, host->lease_expiry >= now ? host->lease_expiry - now : 0) ||
!cti_connection_bool_put(connection, host->removed) ||
!cti_connection_u64_put(connection, host->update_server_id))
{
ERROR("adv_ctl_list_services: unable to write host info for host %s", host->name);
cti_connection_close(connection);
return false;
}
cti_connection_u64_put(connection, host->server_stable_id);
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
if (host->addresses->vec[i] != NULL) {
num_addresses++;
}
}
}
cti_connection_u16_put(connection, num_addresses);
if (host->addresses != NULL) {
for (i = 0; i < host->addresses->num; i++) {
if (host->addresses->vec[i] != NULL) {
if (!cti_connection_u16_put(connection, host->addresses->vec[i]->rrtype) ||
!cti_connection_data_put(connection, host->addresses->vec[i]->rdata, host->addresses->vec[i]->rdlen))
{
ERROR("adv_ctl_list_services: unable to write address %d for host %s", i, host->name);
cti_connection_close(connection);
return false;
}
}
}
}
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
if (host->instances->vec[i] != NULL) {
num_instances++;
}
}
}
cti_connection_u16_put(connection, num_instances);
if (host->instances != NULL) {
for (i = 0; i < host->instances->num; i++) {
adv_instance_t *instance = host->instances->vec[i];
if (instance != NULL) {
if (!cti_connection_string_put(connection, instance->instance_name) ||
!cti_connection_string_put(connection, instance->service_type) ||
!cti_connection_u16_put(connection, instance->port) ||
!cti_connection_data_put(connection, instance->txt_data, instance->txt_length))
{
ERROR("adv_ctl_list_services: unable to write address %d for host %s", i, host->name);
cti_connection_close(connection);
return false;
}
}
}
}
}
return cti_connection_message_send(connection);
}
static bool
adv_ctl_get_ula(advertising_proxy_conn_ref connection, void *context)
{
srp_server_t *server_state = context;
if (!cti_connection_message_create(connection, kDNSSDAdvertisingProxyResponse, 200) ||
!cti_connection_u32_put(connection, (uint32_t)kDNSSDAdvertisingProxyStatus_NoError))
{
ERROR("error starting response");
cti_connection_close(connection);
return false;
}
// Copy out just the global ID part of the ULA prefix.
uint64_t ula = 0;
for (int j = 1; j < 6; j++) {
ula = ula << 8 | (((uint8_t *)&server_state->ula_prefix)[j]);
}
if (!cti_connection_u64_put(connection, ula)) {
ERROR("error sending ula");
cti_connection_close(connection);
return false;
}
return cti_connection_message_send(connection);
}
static void
adv_ctl_message_parse(advertising_proxy_conn_ref connection, void *context)
{
int status = kDNSSDAdvertisingProxyStatus_NoError;
cti_connection_parse_start(connection);
if (!cti_connection_u16_parse(connection, &connection->message_type)) {
return;
}
switch(connection->message_type) {
case kDNSSDAdvertisingProxyEnable:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyEnable request.",
connection->uid, connection->gid);
break;
case kDNSSDAdvertisingProxyListServiceTypes:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyListServiceTypes request.",
connection->uid, connection->gid);
break;
case kDNSSDAdvertisingProxyListServices:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyListServices request.",
connection->uid, connection->gid);
adv_ctl_list_services(connection, context);
return;
case kDNSSDAdvertisingProxyListHosts:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyListHosts request.",
connection->uid, connection->gid);
break;
case kDNSSDAdvertisingProxyGetHost:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyGetHost request.",
connection->uid, connection->gid);
break;
case kDNSSDAdvertisingProxyFlushEntries:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyFlushEntries request.",
connection->uid, connection->gid);
srp_mdns_flush(context);
break;
case kDNSSDAdvertisingProxyBlockService:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyBlockService request.",
connection->uid, connection->gid);
adv_ctl_block_service(true, context);
break;
case kDNSSDAdvertisingProxyUnblockService:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyUnblockService request.",
connection->uid, connection->gid);
adv_ctl_block_service(false, context);
break;
case kDNSSDAdvertisingProxyRegenerateULA:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyRegenerateULA request.",
connection->uid, connection->gid);
adv_ctl_regenerate_ula(context);
break;
case kDNSSDAdvertisingProxyAdvertisePrefix:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyAdvertisePrefix request.",
connection->uid, connection->gid);
adv_ctl_advertise_prefix(context);
break;
case kDNSSDAdvertisingProxyAddPrefix:
void *data = NULL;
uint16_t data_len;
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyAddPrefix request.",
connection->uid, connection->gid);
if (!cti_connection_data_parse(connection, &data, &data_len)) {
ERROR("faile to parse data for kDNSSDAdvertisingProxyAddPrefix request.");
status = kDNSSDAdvertisingProxyStatus_BadParam;
} else {
if (data != NULL && data_len == 16) {
SEGMENTED_IPv6_ADDR_GEN_SRP(data, prefix_buf);
INFO("got prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(data, prefix_buf));
status = adv_ctl_add_prefix(context, data);
} else {
ERROR("invalid add prefix request, data[%p], data_len[%ld]", data, data_len);
status = kDNSSDAdvertisingProxyStatus_BadParam;
}
}
break;
case kDNSSDAdvertisingProxyRemovePrefix:
void *data = NULL;
uint16_t data_len;
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyRemovePrefix request.",
connection->uid, connection->gid);
if (!cti_connection_data_parse(connection, &data, &data_len)) {
ERROR("faile to parse data for kDNSSDAdvertisingProxyRemovePrefix request.");
status = kDNSSDAdvertisingProxyStatus_BadParam;
} else {
if (data != NULL && data_len == 16) {
SEGMENTED_IPv6_ADDR_GEN_SRP(data, prefix_buf);
INFO("got prefix " PRI_SEGMENTED_IPv6_ADDR_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(data, prefix_buf));
status = adv_ctl_add_prefix(context, data);
} else {
ERROR("invalid add prefix request, data[%p], data_len[%ld]", data, data_len);
status = kDNSSDAdvertisingProxyStatus_BadParam;
}
}
break;
case kDNSSDAdvertisingProxyStop:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyStop request.",
connection->uid, connection->gid);
adv_ctl_stop_advertising_service(context);
break;
case kDNSSDAdvertisingProxyGetULA:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyULA request.",
connection->uid, connection->gid);
adv_ctl_get_ula(connection, context);
break;
case kDNSSDAdvertisingProxyDisableReplication:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyDisableReplication request.",
connection->uid, connection->gid);
adv_ctl_disable_replication(context);
break;
case kDNSSDAdvertisingProxyDropSrplConnection:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyDropSrplConnection request.",
connection->uid, connection->gid);
adv_ctl_drop_srpl_connection(context);
break;
case kDNSSDAdvertisingProxyUndropSrplConnection:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyUndropSrplConnection request.",
connection->uid, connection->gid);
adv_ctl_undrop_srpl_connection(context);
break;
case kDNSSDAdvertisingProxyDropSrplAdvertisement:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyDropSrplAdvertisement request.",
connection->uid, connection->gid);
adv_ctl_drop_srpl_advertisement(context);
break;
case kDNSSDAdvertisingProxyUndropSrplAdvertisement:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyUndropSrplAdvertisement request.",
connection->uid, connection->gid);
adv_ctl_undrop_srpl_advertisement(context);
break;
case kDNSSDAdvertisingProxyStartDroppingPushConnections:
INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyStartDroppingPushConnections request.",
connection->uid, connection->gid);
dp_start_smashing();
break;
default:
ERROR("Client uid %d pid %d sent a request with unknown message type %d.",
connection->uid, connection->gid, connection->message_type);
status = kDNSSDAdvertisingProxyStatus_Invalid;
break;
}
cti_send_response(connection, status);
cti_connection_close(connection);
}
static void
adv_ctl_read_callback(io_t *UNUSED io, void *context)
{
advertising_proxy_conn_ref connection = context;
cti_read(connection, adv_ctl_message_parse);
}
static void
adv_ctl_listen_callback(io_t *UNUSED io, void *context)
{
srp_server_t *server_state = context;
uid_t uid;
gid_t gid;
pid_t pid;
int fd = cti_accept(server_state->adv_ctl_listener->fd, &uid, &gid, &pid);
if (fd < 0) {
return;
}
advertising_proxy_conn_ref connection = cti_connection_allocate(500);
if (connection == NULL) {
ERROR("cti_listen_callback: no memory for connection.");
close(fd);
return;
}
RETAIN_HERE(connection);
connection->fd = fd;
connection->uid = uid;
connection->gid = gid;
connection->pid = pid;
connection->io_context = ioloop_file_descriptor_create(connection->fd, connection, adv_ctl_fd_finalize);
if (connection->io_context == NULL) {
ERROR("cti_listen_callback: no memory for io context.");
close(fd);
RELEASE_HERE(connection, cti_connection_finalize);
return;
}
ioloop_add_reader(connection->io_context, adv_ctl_read_callback);
connection->context = context;
connection->callback.callback = NULL;
connection->internal_callback = NULL;
return;
}
static int
adv_ctl_listen(srp_server_t *server_state)
{
int fd = cti_make_unix_socket(ADV_CTL_SERVER_SOCKET_NAME, sizeof(ADV_CTL_SERVER_SOCKET_NAME), true);
if (fd < 0) {
int ret = (errno == ECONNREFUSED
? kDNSSDAdvertisingProxyStatus_DaemonNotRunning
: errno == EPERM ? kDNSSDAdvertisingProxyStatus_NotPermitted : kDNSSDAdvertisingProxyStatus_UnknownErr);
ERROR("adv_ctl_listener: socket: %s", strerror(errno));
return ret;
}
server_state->adv_ctl_listener = ioloop_file_descriptor_create(fd, server_state, NULL);
if (server_state->listener == NULL) {
ERROR("adv_ctl_listener: no memory for io_t object.");
close(fd);
return kDNSSDAdvertisingProxyStatus_NoMemory;
}
RETAIN_HERE(server_state->adv_ctl_listener);
ioloop_add_reader(server_state->adv_ctl_listener, adv_ctl_listen_callback);
return kDNSSDAdvertisingProxyStatus_NoError;
}
bool
adv_ctl_init(void *context)
{
srp_server_t *server_state = context;
return adv_ctl_listen(server_state);
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: