Squashed 'third_party/rawrtc/rawrtc/' content from commit aa3ae4b24
Change-Id: I38a655a4259b62f591334e90a1315bd4e7e4d8ec
git-subtree-dir: third_party/rawrtc/rawrtc
git-subtree-split: aa3ae4b247275cc6e69c30613b3a4ba7fdc82d1b
diff --git a/src/ice_gatherer/attributes.c b/src/ice_gatherer/attributes.c
new file mode 100644
index 0000000..92c6fb5
--- /dev/null
+++ b/src/ice_gatherer/attributes.c
@@ -0,0 +1,19 @@
+#include "gatherer.h"
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtcc/code.h>
+
+/*
+ * Get the current state of an ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_state(
+ enum rawrtc_ice_gatherer_state* const statep, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!statep || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Set state
+ *statep = gatherer->state;
+ return RAWRTC_CODE_SUCCESS;
+}
diff --git a/src/ice_gatherer/gatherer.c b/src/ice_gatherer/gatherer.c
new file mode 100644
index 0000000..51002fe
--- /dev/null
+++ b/src/ice_gatherer/gatherer.c
@@ -0,0 +1,1073 @@
+#include "gatherer.h"
+#include "../ice_candidate/candidate.h"
+#include "../ice_candidate/helper.h"
+#include "../ice_gather_options/options.h"
+#include "../ice_server/address.h"
+#include "../ice_server/resolver.h"
+#include "../ice_server/server.h"
+#include "../main/config.h"
+#include <rawrtc/config.h>
+#include <rawrtc/ice_candidate.h>
+#include <rawrtc/ice_gather_options.h>
+#include <rawrtc/ice_gatherer.h>
+#include <rawrtc/ice_parameters.h>
+#include <rawrtc/main.h>
+#include <rawrtcc/code.h>
+#include <rawrtcc/message_buffer.h>
+#include <rawrtcc/utils.h>
+#include <re.h>
+#include <rew.h>
+#include <string.h> // memcpy
+#include <sys/socket.h> // AF_INET, AF_INET6
+
+#define DEBUG_MODULE "ice-gatherer"
+//#define RAWRTC_DEBUG_MODULE_LEVEL 7 // Note: Uncomment this to debug this module only
+#define RAWRTC_DEBUG_ICE_GATHERER 0 // TODO: Remove
+#include <rawrtcc/debug.h>
+
+/*
+ * Destructor for an existing ICE gatherer.
+ */
+static void rawrtc_ice_gatherer_destroy(void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+
+ // Close gatherer
+ // TODO: Check effects in case transport has been destroyed due to error in create
+ rawrtc_ice_gatherer_close(gatherer);
+
+ // Un-reference
+ mem_deref(gatherer->dns_client);
+ mem_deref(gatherer->ice);
+ list_flush(&gatherer->local_candidates);
+ list_flush(&gatherer->buffered_messages);
+ list_flush(&gatherer->url_resolvers);
+ list_flush(&gatherer->url_addresses);
+ mem_deref(gatherer->options);
+}
+
+/*
+ * Create a new ICE gatherer.
+ * `*gathererp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_create(
+ struct rawrtc_ice_gatherer** const gathererp, // de-referenced
+ struct rawrtc_ice_gather_options* const options, // referenced
+ rawrtc_ice_gatherer_state_change_handler const state_change_handler, // nullable
+ rawrtc_ice_gatherer_error_handler const error_handler, // nullable
+ rawrtc_ice_gatherer_local_candidate_handler const local_candidate_handler, // nullable
+ void* const arg // nullable
+) {
+ struct rawrtc_ice_gatherer* gatherer;
+ int err;
+ struct sa dns_servers[RAWRTC_ICE_GATHERER_DNS_SERVERS] = {0};
+ uint32_t n_dns_servers = ARRAY_SIZE(dns_servers);
+ uint32_t i;
+
+ // Check arguments
+ if (!gathererp || !options) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Allocate
+ gatherer = mem_zalloc(sizeof(*gatherer), rawrtc_ice_gatherer_destroy);
+ if (!gatherer) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+
+ // Set fields/reference
+ gatherer->state = RAWRTC_ICE_GATHERER_STATE_NEW; // TODO: Raise state (delayed)?
+ gatherer->options = mem_ref(options);
+ gatherer->state_change_handler = state_change_handler;
+ gatherer->error_handler = error_handler;
+ gatherer->local_candidate_handler = local_candidate_handler;
+ gatherer->arg = arg;
+ list_init(&gatherer->url_addresses);
+ list_init(&gatherer->url_resolvers);
+ list_init(&gatherer->buffered_messages);
+ list_init(&gatherer->local_candidates);
+
+ // Generate random username fragment and password for ICE
+ rand_str(gatherer->ice_username_fragment, sizeof(gatherer->ice_username_fragment));
+ rand_str(gatherer->ice_password, sizeof(gatherer->ice_password));
+
+ // Set ICE configuration and create trice instance
+ // TODO: Get from config
+ gatherer->ice_config.nom = ICE_NOMINATION_AGGRESSIVE;
+ gatherer->ice_config.debug = RAWRTC_DEBUG_ICE_GATHERER ? true : false;
+ gatherer->ice_config.trace = RAWRTC_DEBUG_ICE_GATHERER ? true : false;
+ gatherer->ice_config.ansi = true;
+ gatherer->ice_config.enable_prflx = true;
+ gatherer->ice_config.optimize_loopback_pairing = true;
+ err = trice_alloc(
+ &gatherer->ice, &gatherer->ice_config, ICE_ROLE_UNKNOWN, gatherer->ice_username_fragment,
+ gatherer->ice_password);
+ if (err) {
+ DEBUG_WARNING("Unable to create trickle ICE instance, reason: %m\n", err);
+ goto out;
+ }
+
+ // Get local DNS servers
+ err = dns_srv_get(NULL, 0, dns_servers, &n_dns_servers);
+ if (err) {
+ DEBUG_WARNING("Unable to retrieve local DNS servers, reason: %m\n", err);
+ goto out;
+ }
+
+ // Print local DNS servers
+ if (n_dns_servers == 0) {
+ DEBUG_NOTICE("No DNS servers found\n");
+ }
+ for (i = 0; i < n_dns_servers; ++i) {
+ DEBUG_PRINTF("DNS server: %j\n", &dns_servers[i]);
+ }
+
+ // Create DNS client (for resolving ICE server IPs)
+ err = dnsc_alloc(&gatherer->dns_client, NULL, dns_servers, n_dns_servers);
+ if (err) {
+ DEBUG_WARNING("Unable to create DNS client instance, reason: %m\n", err);
+ goto out;
+ }
+
+ // Done
+ DEBUG_PRINTF("ICE gatherer created:\n%H", rawrtc_ice_gather_options_debug, gatherer->options);
+
+out:
+ if (err) {
+ mem_deref(gatherer);
+ } else {
+ // Set pointer
+ *gathererp = gatherer;
+ }
+ return rawrtc_error_to_code(err);
+}
+
+/*
+ * Change the state of the ICE gatherer.
+ * Will call the corresponding handler.
+ * TODO: https://github.com/w3c/ortc/issues/606
+ */
+static void set_state(
+ struct rawrtc_ice_gatherer* const gatherer, enum rawrtc_ice_gatherer_state const state) {
+ // Set state
+ gatherer->state = state;
+
+ // Call handler (if any)
+ if (gatherer->state_change_handler) {
+ gatherer->state_change_handler(state, gatherer->arg);
+ }
+}
+
+/*
+ * Close the ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_close(struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Already closed?
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // TODO: Stop ICE transport
+
+ // Stop timeout timer
+ tmr_cancel(&gatherer->timeout_timer);
+
+ // Remove STUN sessions from local candidate helpers
+ // Note: Needed to purge remaining references to the gatherer so it can be free'd.
+ list_apply(
+ &gatherer->local_candidates, true, rawrtc_candidate_helper_remove_stun_sessions_handler,
+ NULL);
+
+ // Flush local candidate helpers
+ list_flush(&gatherer->local_candidates);
+
+ // Remove ICE server URL resolvers
+ list_flush(&gatherer->url_resolvers);
+
+ // Remove ICE server URL addresses
+ list_flush(&gatherer->url_addresses);
+
+ // Stop ICE checklist (if running)
+ trice_checklist_stop(gatherer->ice);
+
+ // Remove ICE agent
+ gatherer->ice = mem_deref(gatherer->ice);
+
+ // Set state to closed and return
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_CLOSED);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Handle received UDP messages.
+ */
+static bool udp_receive_handler(struct sa* source, struct mbuf* buffer, void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ enum rawrtc_code error;
+
+ // Allocate context and copy source address
+ void* const context = mem_zalloc(sizeof(*source), NULL);
+ if (!context) {
+ error = RAWRTC_CODE_NO_MEMORY;
+ goto out;
+ }
+ memcpy(context, source, sizeof(*source));
+
+ // Buffer message
+ error = rawrtc_message_buffer_append(&gatherer->buffered_messages, buffer, context);
+ if (error) {
+ goto out;
+ }
+
+ // Done
+ DEBUG_PRINTF("Buffered UDP packet of size %zu\n", mbuf_get_left(buffer));
+
+out:
+ if (error) {
+ DEBUG_WARNING("Could not buffer UDP packet, reason: %s\n", rawrtc_code_to_str(error));
+ }
+
+ // Un-reference
+ mem_deref(context);
+
+ // Handled
+ return true;
+}
+
+/*
+ * Announce a local candidate.
+ */
+static enum rawrtc_code announce_candidate(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct ice_lcand* const re_candidate, // nullable
+ char const* const url // nullable
+) {
+ enum rawrtc_code error;
+
+ // Don't announce in the completed state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_COMPLETE) {
+ DEBUG_PRINTF("Not announcing candidate, gathering state is complete\n");
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Create ICE candidate
+ if (gatherer->local_candidate_handler) {
+ struct rawrtc_ice_candidate* ice_candidate = NULL;
+
+ // Create ICE candidate
+ if (re_candidate) {
+ error = rawrtc_ice_candidate_create_from_local_candidate(&ice_candidate, re_candidate);
+ if (error) {
+ return error;
+ }
+ }
+
+ // Call candidate handler and un-reference
+ gatherer->local_candidate_handler(ice_candidate, url, gatherer->arg);
+ mem_deref(ice_candidate);
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Check if the gathering process is complete.
+ */
+static void check_gathering_complete(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ bool const force_complete) {
+ struct le* le;
+ enum rawrtc_code error;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return;
+ }
+
+ // Check or force completion?
+ if (!force_complete) {
+ // Ensure no URL resolvers are in flight
+ if (!list_isempty(&gatherer->url_resolvers)) {
+ struct rawrtc_ice_server_url_resolver* const resolver =
+ list_head(&gatherer->url_resolvers)->data;
+ (void) resolver;
+ DEBUG_PRINTF(
+ "Gathering still in progress, resolving URL (%s [%s])\n", resolver->url->url,
+ dns_rr_typename(resolver->dns_type));
+ return;
+ }
+
+ // Ensure every local candidate has no pending srflx/relay candidates
+ for (le = list_head(&gatherer->local_candidates); le != NULL; le = le->next) {
+ struct rawrtc_candidate_helper* const candidate = le->data;
+
+ // Check counters
+ if (candidate->srflx_pending_count > 0 || candidate->relay_pending_count > 0) {
+ // Nope
+ DEBUG_PRINTF(
+ "Gathering still in progress at candidate %j, #srflx=%" PRIuFAST8
+ ", #relay=%" PRIuFAST8 "\n",
+ &candidate->candidate->attr.addr, candidate->srflx_pending_count,
+ candidate->relay_pending_count);
+ return;
+ }
+ }
+ }
+
+ // Stop timeout timer
+ tmr_cancel(&gatherer->timeout_timer);
+
+ // TODO: Skip the remaining code below when using continuous gathering
+
+ // Announce candidate gathering complete
+ error = announce_candidate(gatherer, NULL, NULL);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not announce end-of-candidates, reason: %s\n", rawrtc_code_to_str(error));
+
+ // This should never happen, so close on failure
+ rawrtc_ice_gatherer_close(gatherer);
+ return;
+ }
+
+ // Update state & done
+ if (gatherer->state != RAWRTC_ICE_GATHERER_STATE_COMPLETE) {
+ DEBUG_PRINTF("Gathering complete:\n%H", trice_debug, gatherer->ice);
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_COMPLETE);
+ }
+}
+
+/*
+ * Find an existing local candidate.
+ * TODO: This should probably be moved into a PR towards rew
+ */
+static struct ice_lcand* find_candidate(
+ struct trice* const ice,
+ enum ice_cand_type type, // set to -1 if it should not be checked
+ unsigned const component_id, // set to 0 if it should not be checked
+ int const protocol,
+ struct sa const* const address, // nullable
+ enum sa_flag const address_flag,
+ struct sa const* base_address, // nullable
+ enum sa_flag const base_address_flags) {
+ struct le* le;
+
+ // Check arguments
+ if (!ice) {
+ return NULL;
+ }
+
+ // If base address and address have an identical IP, ignore the base address and the type
+ if (address && base_address && sa_cmp(address, base_address, SA_ADDR)) {
+ base_address = NULL;
+ type = (enum ice_cand_type) - 1;
+ }
+
+ for (le = list_head(trice_lcandl(ice)); le != NULL; le = le->next) {
+ struct ice_lcand* candidate = le->data;
+
+ // Check type (if requested)
+ if (type != (enum ice_cand_type) - 1 && type != candidate->attr.type) {
+ continue;
+ }
+
+ // Check component id (if requested)
+ if (component_id && candidate->attr.compid != component_id) {
+ continue;
+ }
+
+ // Check protocol
+ if (candidate->attr.proto != protocol) {
+ continue;
+ }
+
+ // Check address
+ if (address && !sa_cmp(&candidate->attr.addr, address, address_flag)) {
+ continue;
+ }
+
+ // Check base address
+ if (base_address && !sa_cmp(&candidate->base_addr, base_address, base_address_flags)) {
+ continue;
+ }
+
+ // Found
+ return candidate;
+ }
+
+ // Not found
+ return NULL;
+}
+
+/*
+ * Gather relay candidates on an ICE server.
+ */
+static enum rawrtc_code gather_relay_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ // Check ICE server is enabled for TURN
+ if (server_address->url->type != RAWRTC_ICE_SERVER_TYPE_TURN) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // TODO: Create TURN request
+ (void) candidate;
+ DEBUG_NOTICE(
+ "TODO: Gather relay candidates using server %J (%s)\n", &server_address->address,
+ server_address->url->url);
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Handle gathered server reflexive candidate.
+ */
+static void reflexive_candidate_handler(
+ int err,
+ struct sa const* address, // not checked
+ void* arg // not checked
+) {
+ struct rawrtc_candidate_helper_stun_session* const session = arg;
+ struct rawrtc_candidate_helper* const candidate = session->candidate_helper;
+ struct rawrtc_ice_gatherer* const gatherer = candidate->gatherer;
+ struct ice_lcand* const re_candidate = candidate->candidate;
+ struct ice_lcand* re_other_candidate;
+ uint32_t priority;
+ struct ice_lcand* srflx_candidate;
+ enum rawrtc_code error;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return;
+ }
+
+ // Error?
+ if (err) {
+ DEBUG_NOTICE("STUN request failed, reason: %m\n", err);
+ goto out;
+ }
+
+ // Check if a local candidate with the same base and same attributes (apart from the port)
+ // exists
+ re_other_candidate = find_candidate(
+ gatherer->ice, ICE_CAND_TYPE_SRFLX, re_candidate->attr.compid, re_candidate->attr.proto,
+ address, SA_ADDR, &re_candidate->attr.addr, SA_ALL);
+ if (re_other_candidate) {
+ DEBUG_PRINTF(
+ "Ignoring server reflexive candidate with same base %J and public IP %j (%s)"
+ "\n",
+ &re_candidate->attr.addr, address, session->url->url);
+ goto out;
+ }
+
+ // Add server reflexive candidate
+ // TODO: Using the candidate's protocol, TCP type and component id correct?
+ priority = rawrtc_ice_candidate_calculate_priority(
+ list_count(trice_lcandl(gatherer->ice)), ICE_CAND_TYPE_SRFLX, re_candidate->attr.proto,
+ sa_af(address), re_candidate->attr.tcptype);
+ err = trice_lcand_add(
+ &srflx_candidate, gatherer->ice, re_candidate->attr.compid, re_candidate->attr.proto,
+ priority, address, &re_candidate->attr.addr, ICE_CAND_TYPE_SRFLX, &re_candidate->attr.addr,
+ re_candidate->attr.tcptype, NULL, RAWRTC_LAYER_ICE);
+ if (err) {
+ DEBUG_WARNING("Could not add server reflexive candidate, reason: %m\n", err);
+ goto out;
+ }
+ DEBUG_PRINTF(
+ "Added %s server reflexive candidate for interface %j (%s)\n",
+ net_proto2name(srflx_candidate->attr.proto), address, session->url->url);
+
+ // Announce candidate to handler
+ error = announce_candidate(gatherer, srflx_candidate, session->url->url);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not announce server reflexive candidate, reason: %s\n",
+ rawrtc_code_to_str(error));
+ goto out;
+ }
+
+out:
+ // Decrease counter & check if done gathering
+ if (session->pending) {
+ --candidate->srflx_pending_count;
+ session->pending = false;
+ }
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Gather server reflexive candidates on an ICE server.
+ */
+static enum rawrtc_code gather_reflexive_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ enum rawrtc_code error;
+ struct ice_lcand* const re_candidate = candidate->candidate;
+ struct ice_cand_attr* const attribute = &candidate->candidate->attr;
+ int const af = sa_af(&attribute->addr);
+ enum rawrtc_ice_candidate_type type;
+ char const* type_str;
+ struct rawrtc_candidate_helper_stun_session* session = NULL;
+ struct stun_conf stun_config = {
+ // TODO: Make this configurable!
+ .rto = STUN_DEFAULT_RTO, // 500ms
+ .rc = 3, // Send at: 0ms, 500ms, 1500ms
+ .rm = 6, // Additional wait: 3000ms
+ .ti = 4500, // Total timeout: 4500ms
+ .tos = 0x00,
+ };
+ struct stun_keepalive* stun_keepalive = NULL;
+
+ // Ignore IPv6 addresses
+ // Note: If you have a use case for IPv6 server reflexive candidates, let me know.
+ if (af == AF_INET6) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Ensure the candidate's IP version matches the server address's IP version
+ if (af != sa_af(&server_address->address)) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Convert ICE candidate type
+ error = rawrtc_ice_cand_type_to_ice_candidate_type(&type, attribute->type);
+ if (error) {
+ goto out;
+ }
+ type_str = rawrtc_ice_candidate_type_to_str(type);
+ (void) type_str;
+
+ // TODO: Handle TCP/TLS/DTLS transports
+
+ // Create STUN session
+ error = rawrtc_candidate_helper_stun_session_create(&session, server_address->url);
+ if (error) {
+ goto out;
+ }
+
+ // Create STUN keep-alive session
+ // TODO: We're using the candidate's protocol which conflicts with the ICE server URL transport
+ DEBUG_PRINTF(
+ "Creating STUN request for %s %s candidate %J using ICE server %J (%s)\n",
+ net_proto2name(attribute->proto), type_str, &attribute->addr, &server_address->address,
+ server_address->url->url);
+ error = rawrtc_error_to_code(stun_keepalive_alloc(
+ &stun_keepalive, re_candidate->attr.proto, re_candidate->us, RAWRTC_LAYER_STUN,
+ &server_address->address, &stun_config, reflexive_candidate_handler, session));
+ if (error) {
+ goto out;
+ }
+
+ // Add the STUN session to the candidate
+ error = rawrtc_candidate_helper_stun_session_add(session, candidate, stun_keepalive);
+ if (error) {
+ goto out;
+ }
+
+ // Increase counter, start the STUN session & done
+ ++candidate->srflx_pending_count;
+ stun_keepalive_enable(stun_keepalive, rawrtc_default_config.stun_keepalive_interval);
+ error = RAWRTC_CODE_SUCCESS;
+
+out:
+ if (error) {
+ DEBUG_WARNING("Could not create STUN request, reason: %s\n", rawrtc_code_to_str(error));
+ mem_deref(session);
+ }
+
+ // Un-reference & done
+ mem_deref(stun_keepalive);
+ return error;
+}
+
+/*
+ * Gather server reflexive and relay candidates using a specific ICE
+ * server.
+ */
+static void gather_candidates(
+ struct rawrtc_candidate_helper* const candidate, // not checked
+ struct rawrtc_ice_server_url_address* const server_address // not checked
+) {
+ struct sa* const address = &candidate->candidate->attr.addr;
+ int af;
+ enum rawrtc_code error;
+
+ // Skip IPv4, IPv6 (server [!] addresses)?
+ // TODO: Get config from struct
+ af = sa_af(&server_address->address);
+ if ((!rawrtc_default_config.ipv6_enable && af == AF_INET6) ||
+ (!rawrtc_default_config.ipv4_enable && af == AF_INET)) {
+ DEBUG_PRINTF(
+ "Ignoring ICE server address %j (family disabled)\n", &server_address->address);
+ return;
+ }
+
+ // Ignore 'any', loopback and link-local server (!) addresses
+ if (sa_is_any(&server_address->address) || sa_is_loopback(&server_address->address) ||
+ sa_is_linklocal(&server_address->address)) {
+ DEBUG_NOTICE("Ignoring ICE server address %j\n", &server_address->address);
+ return;
+ }
+
+ // Ignore loopback and link-local candidate (!) addresses (there is no mapped NAT address since
+ // the addresses aren't reachable from outside of the local network)
+ if (sa_is_linklocal(address) || sa_is_loopback(address)) {
+ return;
+ }
+
+ // Gather reflexive candidates
+ error = gather_reflexive_candidates(candidate, server_address);
+ if (error) {
+ DEBUG_WARNING(
+ "Could not gather server reflexive candidates, reason: %s", rawrtc_code_to_str(error));
+ // Note: Considered non-critical, continuing
+ }
+
+ // Gather relay candidates
+ error = gather_relay_candidates(candidate, server_address);
+ if (error) {
+ DEBUG_WARNING("Could not gather relay candidates, reason: %s", rawrtc_code_to_str(error));
+ // Note: Considered non-critical, continuing
+ }
+}
+
+/*
+ * Gather server reflexive and relay candidates using a newly resolved
+ * ICE server URL address.
+ */
+static void gather_candidates_using_server(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_ice_server_url_address* const address // not checked
+) {
+ struct le* le;
+ for (le = list_head(&gatherer->local_candidates); le != NULL; le = le->next) {
+ struct rawrtc_candidate_helper* const candidate = le->data;
+
+ // Gather candidates
+ gather_candidates(candidate, address);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Gather server reflexive candidates of a local candidate using
+ * already resolved ICE servers.
+ */
+static void gather_candidates_using_resolved_servers(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_candidate_helper* const candidate // not checked
+) {
+ struct le* le;
+ for (le = list_head(&gatherer->url_addresses); le != NULL; le = le->next) {
+ struct rawrtc_ice_server_url_address* const address = le->data;
+
+ // Gather candidates
+ gather_candidates(candidate, address);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+}
+
+/*
+ * Add local candidate, gather server reflexive and relay candidates.
+ */
+static enum rawrtc_code add_candidate(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct sa const* const address, // not checked
+ enum rawrtc_ice_protocol const protocol,
+ enum ice_tcptype const tcp_type) {
+ uint32_t priority;
+ int const ipproto = rawrtc_ice_protocol_to_ipproto(protocol);
+ struct ice_lcand* re_candidate;
+ int err;
+ struct rawrtc_candidate_helper* candidate;
+ enum rawrtc_code error;
+
+ // Add host candidate
+ priority = rawrtc_ice_candidate_calculate_priority(
+ list_count(trice_lcandl(gatherer->ice)), ICE_CAND_TYPE_HOST, ipproto, sa_af(address),
+ tcp_type);
+ // TODO: Set component id properly
+ err = trice_lcand_add(
+ &re_candidate, gatherer->ice, 1, ipproto, priority, address, NULL, ICE_CAND_TYPE_HOST, NULL,
+ tcp_type, NULL, RAWRTC_LAYER_ICE);
+ if (err) {
+ DEBUG_WARNING("Could not add host candidate, reason: %m\n", err);
+ return rawrtc_error_to_code(err);
+ }
+
+ // Create candidate helper (attaches receive handler)
+ error = rawrtc_candidate_helper_create(
+ &candidate, gatherer, re_candidate, udp_receive_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING("Could not create candidate helper, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Add to local candidates list
+ list_append(&gatherer->local_candidates, &candidate->le, candidate);
+ DEBUG_PRINTF(
+ "Added %s host candidate for interface %j\n", rawrtc_ice_protocol_to_str(protocol),
+ address);
+
+ // Announce host candidate to handler
+ error = announce_candidate(gatherer, re_candidate, NULL);
+ if (error) {
+ DEBUG_WARNING("Could not announce host candidate, reason: %s\n", rawrtc_code_to_str(error));
+ return error;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Gather server reflexive and relay candidates
+ gather_candidates_using_resolved_servers(gatherer, candidate);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Local interfaces callback.
+ * TODO: Consider ICE gather policy
+ * TODO: https://tools.ietf.org/html/draft-ietf-rtcweb-ip-handling-01
+ */
+static bool interface_handler(
+ char const* interface, // not checked
+ struct sa const* address, // not checked
+ void* arg // not checked
+) {
+ int af;
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+ (void) interface;
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return true; // Don't continue gathering
+ }
+
+ // Ignore 'any' address
+ if (sa_is_any(address)) {
+ DEBUG_PRINTF("Ignoring gathered 'any' address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Ignore loopback address
+ // TODO: Make this configurable
+ if (sa_is_loopback(address)) {
+ DEBUG_PRINTF("Ignoring gathered loopback address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Ignore link-local address
+ // TODO: Make this configurable
+ if (sa_is_linklocal(address)) {
+ DEBUG_PRINTF("Ignoring gathered link-local address %j\n", address);
+ return false; // Continue gathering
+ }
+
+ // Skip IPv4, IPv6?
+ // TODO: Get config from struct
+ af = sa_af(address);
+ if ((!rawrtc_default_config.ipv6_enable && af == AF_INET6) ||
+ (!rawrtc_default_config.ipv4_enable && af == AF_INET)) {
+ DEBUG_PRINTF("Ignoring gathered address %j (family disabled)\n", address);
+ return false; // Continue gathering
+ }
+
+ // TODO: Ignore interfaces gathered twice
+
+ DEBUG_PRINTF("Gathered local interface %j\n", address);
+
+ // Add UDP candidate
+ if (rawrtc_default_config.udp_enable) {
+ error = add_candidate(gatherer, address, RAWRTC_ICE_PROTOCOL_UDP, ICE_TCP_ACTIVE);
+ if (error) {
+ DEBUG_WARNING("Could not add candidate, reason: %s", rawrtc_code_to_str(error));
+ goto out;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return true; // Don't continue gathering
+ }
+ }
+
+ // Add TCP candidate
+ if (rawrtc_default_config.tcp_enable) {
+ // TODO
+ // add_candidate(gatherer, address, RAWRTC_ICE_PROTOCOL_TCP, ICE_TCP_SO);
+ DEBUG_WARNING("TODO: Add TCP host candidate for interface %j\n", address);
+ }
+
+out:
+ if (error) {
+ // Close and don't continue gathering
+ rawrtc_ice_gatherer_close(gatherer);
+ return true;
+ } else {
+ return false; // Continue gathering
+ }
+}
+
+/*
+ * ICE server URL address resolved handler.
+ */
+static bool ice_server_url_address_result_handler(
+ struct rawrtc_ice_server_url_address* const address, // not checked, referenced
+ void* const arg // not checked
+) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+ DEBUG_INFO("Resolved URL %s to address %J\n", address->url->url, &address->address);
+
+ // Append to list of URL addresses
+ list_append(&gatherer->url_addresses, &address->le, mem_ref(address));
+
+ // Gather on the newly created address
+ gather_candidates_using_server(gatherer, address);
+
+ // Done, stop traversing, one address per family is sufficient
+ return true;
+}
+
+/*
+ * Resolve ICE server IP addresses.
+ */
+static enum rawrtc_code resolve_ice_server_addresses(
+ struct rawrtc_ice_gatherer* const gatherer, // not checked
+ struct rawrtc_ice_gather_options* const options // not checked
+) {
+ struct le* le;
+
+ // Remove all ICE server URL resolvers
+ // Note: This will cancel pending URL resolve processes
+ list_flush(&gatherer->url_resolvers);
+
+ // Remove all resolved ICE server URL addresses
+ list_flush(&gatherer->url_addresses);
+
+ for (le = list_head(&options->ice_servers); le != NULL; le = le->next) {
+ struct rawrtc_ice_server* const ice_server = le->data;
+ struct le* url_le;
+ enum rawrtc_code error;
+
+ for (url_le = list_head(&ice_server->urls); url_le != NULL; url_le = url_le->next) {
+ struct rawrtc_ice_server_url* const url = url_le->data;
+ // URL already resolved (decoded IP address)?
+ if (!sa_is_any(&url->resolved_address)) {
+ struct rawrtc_ice_server_url_address* address;
+
+ // Create URL address from resolved URL
+ error = rawrtc_ice_server_url_address_create(&address, url, &url->resolved_address);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to create ICE server URL address, reason: %s\n",
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL addresses
+ list_append(&gatherer->url_addresses, &address->le, address);
+ }
+ } else {
+ // Create URL resolver for A record (if enabled)
+ if (rawrtc_default_config.ipv4_enable) {
+ struct rawrtc_ice_server_url_resolver* resolver;
+ error = rawrtc_ice_server_url_resolver_create(
+ &resolver, gatherer->dns_client, DNS_TYPE_A, url,
+ ice_server_url_address_result_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to query A record for URL %s, reason: %s\n", url->url,
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL resolvers
+ list_append(&gatherer->url_resolvers, &resolver->le, resolver);
+ }
+ }
+
+ // Create URL resolver for AAAA record (if enabled)
+ if (rawrtc_default_config.ipv6_enable) {
+ struct rawrtc_ice_server_url_resolver* resolver;
+ error = rawrtc_ice_server_url_resolver_create(
+ &resolver, gatherer->dns_client, DNS_TYPE_AAAA, url,
+ ice_server_url_address_result_handler, gatherer);
+ if (error) {
+ DEBUG_WARNING(
+ "Unable to query AAAA record for URL %s, reason: %s\n", url->url,
+ rawrtc_code_to_str(error));
+ // Continue - not considered critical
+ } else {
+ // Append to list of URL resolvers
+ list_append(&gatherer->url_resolvers, &resolver->le, resolver);
+ }
+ }
+ }
+ }
+ }
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Gathering timeout handler.
+ * Note: This timeout has no effect when using continuous gathering.
+ */
+static void gather_timeout_handler(void* arg) {
+ struct rawrtc_ice_gatherer* const gatherer = arg;
+
+ // Force gathering complete
+ check_gathering_complete(gatherer, true);
+}
+
+/*
+ * Start gathering using an ICE gatherer.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_gather(
+ struct rawrtc_ice_gatherer* const gatherer,
+ struct rawrtc_ice_gather_options* options // referenced, nullable
+) {
+ enum rawrtc_code error;
+
+ // Check arguments
+ if (!gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+ if (!options) {
+ options = gatherer->options;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Already gathering?
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_GATHERING) {
+ return RAWRTC_CODE_SUCCESS;
+ }
+
+ // Resolve ICE server IP addresses
+ error = resolve_ice_server_addresses(gatherer, options);
+ if (error) {
+ return error;
+ }
+
+ // Update state
+ set_state(gatherer, RAWRTC_ICE_GATHERER_STATE_GATHERING);
+
+ // Start timeout timer
+ // TODO: Make the timeout configurable
+ tmr_start(&gatherer->timeout_timer, 6000, gather_timeout_handler, gatherer);
+
+ // Start gathering host candidates
+ if (options->gather_policy != RAWRTC_ICE_GATHER_POLICY_NOHOST) {
+ net_if_apply(interface_handler, gatherer);
+ }
+
+ // Gathering complete?
+ check_gathering_complete(gatherer, false);
+
+ // Done
+ return RAWRTC_CODE_SUCCESS;
+}
+
+/*
+ * Get local ICE parameters of an ICE gatherer.
+ * `*parametersp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_local_parameters(
+ struct rawrtc_ice_parameters** const parametersp, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ // Check arguments
+ if (!parametersp || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Check state
+ if (gatherer->state == RAWRTC_ICE_GATHERER_STATE_CLOSED) {
+ return RAWRTC_CODE_INVALID_STATE;
+ }
+
+ // Create and return ICE parameters instance
+ return rawrtc_ice_parameters_create(
+ parametersp, gatherer->ice_username_fragment, gatherer->ice_password, false);
+}
+
+/*
+ * Destructor for an existing local candidates array.
+ */
+static void rawrtc_ice_gatherer_local_candidates_destroy(void* arg) {
+ struct rawrtc_ice_candidates* const candidates = arg;
+ size_t i;
+
+ // Un-reference each item
+ for (i = 0; i < candidates->n_candidates; ++i) {
+ mem_deref(candidates->candidates[i]);
+ }
+}
+
+/*
+ * Get local ICE candidates of an ICE gatherer.
+ * `*candidatesp` must be unreferenced.
+ */
+enum rawrtc_code rawrtc_ice_gatherer_get_local_candidates(
+ struct rawrtc_ice_candidates** const candidatesp, // de-referenced
+ struct rawrtc_ice_gatherer* const gatherer) {
+ size_t n;
+ struct rawrtc_ice_candidates* candidates;
+ struct le* le;
+ size_t i;
+ enum rawrtc_code error = RAWRTC_CODE_SUCCESS;
+
+ // Check arguments
+ if (!candidatesp || !gatherer) {
+ return RAWRTC_CODE_INVALID_ARGUMENT;
+ }
+
+ // Get length
+ n = list_count(trice_lcandl(gatherer->ice));
+
+ // Allocate & set length immediately
+ candidates = mem_zalloc(
+ sizeof(*candidates) + (sizeof(struct rawrtc_ice_candidate*) * n),
+ rawrtc_ice_gatherer_local_candidates_destroy);
+ if (!candidates) {
+ return RAWRTC_CODE_NO_MEMORY;
+ }
+ candidates->n_candidates = n;
+
+ // Copy each ICE candidate
+ for (le = list_head(trice_lcandl(gatherer->ice)), i = 0; le != NULL; le = le->next, ++i) {
+ struct ice_lcand* re_candidate = le->data;
+
+ // Create ICE candidate
+ error = rawrtc_ice_candidate_create_from_local_candidate(
+ &candidates->candidates[i], re_candidate);
+ if (error) {
+ goto out;
+ }
+ }
+
+out:
+ if (error) {
+ mem_deref(candidates);
+ } else {
+ // Set pointers
+ *candidatesp = candidates;
+ }
+ return error;
+}
diff --git a/src/ice_gatherer/gatherer.h b/src/ice_gatherer/gatherer.h
new file mode 100644
index 0000000..7f2d447
--- /dev/null
+++ b/src/ice_gatherer/gatherer.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <rawrtc/ice_gatherer.h>
+#include <re.h>
+#include <rew.h>
+
+enum {
+ RAWRTC_ICE_GATHERER_DNS_SERVERS = 10,
+ RAWRTC_ICE_USERNAME_FRAGMENT_LENGTH = 16,
+ RAWRTC_ICE_PASSWORD_LENGTH = 32,
+};
+
+struct rawrtc_ice_gatherer {
+ enum rawrtc_ice_gatherer_state state;
+ struct rawrtc_ice_gather_options* options; // referenced
+ rawrtc_ice_gatherer_state_change_handler state_change_handler; // nullable
+ rawrtc_ice_gatherer_error_handler error_handler; // nullable
+ rawrtc_ice_gatherer_local_candidate_handler local_candidate_handler; // nullable
+ void* arg; // nullable
+ struct tmr timeout_timer;
+ struct list url_addresses;
+ struct list url_resolvers;
+ struct list buffered_messages; // TODO: Can this be added to the candidates list?
+ struct list local_candidates; // TODO: Hash list instead?
+ char ice_username_fragment[RAWRTC_ICE_USERNAME_FRAGMENT_LENGTH + 1];
+ char ice_password[RAWRTC_ICE_PASSWORD_LENGTH + 1];
+ struct trice* ice;
+ struct trice_conf ice_config;
+ struct dnsc* dns_client;
+};
diff --git a/src/ice_gatherer/meson.build b/src/ice_gatherer/meson.build
new file mode 100644
index 0000000..1276f93
--- /dev/null
+++ b/src/ice_gatherer/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+ 'attributes.c',
+ 'gatherer.c',
+ 'utils.c',
+])
diff --git a/src/ice_gatherer/utils.c b/src/ice_gatherer/utils.c
new file mode 100644
index 0000000..982a5cb
--- /dev/null
+++ b/src/ice_gatherer/utils.c
@@ -0,0 +1,19 @@
+#include <rawrtc/ice_gatherer.h>
+
+/*
+ * Get the corresponding name for an ICE gatherer state.
+ */
+char const* rawrtc_ice_gatherer_state_to_name(enum rawrtc_ice_gatherer_state const state) {
+ switch (state) {
+ case RAWRTC_ICE_GATHERER_STATE_NEW:
+ return "new";
+ case RAWRTC_ICE_GATHERER_STATE_GATHERING:
+ return "gathering";
+ case RAWRTC_ICE_GATHERER_STATE_COMPLETE:
+ return "complete";
+ case RAWRTC_ICE_GATHERER_STATE_CLOSED:
+ return "closed";
+ default:
+ return "???";
+ }
+}