| /* advertising_proxy_services.h |
| * |
| * Copyright (c) 2020-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 implementation of the SRP Advertising Proxy management |
| * API on MacOS, which is private API used to control and manage the advertising |
| * proxy. |
| */ |
| |
| |
| |
| #include <errno.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <netinet/in.h> |
| #include <stdlib.h> |
| |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "ioloop.h" |
| #include "cti-proto.h" |
| |
| #include "adv-ctl-common.h" |
| #include "advertising_proxy_services.h" |
| |
| static void |
| adv_host_finalize(advertising_proxy_host_t *host) |
| { |
| int i; |
| |
| free(host->hostname); |
| free(host->regname); |
| |
| if (host->addresses != NULL) { |
| for (i = 0; i < host->num_addresses; i++) { |
| free(host->addresses[i].rdata); |
| } |
| free(host->addresses); |
| } |
| if (host->instances != NULL) { |
| for (i = 0; i < host->num_instances; i++) { |
| free(host->instances[i].instance_name); |
| free(host->instances[i].service_type); |
| free(host->instances[i].txt_data); |
| } |
| } |
| free(host); |
| } |
| |
| #define adv_host_allocate() adv_host_allocate_(__FILE__, __LINE__) |
| static advertising_proxy_host_t * |
| adv_host_allocate_(const char *file, int line) |
| { |
| advertising_proxy_host_t *host = calloc(1, sizeof(*host)); |
| if (host == NULL) { |
| return host; |
| } |
| RETAIN(host); |
| return host; |
| } |
| |
| |
| |
| static void |
| adv_fd_finalize(void *context) |
| { |
| advertising_proxy_conn_ref connection = context; |
| connection->io_context = NULL; |
| RELEASE_HERE(connection, cti_connection_finalize); |
| } |
| |
| void |
| advertising_proxy_ref_dealloc(advertising_proxy_conn_ref conn_ref) |
| { |
| if (conn_ref == NULL) { |
| ERROR("advertising_proxy_ref_dealloc called with NULL advertising_proxy_conn_ref"); |
| return; |
| } |
| conn_ref->callback.callback = NULL; |
| cti_connection_close(conn_ref); |
| |
| // This is releasing the caller's reference. We may still have an internal reference. |
| RELEASE_HERE(conn_ref, cti_connection_finalize); |
| ERROR("advertising_proxy_ref_dealloc successfully released conn_ref"); |
| } |
| |
| static void |
| adv_message_parse(cti_connection_t connection) |
| { |
| int err = kDNSSDAdvertisingProxyStatus_NoError; |
| int32_t status; |
| |
| cti_connection_parse_start(connection); |
| if (!cti_connection_u16_parse(connection, &connection->message_type)) { |
| err = kDNSSDAdvertisingProxyStatus_Disconnected; |
| goto out; |
| } |
| if (connection->message_type != kDNSSDAdvertisingProxyResponse) { |
| syslog(LOG_ERR, "adv_message_parse: unexpected message type %d", connection->message_type); |
| err = kDNSSDAdvertisingProxyStatus_Disconnected; |
| goto out; |
| } |
| if (!cti_connection_u32_parse(connection, (uint32_t *)&status)) { |
| err = kDNSSDAdvertisingProxyStatus_Disconnected; |
| goto out; |
| } |
| if (status != kDNSSDAdvertisingProxyStatus_NoError) { |
| goto out; |
| } |
| if (connection->internal_callback != NULL) { |
| (*connection->internal_callback)(connection, NULL, status); |
| } else { |
| if (!cti_connection_parse_done(connection)) { |
| err = kDNSSDAdvertisingProxyStatus_Disconnected; |
| goto out; |
| } |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, NULL, status); |
| } |
| } |
| return; |
| out: |
| cti_connection_close(connection); |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, NULL, err); |
| } |
| return; |
| } |
| |
| static void |
| adv_read_callback(io_t *UNUSED io, void *context) |
| { |
| cti_connection_t connection = context; |
| |
| cti_read(connection, adv_message_parse); |
| } |
| |
| static void |
| adv_service_list_callback(cti_connection_t connection, void *UNUSED object, cti_status_t UNUSED status) |
| { |
| int i; |
| advertising_proxy_host_t *host = NULL; |
| uint32_t num_hosts; |
| |
| if (!cti_connection_u32_parse(connection, &num_hosts)) { |
| ERROR("adv_ctl_list_callback: error parsing host count"); |
| goto fail; |
| } |
| for (i = 0; i < num_hosts; i++) { |
| host = adv_host_allocate(); |
| if (host == NULL) { |
| ERROR("adv_ctl_list_callback: no memory for host object"); |
| cti_connection_close(connection); |
| goto fail; |
| } |
| if (!cti_connection_string_parse(connection, &host->hostname) || |
| !cti_connection_string_parse(connection, &host->regname) || |
| !cti_connection_u32_parse(connection, &host->lease_time) || |
| !cti_connection_bool_parse(connection, &host->removed) || |
| !cti_connection_u64_parse(connection, &host->server_id)) |
| { |
| ERROR("adv_ctl_list_callback: unable to parse host info for host %d", i); |
| cti_connection_close(connection); |
| goto fail; |
| } |
| |
| if (!cti_connection_u16_parse(connection, &host->num_addresses)) { |
| ERROR("adv_ctl_list_callback: unable to parse host address count for host %s", host->hostname); |
| cti_connection_close(connection); |
| goto fail; |
| } |
| if (host->num_addresses > 0) { |
| host->addresses = calloc(host->num_addresses, sizeof(advertising_proxy_host_address_t)); |
| if (host->addresses == NULL) { |
| ERROR("adv_ctl_list_callback: no memory for addresses for host %s", host->hostname); |
| goto fail; |
| } |
| } |
| for (i = 0; i < host->num_addresses; i++) { |
| if (!cti_connection_u16_parse(connection, &host->addresses[i].rrtype) || |
| !cti_connection_data_parse(connection, (void **)&host->addresses[i].rdata, &host->addresses[i].rdlen)) |
| { |
| ERROR("adv_ctl_list_callback: unable to parse address %d for host %s", i, host->hostname); |
| goto fail; |
| } |
| } |
| |
| if (!cti_connection_u64_parse(connection, &host->server_id)) { |
| ERROR("adv_ctl_list_callback: unable to parse stable server ID for host %s", host->hostname); |
| goto fail; |
| } |
| |
| if (!cti_connection_u16_parse(connection, &host->num_instances)) { |
| ERROR("adv_ctl_list_callback: unable to parse host address count for host %s", host->hostname); |
| cti_connection_close(connection); |
| goto fail; |
| } |
| if (host->num_instances > 0) { |
| host->instances = calloc(host->num_instances, sizeof(advertising_proxy_instance_t)); |
| if (host->instances == NULL) { |
| ERROR("adv_ctl_list_callback: no memory for instances for host %s", host->hostname); |
| goto fail; |
| } |
| } |
| for (i = 0; i < host->num_instances; i++) { |
| if (!cti_connection_string_parse(connection, &host->instances[i].instance_name) || |
| !cti_connection_string_parse(connection, &host->instances[i].service_type) || |
| !cti_connection_u16_parse(connection, &host->instances[i].port) || |
| !cti_connection_data_parse(connection, (void **)&host->instances[i].txt_data, &host->instances[i].txt_len)) |
| { |
| ERROR("adv_ctl_list_callback: unable to write address %d for host %s", i, host->hostname); |
| goto fail; |
| } |
| } |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, host, kDNSSDAdvertisingProxyStatus_NoError); |
| } |
| RELEASE_HERE(host, adv_host_finalize); |
| host = NULL; |
| } |
| |
| if (!cti_connection_parse_done(connection)) { |
| fail: |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, NULL, kDNSSDAdvertisingProxyStatus_Disconnected); |
| } |
| } else { |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, NULL, kDNSSDAdvertisingProxyStatus_NoError); |
| } |
| } |
| if (host != NULL) { |
| RELEASE_HERE(host, adv_host_finalize); |
| } |
| } |
| |
| static void |
| adv_ula_callback(cti_connection_t connection, void *UNUSED object, cti_status_t UNUSED status) |
| { |
| uint64_t ula_prefix; |
| |
| if (!cti_connection_u64_parse(connection, &ula_prefix)) { |
| ERROR("error parsing ula prefix"); |
| goto fail; |
| } |
| |
| if (!cti_connection_parse_done(connection)) { |
| fail: |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, NULL, kDNSSDAdvertisingProxyStatus_Disconnected); |
| } |
| } else { |
| if (connection->callback.reply != NULL) { |
| connection->callback.reply(connection, &ula_prefix, kDNSSDAdvertisingProxyStatus_NoError); |
| } |
| } |
| } |
| |
| #define adv_send_command(ref, client_queue, command_name, command, app_callback, internal_callback, allocation) \ |
| adv_send_command_(ref, client_queue, command_name, command, app_callback, internal_callback, allocation, __FILE__, __LINE__) |
| static advertising_proxy_error_type |
| adv_send_command_(advertising_proxy_conn_ref *ref, run_context_t client_queue, const char *command_name, int command, |
| advertising_proxy_reply app_callback, cti_internal_callback_t internal_callback, size_t allocation, |
| const char *file, int line) |
| { |
| int fd; |
| cti_connection_t connection; |
| |
| fd = cti_make_unix_socket(ADV_CTL_SERVER_SOCKET_NAME, sizeof(ADV_CTL_SERVER_SOCKET_NAME), false); |
| if (fd < 0) { |
| return kDNSSDAdvertisingProxyStatus_DaemonNotRunning; |
| } |
| connection = cti_connection_allocate(allocation); |
| if (connection == NULL) { |
| close(fd); |
| return kDNSSDAdvertisingProxyStatus_NoMemory; |
| } |
| connection->fd = fd; |
| RETAIN(connection); |
| |
| connection->io_context = ioloop_file_descriptor_create(connection->fd, connection, adv_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 kDNSSDAdvertisingProxyStatus_NoMemory; |
| } |
| ioloop_add_reader(connection->io_context, adv_read_callback); |
| connection->callback.reply = app_callback; |
| connection->internal_callback = internal_callback; |
| cti_connection_message_create(connection, command, 2); |
| cti_connection_message_send(connection); |
| *ref = connection; |
| return kDNSSDAdvertisingProxyStatus_NoError; |
| } |
| |
| |
| advertising_proxy_error_type |
| advertising_proxy_flush_entries(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_flush_entries", |
| kDNSSDAdvertisingProxyFlushEntries, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_get_service_list(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_get_service_list", |
| kDNSSDAdvertisingProxyListServices, callback, adv_service_list_callback, 4096); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_block_service(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_block_service", |
| kDNSSDAdvertisingProxyBlockService, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_unblock_service(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_unblock_service", |
| kDNSSDAdvertisingProxyUnblockService, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_regenerate_ula(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_regenerate_ula", |
| kDNSSDAdvertisingProxyRegenerateULA, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_advertise_prefix(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_advertise_prefix", |
| kDNSSDAdvertisingProxyAdvertisePrefix, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_add_prefix(advertising_proxy_conn_ref *conn_ref, run_context_t client_queue, |
| advertising_proxy_reply callback, const uint8_t *prefix_buf, size_t buf_len) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command_with_data(conn_ref, client_queue, "advertising_proxy_add_prefix", |
| kDNSSDAdvertisingProxyAddPrefix, callback, NULL, 0, |
| prefix_buf, buf_len); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_remove_prefix(advertising_proxy_conn_ref *conn_ref, run_context_t client_queue, |
| advertising_proxy_reply callback, const uint8_t *prefix_buf, size_t buf_len) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command_with_data(conn_ref, client_queue, "advertising_proxy_remove_prefix", |
| kDNSSDAdvertisingProxyRemovePrefix, callback, NULL, 0, |
| prefix_buf, buf_len); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_stop(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_stop", |
| kDNSSDAdvertisingProxyStop, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_get_ula(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_get_ula", |
| kDNSSDAdvertisingProxyGetULA, callback, adv_ula_callback, 128); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_disable_srp_replication(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_disable_srp_replication", |
| kDNSSDAdvertisingProxyDisableReplication, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_drop_srpl_connection(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_drop_srpl_connection", |
| kDNSSDAdvertisingProxyDropSrplConnection, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_undrop_srpl_connection(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_undrop_srpl_connection", |
| kDNSSDAdvertisingProxyUndropSrplConnection, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_drop_srpl_advertisement(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_drop_srpl_advertisement", |
| kDNSSDAdvertisingProxyDropSrplAdvertisement, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_undrop_srpl_advertisement(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_disable_undrop_srpl_advertisement", |
| kDNSSDAdvertisingProxyUndropSrplAdvertisement, callback, NULL, 0); |
| return errx; |
| } |
| |
| advertising_proxy_error_type |
| advertising_proxy_start_dropping_push_connections(advertising_proxy_conn_ref *conn_ref, |
| run_context_t client_queue, advertising_proxy_reply callback) |
| { |
| advertising_proxy_error_type errx; |
| errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_disable_start_dropping_push_connections", |
| kDNSSDAdvertisingProxyStartDroppingPushConnections, callback, NULL, 0); |
| return errx; |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 120 |
| // indent-tabs-mode: nil |
| // End: |