Files
crossdesk/thirdparty/libjuice/src/ice.c
2023-07-13 16:58:20 +08:00

409 lines
13 KiB
C

/**
* Copyright (c) 2020 Paul-Louis Ageneau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#include "ice.h"
#include "log.h"
#include "random.h"
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 1024
#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
// See RFC4566 for SDP format: https://www.rfc-editor.org/rfc/rfc4566.html
static const char *skip_prefix(const char *str, const char *prefix) {
size_t len = strlen(prefix);
return strncmp(str, prefix, len) == 0 ? str + len : str;
}
static bool match_prefix(const char *str, const char *prefix, const char **end) {
*end = skip_prefix(str, prefix);
return *end != str || !*prefix;
}
static int parse_sdp_line(const char *line, ice_description_t *description) {
const char *arg;
if (match_prefix(line, "a=ice-ufrag:", &arg)) {
sscanf(arg, "%256s", description->ice_ufrag);
return 0;
}
if (match_prefix(line, "a=ice-pwd:", &arg)) {
sscanf(arg, "%256s", description->ice_pwd);
return 0;
}
if (match_prefix(line, "a=end-of-candidates:", &arg)) {
description->finished = true;
return 0;
}
ice_candidate_t candidate;
if (ice_parse_candidate_sdp(line, &candidate) == 0) {
ice_add_candidate(&candidate, description);
return 0;
}
return ICE_PARSE_IGNORED;
}
static int parse_sdp_candidate(const char *line, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
line = skip_prefix(line, "a=");
line = skip_prefix(line, "candidate:");
char transport[32 + 1];
char type[32 + 1];
if (sscanf(line, "%32s %d %32s %u %256s %32s typ %32s", candidate->foundation,
&candidate->component, transport, &candidate->priority, candidate->hostname,
candidate->service, type) != 7) {
JLOG_WARN("Failed to parse candidate: %s", line);
return ICE_PARSE_ERROR;
}
for (int i = 0; transport[i]; ++i)
transport[i] = toupper((unsigned char)transport[i]);
for (int i = 0; type[i]; ++i)
type[i] = tolower((unsigned char)type[i]);
if (strcmp(type, "host") == 0)
candidate->type = ICE_CANDIDATE_TYPE_HOST;
else if (strcmp(type, "srflx") == 0)
candidate->type = ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
else if (strcmp(type, "relay") == 0)
candidate->type = ICE_CANDIDATE_TYPE_RELAYED;
else {
JLOG_WARN("Ignoring candidate with unknown type \"%s\"", type);
return ICE_PARSE_IGNORED;
}
if (strcmp(transport, "UDP") != 0) {
JLOG_WARN("Ignoring candidate with transport %s", transport);
return ICE_PARSE_IGNORED;
}
return 0;
}
int ice_parse_sdp(const char *sdp, ice_description_t *description) {
memset(description, 0, sizeof(*description));
description->candidates_count = 0;
description->finished = false;
char buffer[BUFFER_SIZE];
size_t size = 0;
while (*sdp) {
if (*sdp == '\n') {
if (size) {
buffer[size++] = '\0';
if(parse_sdp_line(buffer, description) == ICE_PARSE_ERROR)
return ICE_PARSE_ERROR;
size = 0;
}
} else if (*sdp != '\r' && size + 1 < BUFFER_SIZE) {
buffer[size++] = *sdp;
}
++sdp;
}
ice_sort_candidates(description);
JLOG_DEBUG("Parsed remote description: ufrag=\"%s\", pwd=\"%s\", candidates=%d",
description->ice_ufrag, description->ice_pwd, description->candidates_count);
if (*description->ice_ufrag == '\0')
return ICE_PARSE_MISSING_UFRAG;
if (*description->ice_pwd == '\0')
return ICE_PARSE_MISSING_PWD;
return 0;
}
int ice_parse_candidate_sdp(const char *line, ice_candidate_t *candidate) {
const char *arg;
if (match_prefix(line, "a=candidate:", &arg)) {
int ret = parse_sdp_candidate(line, candidate);
if (ret < 0)
return ret;
ice_resolve_candidate(candidate, ICE_RESOLVE_MODE_SIMPLE);
return 0;
}
return ICE_PARSE_ERROR;
}
int ice_create_local_description(ice_description_t *description) {
memset(description, 0, sizeof(*description));
juice_random_str64(description->ice_ufrag, 4 + 1);
juice_random_str64(description->ice_pwd, 22 + 1);
description->candidates_count = 0;
description->finished = false;
JLOG_DEBUG("Created local description: ufrag=\"%s\", pwd=\"%s\"", description->ice_ufrag,
description->ice_pwd);
return 0;
}
int ice_create_local_candidate(ice_candidate_type_t type, int component, int index,
const addr_record_t *record, ice_candidate_t *candidate) {
memset(candidate, 0, sizeof(*candidate));
candidate->type = type;
candidate->component = component;
candidate->resolved = *record;
strcpy(candidate->foundation, "-");
candidate->priority = ice_compute_priority(candidate->type, candidate->resolved.addr.ss_family,
candidate->component, index);
if (getnameinfo((struct sockaddr *)&record->addr, record->len, candidate->hostname, 256,
candidate->service, 32, NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM)) {
JLOG_ERROR("getnameinfo failed, errno=%d", sockerrno);
return -1;
}
return 0;
}
int ice_resolve_candidate(ice_candidate_t *candidate, ice_resolve_mode_t mode) {
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_ADDRCONFIG;
if (mode != ICE_RESOLVE_MODE_LOOKUP)
hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
struct addrinfo *ai_list = NULL;
if (getaddrinfo(candidate->hostname, candidate->service, &hints, &ai_list)) {
JLOG_INFO("Failed to resolve address: %s:%s", candidate->hostname, candidate->service);
candidate->resolved.len = 0;
return -1;
}
for (struct addrinfo *ai = ai_list; ai; ai = ai->ai_next) {
if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) {
candidate->resolved.len = (socklen_t)ai->ai_addrlen;
memcpy(&candidate->resolved.addr, ai->ai_addr, ai->ai_addrlen);
break;
}
}
freeaddrinfo(ai_list);
return 0;
}
int ice_add_candidate(ice_candidate_t *candidate, ice_description_t *description) {
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN)
return -1;
if (description->candidates_count >= ICE_MAX_CANDIDATES_COUNT) {
JLOG_WARN("Description already has the maximum number of candidates");
return -1;
}
if (strcmp(candidate->foundation, "-") == 0)
snprintf(candidate->foundation, 32, "%u",
(unsigned int)(description->candidates_count + 1));
ice_candidate_t *pos = description->candidates + description->candidates_count;
*pos = *candidate;
++description->candidates_count;
return 0;
}
void ice_sort_candidates(ice_description_t *description) {
// In-place insertion sort
ice_candidate_t *begin = description->candidates;
ice_candidate_t *end = begin + description->candidates_count;
ice_candidate_t *cur = begin;
while (++cur < end) {
uint32_t priority = cur->priority;
ice_candidate_t *prev = cur;
ice_candidate_t tmp = *prev;
while (--prev >= begin && prev->priority < priority) {
*(prev + 1) = *prev;
}
if (prev + 1 != cur)
*(prev + 1) = tmp;
}
}
ice_candidate_t *ice_find_candidate_from_addr(ice_description_t *description,
const addr_record_t *record,
ice_candidate_type_t type) {
ice_candidate_t *cur = description->candidates;
ice_candidate_t *end = cur + description->candidates_count;
while (cur != end) {
if ((type == ICE_CANDIDATE_TYPE_UNKNOWN || cur->type == type) &&
addr_is_equal((struct sockaddr *)&record->addr, (struct sockaddr *)&cur->resolved.addr,
true))
return cur;
++cur;
}
return NULL;
}
int ice_generate_sdp(const ice_description_t *description, char *buffer, size_t size) {
if (!*description->ice_ufrag || !*description->ice_pwd)
return -1;
int len = 0;
char *begin = buffer;
char *end = begin + size;
// Round 0: description
// Round i with i>0 and i<count+1: candidate i-1
// Round count + 1: end-of-candidates and ice-options lines
for (int i = 0; i < description->candidates_count + 2; ++i) {
int ret;
if (i == 0) {
ret = snprintf(begin, end - begin, "a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",
description->ice_ufrag, description->ice_pwd);
} else if (i < description->candidates_count + 1) {
const ice_candidate_t *candidate = description->candidates + i - 1;
if (candidate->type == ICE_CANDIDATE_TYPE_UNKNOWN ||
candidate->type == ICE_CANDIDATE_TYPE_PEER_REFLEXIVE)
continue;
char tmp[BUFFER_SIZE];
if (ice_generate_candidate_sdp(candidate, tmp, BUFFER_SIZE) < 0)
continue;
ret = snprintf(begin, end - begin, "%s\r\n", tmp);
} else { // i == description->candidates_count + 1
// RFC 8445 10. ICE Option: An agent compliant to this specification MUST inform the
// peer about the compliance using the 'ice2' option.
if (description->finished)
ret = snprintf(begin, end - begin, "a=end-of-candidates\r\na=ice-options:ice2\r\n");
else
ret = snprintf(begin, end - begin, "a=ice-options:ice2,trickle\r\n");
}
if (ret < 0)
return -1;
len += ret;
if (begin < end)
begin += ret >= end - begin ? end - begin - 1 : ret;
}
return len;
}
int ice_generate_candidate_sdp(const ice_candidate_t *candidate, char *buffer, size_t size) {
const char *type = NULL;
const char *suffix = NULL;
switch (candidate->type) {
case ICE_CANDIDATE_TYPE_HOST:
type = "host";
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
type = "prflx";
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
type = "srflx";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
case ICE_CANDIDATE_TYPE_RELAYED:
type = "relay";
suffix = "raddr 0.0.0.0 rport 0"; // This is needed for compatibility with Firefox
break;
default:
JLOG_ERROR("Unknown candidate type");
return -1;
}
return snprintf(buffer, size, "a=candidate:%s %u UDP %u %s %s typ %s%s%s",
candidate->foundation, candidate->component, candidate->priority,
candidate->hostname, candidate->service, type, suffix ? " " : "",
suffix ? suffix : "");
}
int ice_create_candidate_pair(ice_candidate_t *local, ice_candidate_t *remote, bool is_controlling,
ice_candidate_pair_t *pair) { // local or remote might be NULL
if (local && remote && local->resolved.addr.ss_family != remote->resolved.addr.ss_family) {
JLOG_ERROR("Mismatching candidates address families");
return -1;
}
memset(pair, 0, sizeof(*pair));
pair->local = local;
pair->remote = remote;
pair->state = ICE_CANDIDATE_PAIR_STATE_FROZEN;
return ice_update_candidate_pair(pair, is_controlling);
}
int ice_update_candidate_pair(ice_candidate_pair_t *pair, bool is_controlling) {
// Compute pair priority according to RFC 8445, extended to support generic pairs missing local
// or remote See https://www.rfc-editor.org/rfc/rfc8445.html#section-6.1.2.3
if (!pair->local && !pair->remote)
return 0;
uint64_t local_priority =
pair->local
? pair->local->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->remote->resolved.addr.ss_family,
pair->remote->component, 0);
uint64_t remote_priority =
pair->remote
? pair->remote->priority
: ice_compute_priority(ICE_CANDIDATE_TYPE_HOST, pair->local->resolved.addr.ss_family,
pair->local->component, 0);
uint64_t g = is_controlling ? local_priority : remote_priority;
uint64_t d = is_controlling ? remote_priority : local_priority;
uint64_t min = g < d ? g : d;
uint64_t max = g > d ? g : d;
pair->priority = (min << 32) + (max << 1) + (g > d ? 1 : 0);
return 0;
}
int ice_candidates_count(const ice_description_t *description, ice_candidate_type_t type) {
int count = 0;
for (int i = 0; i < description->candidates_count; ++i) {
const ice_candidate_t *candidate = description->candidates + i;
if (candidate->type == type)
++count;
}
return count;
}
uint32_t ice_compute_priority(ice_candidate_type_t type, int family, int component, int index) {
// Compute candidate priority according to RFC 8445
// See https://www.rfc-editor.org/rfc/rfc8445.html#section-5.1.2.1
uint32_t p = 0;
switch (type) {
case ICE_CANDIDATE_TYPE_HOST:
p += ICE_CANDIDATE_PREF_HOST;
break;
case ICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_PEER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
p += ICE_CANDIDATE_PREF_SERVER_REFLEXIVE;
break;
case ICE_CANDIDATE_TYPE_RELAYED:
p += ICE_CANDIDATE_PREF_RELAYED;
break;
default:
break;
}
p <<= 16;
switch (family) {
case AF_INET:
p += 32767;
break;
case AF_INET6:
p += 65535;
break;
default:
break;
}
p -= CLAMP(index, 0, 32767);
p <<= 8;
p += 256 - CLAMP(component, 1, 256);
return p;
}