/* ioloop.h * * Copyright (c) 2018-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. * * Definitions for simple dispatch implementation. */ #ifndef __IOLOOP_H #define __IOLOOP_H #include #include #include #include #ifndef NSEC_PER_SEC #define NSEC_PER_SEC 1000000000ull #endif #ifndef NSEC_PER_MSEC #define NSEC_PER_MSEC 1000000ull #endif #ifndef MSEC_PER_SEC #define MSEC_PER_SEC (NSEC_PER_SEC / NSEC_PER_MSEC) #endif #ifndef IN_LINKLOCAL #define IN_LINKLOCAL(x) (((uint32_t)(x) & 0xffff0000) == 0xA9FE0000) // 169.254.* #endif #ifndef IN_LOOPBACK #define IN_LOOPBACK(x) (((uint32_t)(x) & 0xff000000) == 0x7f000000) // 127.* #endif #ifndef UDP_LISTENER_USES_CONNECTION_GROUPS #define UDP_LISTENER_USES_CONNECTION_GROUPS 0 #endif #ifndef __DSO_H typedef struct dso_state dso_state_t; #endif typedef union addr addr_t; union addr { struct sockaddr sa; struct sockaddr_in sin; struct sockaddr_in6 sin6; struct { char len; char family; int index; uint8_t addr[8]; } ether_addr; }; #define IOLOOP_NTOP(addr, buf) \ (((addr)->sa.sa_family == AF_INET || (addr)->sa.sa_family == AF_INET6) \ ? (inet_ntop((addr)->sa.sa_family, ((addr)->sa.sa_family == AF_INET \ ? (void *)&(addr)->sin.sin_addr \ : (void *)&(addr)->sin6.sin6_addr), buf, sizeof buf) != NULL) \ : snprintf(buf, sizeof buf, "Address type %d", (addr)->sa.sa_family)) struct message { int ref_count; #if !defined(IOLOOP_MACOS) || !UDP_LISTENER_USES_CONNECTION_GROUPS addr_t src; addr_t local; #endif int ifindex; uint16_t length; time_t received_time; // Only for SRP Replication, zero otherwise. uint32_t lease, key_lease; // For SRP replication, leases agreed to by original registrar dns_wire_t wire; }; typedef struct dso_transport comm_t; typedef struct io io_t; typedef struct subproc subproc_t; typedef struct wakeup wakeup_t; typedef struct dnssd_txn dnssd_txn_t; typedef struct interface_address_state interface_address_state_t; typedef struct ifpermit_list ifpermit_list_t; typedef void (*dnssd_txn_finalize_callback_t)(void *NONNULL context); typedef void (*dnssd_txn_failure_callback_t)(void *NONNULL context, int status); typedef void (*wakeup_callback_t)(void *NONNULL context); typedef void (*finalize_callback_t)(void *NONNULL context); typedef void (*cancel_callback_t)(comm_t *NONNULL comm, void *NONNULL context); typedef void (*ready_callback_t)(void *NONNULL context, uint16_t port); typedef void (*io_callback_t)(io_t *NONNULL io, void *NONNULL context); typedef void (*comm_callback_t)(comm_t *NONNULL comm); typedef void (*datagram_callback_t)(comm_t *NONNULL comm, message_t *NONNULL message, void *NULLABLE context); typedef void (*connect_callback_t)(comm_t *NONNULL connection, void *NULLABLE context); typedef void (*disconnect_callback_t)(comm_t *NONNULL comm, void *NULLABLE context, int error); enum interface_address_change { interface_address_added, interface_address_deleted, interface_address_unchanged }; typedef struct srp_server_state srp_server_t; typedef void (*interface_callback_t)(srp_server_t *NULLABLE server_state, void *NULLABLE context, const char *NONNULL name, const addr_t *NONNULL address, const addr_t *NONNULL netmask, uint32_t flags, enum interface_address_change event_type); typedef void (*subproc_callback_t)(void *NULLABLE context, int status, const char *NULLABLE error); typedef void (*tls_config_callback_t)(void *NONNULL context); typedef void (*async_callback_t)(void *NULLABLE context); typedef struct tls_context tls_context_t; #define IOLOOP_SECOND 1000LL #define IOLOOP_MINUTE 60 * IOLOOP_SECOND #define IOLOOP_HOUR 60 * IOLOOP_MINUTE #define IOLOOP_DAY 24 * IOLOOP_HOUR struct interface_address_state { interface_address_state_t *NULLABLE next; char *NONNULL name; addr_t addr; addr_t mask; uint32_t flags; }; struct io { int ref_count; io_t *NULLABLE next; io_callback_t NULLABLE read_callback; io_callback_t NULLABLE write_callback; finalize_callback_t NULLABLE finalize; finalize_callback_t NULLABLE io_finalize; finalize_callback_t NULLABLE context_release; void *NULLABLE context; io_t *NULLABLE cancel_on_close; io_callback_t NULLABLE ready; bool want_read : 1; bool want_write : 1; int fd; }; struct wakeup { int ref_count; wakeup_t *NULLABLE next; void *NULLABLE context; wakeup_callback_t NULLABLE wakeup; finalize_callback_t NULLABLE finalize; #ifdef IOLOOP_MACOS dispatch_source_t NULLABLE dispatch_source; #else int64_t wakeup_time; #endif }; struct dso_transport { #ifdef IOLOOP_MACOS nw_connection_t NULLABLE connection; nw_listener_t NULLABLE listener; #if UDP_LISTENER_USES_CONNECTION_GROUPS nw_connection_group_t NULLABLE connection_group; nw_content_context_t NULLABLE content_context; #endif nw_parameters_t NULLABLE parameters; comm_t *NULLABLE listener_state; int ref_count; int writes_pending; wakeup_t *NULLABLE idle_timer; // nw_connection objects aren't necessarily ready to write to immediately. But when we create an outgoing connection, we // typically want to write to it immediately. So we have a one-datum queue in case this happens; if the connection takes // so long to get ready that another write happens, we drop the first write. This will work okay for UDP connections, where // the retransmit logic is in the application. For future, we may want to rearchitect the flow so that the write is always // done in a callback. dispatch_data_t NULLABLE pending_write; #if !UDP_LISTENER_USES_CONNECTION_GROUPS io_t io; message_t *NULLABLE message; #endif #else io_t io; message_t *NULLABLE message; int multicast_ifindex; #endif uint16_t listen_port; uint16_t *NULLABLE avoid_ports; int num_avoid_ports; int serial; bool avoiding; char *NONNULL name; void *NULLABLE context; #ifdef SRP_TEST_SERVER void *NULLABLE test_context; bool (*NULLABLE test_send_intercept)(comm_t *NONNULL connection, message_t *NULLABLE responding_to, struct iovec *NONNULL iov, int iov_len, bool final, bool send_length); void *NULLABLE srp_server; #endif datagram_callback_t NULLABLE datagram_callback; comm_callback_t NULLABLE close_callback; connect_callback_t NULLABLE connected; disconnect_callback_t NULLABLE disconnected; finalize_callback_t NULLABLE finalize; cancel_callback_t NULLABLE cancel; ready_callback_t NULLABLE ready; uint8_t *NULLABLE buf; dso_state_t *NULLABLE dso; tls_context_t *NULLABLE tls_context; addr_t address, multicast, local; size_t message_length_len; size_t message_length, message_cur; uint8_t message_length_bytes[2]; #ifdef IOLOOP_MACOS bool read_pending: 1; // Only ever one. bool server: 1; // Indicates that this connection was created by a listener bool connection_ready: 1; bool final_data : 1; // Indicates that the next message written will be the final message, so send a FIN. #else bool tls_handshake_incomplete: 1; #endif // IOLOOP_MACOS bool tls_rotation_ready: 1; // Indicates if the listener should rotate its TLS certificate. bool tcp_stream: 1; bool is_multicast: 1; bool is_connected: 1; bool is_listener: 1; bool opportunistic: 1; bool canceled: 1; }; #define MAX_SUBPROC_ARGS 20 struct subproc { int ref_count; #ifdef IOLOOP_MACOS dispatch_source_t NULLABLE dispatch_source; #else subproc_t *NULLABLE next; #endif int pipe_fds[2]; io_t *NULLABLE output_fd; void *NULLABLE context; subproc_callback_t NONNULL callback; finalize_callback_t NULLABLE finalize; char *NULLABLE argv[MAX_SUBPROC_ARGS + 1]; int argc; pid_t pid; bool finished : 1; }; struct dnssd_txn { #ifndef IOLOOP_MACOS io_t *NULLABLE io; #endif int ref_count; DNSServiceRef NULLABLE sdref; void *NULLABLE context; void *NULLABLE aux_pointer; dnssd_txn_finalize_callback_t NULLABLE finalize_callback; dnssd_txn_failure_callback_t NULLABLE failure_callback; }; extern int64_t ioloop_now; int getipaddr(addr_t *NONNULL addr, const char *NONNULL p); int64_t ioloop_timenow(void); message_t *NULLABLE message_allocate(size_t message_size); void message_free(message_t *NONNULL message); void ioloop_close(io_t *NONNULL io); void ioloop_add_reader(io_t *NONNULL io, io_callback_t NONNULL callback); #define ioloop_wakeup_create() ioloop_wakeup_create_(__FILE__, __LINE__) wakeup_t *NULLABLE ioloop_wakeup_create_(const char *NONNULL file, int line); #define ioloop_wakeup_retain(wakeup) ioloop_wakeup_retain_(wakeup, __FILE__, __LINE__) void ioloop_wakeup_retain_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line); #define ioloop_wakeup_release(wakeup) ioloop_wakeup_release_(wakeup, __FILE__, __LINE__) #ifndef ioloop_wakeup_forget #define ioloop_wakeup_forget(ptr) _SRP_STRICT_DISPOSE_TEMPLATE(ptr, ioloop_wakeup_release) #endif void ioloop_wakeup_release_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line); bool ioloop_add_wake_event(wakeup_t *NONNULL wakeup, void *NULLABLE context, wakeup_callback_t NONNULL callback, finalize_callback_t NULLABLE finalize, int32_t milliseconds); void ioloop_cancel_wake_event(wakeup_t *NONNULL wakeup); bool ioloop_init(void); int ioloop(void); #define ioloop_comm_retain(comm) ioloop_comm_retain_(comm, __FILE__, __LINE__) void ioloop_comm_retain_(comm_t *NONNULL comm, const char *NONNULL file, int line); #define ioloop_comm_release(wakeup) ioloop_comm_release_(wakeup, __FILE__, __LINE__) void ioloop_comm_release_(comm_t *NONNULL comm, const char *NONNULL file, int line); void ioloop_comm_context_set(comm_t *NONNULL connection, void *NULLABLE context, finalize_callback_t NULLABLE callback); void ioloop_comm_connect_callback_set(comm_t *NONNULL comm, connect_callback_t NULLABLE callback); void ioloop_comm_disconnect_callback_set(comm_t *NONNULL comm, disconnect_callback_t NULLABLE callback); void ioloop_comm_cancel(comm_t *NONNULL comm); #define ioloop_listener_retain(comm) ioloop_listener_retain_(comm, __FILE__, __LINE__) void ioloop_listener_retain_(comm_t *NONNULL listener, const char *NONNULL file, int line); #define ioloop_listener_release(wakeup) ioloop_listener_release_(wakeup, __FILE__, __LINE__) void ioloop_listener_release_(comm_t *NONNULL listener, const char *NONNULL file, int line); void ioloop_listener_cancel(comm_t *NONNULL comm); comm_t *NULLABLE ioloop_listener_create(bool stream, bool tls, bool init_daemon, uint16_t *NULLABLE avoid_ports, int num_avoid_ports, const addr_t *NULLABLE ip_address, const char *NULLABLE multicast, const char *NONNULL name, datagram_callback_t NONNULL datagram_callback, connect_callback_t NULLABLE connected, cancel_callback_t NULLABLE cancel, ready_callback_t NULLABLE ready, finalize_callback_t NULLABLE finalize, tls_config_callback_t NULLABLE tls_config, unsigned ifindex, void *NULLABLE context); comm_t *NULLABLE ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream, bool stable, bool opportunistic, datagram_callback_t NONNULL datagram_callback, connect_callback_t NULLABLE connected, disconnect_callback_t NULLABLE disconnected, finalize_callback_t NULLABLE finalize, void *NULLABLE context); #define ioloop_message_create(x) ioloop_message_create_(x, __FILE__, __LINE__) message_t *NULLABLE ioloop_message_create_(size_t message_size, const char *NONNULL file, int line); #define ioloop_message_retain(wakeup) ioloop_message_retain_(wakeup, __FILE__, __LINE__) void ioloop_message_retain_(message_t *NONNULL message, const char *NONNULL file, int line); #define ioloop_message_release(wakeup) ioloop_message_release_(wakeup, __FILE__, __LINE__) void ioloop_message_release_(message_t *NONNULL message, const char *NONNULL file, int line); bool ioloop_send_multicast(comm_t *NONNULL comm, int ifindex, struct iovec *NONNULL iov, int iov_len); bool ioloop_send_message(comm_t *NONNULL connection, message_t *NULLABLE responding_to, struct iovec *NONNULL iov, int iov_len); bool ioloop_send_final_message(comm_t *NONNULL connection, message_t *NULLABLE responding_to, struct iovec *NONNULL iov, int iov_len); bool ioloop_send_data(comm_t *NONNULL connection, message_t *NULLABLE responding_to, struct iovec *NONNULL iov, int iov_len); bool ioloop_send_final_data(comm_t *NONNULL connection, message_t *NULLABLE responding_to, struct iovec *NONNULL iov, int iov_len); void ioloop_dump_object_allocation_stats(void); void ioloop_strcpy(char *NONNULL dest, const char *NONNULL src, size_t lim); bool ioloop_map_interface_addresses(srp_server_t *NULLABLE server_state, const char *NULLABLE ifname, void *NULLABLE context, interface_callback_t NULLABLE callback); #define ioloop_map_interface_addresses_here(server_state, here, ifname, context, callback) \ ioloop_map_interface_addresses_here_(server_state, here, ifname, context, callback, __FILE__, __LINE__) bool ioloop_map_interface_addresses_here_(srp_server_t *NULLABLE server_state, interface_address_state_t *NONNULL *NULLABLE here, const char *NULLABLE ifname, void *NULLABLE context, interface_callback_t NULLABLE callback, const char *NONNULL file, int line); ssize_t ioloop_recvmsg(int sock, uint8_t *NONNULL buffer, size_t buffer_length, int *NONNULL ifindex, int *NONNULL hoplimit, addr_t *NONNULL source, addr_t *NONNULL destination); #define ioloop_subproc_release(subproc) ioloop_subproc_release_(subproc, __FILE__, __LINE__) void ioloop_subproc_release_(subproc_t *NONNULL subproc, const char *NONNULL file, int line); #define ioloop_subproc_retain(subproc) ioloop_subproc_retain_(subproc, __FILE__, __LINE__) void ioloop_subproc_retain_(subproc_t *NONNULL subproc, const char *NONNULL file, int line); subproc_t *NULLABLE ioloop_subproc(const char *NONNULL exepath, char *NULLABLE *NONNULL argv, int argc, subproc_callback_t NULLABLE callback, io_callback_t NULLABLE output_callback, void *NULLABLE context); void ioloop_subproc_run_sync(subproc_t *NONNULL subproc); #define ioloop_dnssd_txn_add(ref, context, finalize_callback, failure_callback) \ ioloop_dnssd_txn_add_(ref, context, finalize_callback, failure_callback, __FILE__, __LINE__) dnssd_txn_t *NULLABLE ioloop_dnssd_txn_add_(DNSServiceRef NONNULL ref, void *NULLABLE context, dnssd_txn_finalize_callback_t NULLABLE callback, dnssd_txn_failure_callback_t NULLABLE failure_callback, const char *NONNULL file, int line); #define ioloop_dnssd_txn_add_subordinate(ref, context, finalize_callback, failure_callback) \ ioloop_dnssd_txn_add_subordinate_(ref, context, finalize_callback, failure_callback, __FILE__, __LINE__) dnssd_txn_t *NULLABLE ioloop_dnssd_txn_add_subordinate_(DNSServiceRef NONNULL ref, void *NULLABLE context, dnssd_txn_finalize_callback_t NULLABLE callback, dnssd_txn_failure_callback_t NULLABLE failure_callback, const char *NONNULL file, int line); void ioloop_dnssd_txn_cancel(dnssd_txn_t *NONNULL txn); #define ioloop_dnssd_txn_retain(txn) ioloop_dnssd_txn_retain_(txn, __FILE__, __LINE__) void ioloop_dnssd_txn_retain_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line); #define ioloop_dnssd_txn_release(txn) ioloop_dnssd_txn_release_(txn, __FILE__, __LINE__) #ifndef ioloop_dnssd_txn_forget #define ioloop_dnssd_txn_forget(ptr) _SRP_STRICT_DISPOSE_TEMPLATE(ptr, ioloop_dnssd_txn_release) #endif void ioloop_dnssd_txn_release_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line); #endif void ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *NULLABLE aux_pointer); void *NULLABLE ioloop_dnssd_txn_get_aux_pointer(dnssd_txn_t *NONNULL txn); void *NULLABLE ioloop_dnssd_txn_get_context(dnssd_txn_t *NONNULL txn); #define ioloop_file_descriptor_create(fd, context, finalize) \ ioloop_file_descriptor_create_(fd, context, finalize, __FILE__, __LINE__) io_t *NULLABLE ioloop_file_descriptor_create_(int fd, void *NULLABLE context, finalize_callback_t NULLABLE finalize, const char *NONNULL file, int line); #define ioloop_file_descriptor_retain(file_descriptor) ioloop_file_descriptor_retain_(file_descriptor, __FILE__, \ __LINE__) void ioloop_file_descriptor_retain_(io_t *NONNULL file_descriptor, const char *NONNULL file, int line); #define ioloop_file_descriptor_release(file_descriptor) ioloop_file_descriptor_release_(file_descriptor, __FILE__, \ __LINE__) void ioloop_file_descriptor_release_(io_t *NONNULL file_descriptor, const char *NONNULL file, int line); bool ioloop_interface_monitor_start(void); void ioloop_run_async(async_callback_t NULLABLE callback, void *NULLABLE context); bool srp_load_file_data(void *NULLABLE host_context, const char *NONNULL filename, uint8_t *NONNULL buffer, uint16_t *NONNULL length, uint16_t buffer_size); bool srp_store_file_data(void *NULLABLE host_context, const char *NONNULL filename, uint8_t *NONNULL buffer, uint16_t length); time_t srp_time(void); double srp_fractional_time(void); int64_t srp_utime(void); void srp_format_time_offset(char *NONNULL buf, size_t buf_len, time_t offset); const struct sockaddr *NULLABLE connection_get_local_address(message_t *NULLABLE message); #if !UDP_LISTENER_USES_CONNECTION_GROUPS bool ioloop_udp_send_message(comm_t *NONNULL comm, addr_t *NULLABLE source, addr_t *NONNULL dest, int ifindex, struct iovec *NONNULL iov, int iov_len); void ioloop_udp_read_callback(io_t *NONNULL io, void *NULLABLE context); #endif int get_num_fds(void); // Local Variables: // mode: C // tab-width: 4 // c-file-style: "bsd" // c-basic-offset: 4 // fill-column: 108 // indent-tabs-mode: nil // End: