mirror of
https://github.com/kunkundi/crossdesk.git
synced 2025-10-26 20:25:34 +08:00
409 lines
13 KiB
C
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;
|
|
}
|