Skip to content

Commit db9579a

Browse files
Implement ICE-TCP
Co-authored-by: Paul-Louis Ageneau <[email protected]>
1 parent 0727156 commit db9579a

File tree

20 files changed

+781
-159
lines changed

20 files changed

+781
-159
lines changed

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ set(LIBJUICE_SOURCES
5454
${CMAKE_CURRENT_SOURCE_DIR}/src/server.c
5555
${CMAKE_CURRENT_SOURCE_DIR}/src/stun.c
5656
${CMAKE_CURRENT_SOURCE_DIR}/src/timestamp.c
57+
${CMAKE_CURRENT_SOURCE_DIR}/src/tcp.c
5758
${CMAKE_CURRENT_SOURCE_DIR}/src/turn.c
5859
${CMAKE_CURRENT_SOURCE_DIR}/src/udp.c
5960
)
@@ -83,6 +84,7 @@ set(TESTS_SOURCES
8384
${CMAKE_CURRENT_SOURCE_DIR}/test/conflict.c
8485
${CMAKE_CURRENT_SOURCE_DIR}/test/bind.c
8586
${CMAKE_CURRENT_SOURCE_DIR}/test/ufrag.c
87+
${CMAKE_CURRENT_SOURCE_DIR}/test/tcp.c
8688
)
8789
source_group("Test Files" FILES "${TESTS_SOURCES}")
8890

@@ -238,6 +240,10 @@ if(NOT NO_TESTS)
238240
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libjuice.tests)
239241

240242
target_link_libraries(juice-tests juice Threads::Threads)
243+
244+
if(WIN32)
245+
target_link_libraries(juice-tests ws2_32)
246+
endif()
241247
endif()
242248

243249
# Fuzzer

include/juice/juice.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ typedef enum juice_concurrency_mode {
9090
JUICE_CONCURRENCY_MODE_THREAD, // Each connection runs in its own thread
9191
} juice_concurrency_mode_t;
9292

93+
typedef enum juice_ice_tcp_mode {
94+
JUICE_ICE_TCP_MODE_NONE = 0, // ICE-TCP is disabled
95+
JUICE_ICE_TCP_MODE_ACTIVE, // ICE-TCP will operate as a client
96+
} juice_ice_tcp_mode_t;
97+
9398
typedef struct juice_config {
9499
juice_concurrency_mode_t concurrency_mode;
95100

@@ -132,6 +137,7 @@ JUICE_EXPORT int juice_get_selected_addresses(juice_agent_t *agent, char *local,
132137
JUICE_EXPORT int juice_set_local_ice_attributes(juice_agent_t *agent, const char *ufrag, const char *pwd);
133138
JUICE_EXPORT const char *juice_state_to_string(juice_state_t state);
134139
JUICE_EXPORT int juice_mux_listen(const char *bind_address, int local_port, juice_cb_mux_incoming_t cb, void *user_ptr);
140+
JUICE_EXPORT int juice_set_ice_tcp_mode(juice_agent_t *agent, juice_ice_tcp_mode_t ice_tcp_mode);
135141

136142
// ICE server
137143

src/addr.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,14 @@ unsigned long addr_hash(const struct sockaddr *sa, bool with_port) {
250250
return hash;
251251
}
252252

253-
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count) {
253+
int addr_resolve(const char *hostname, const char *service, int socktype, addr_record_t *records, size_t count) {
254254
addr_record_t *end = records + count;
255255

256256
struct addrinfo hints;
257257
memset(&hints, 0, sizeof(hints));
258258
hints.ai_family = AF_UNSPEC;
259-
hints.ai_socktype = SOCK_DGRAM;
260-
hints.ai_protocol = IPPROTO_UDP;
259+
hints.ai_socktype = socktype;
260+
hints.ai_protocol = socktype == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP;
261261
hints.ai_flags = AI_ADDRCONFIG;
262262
struct addrinfo *ai_list = NULL;
263263
if (getaddrinfo(hostname, service, &hints, &ai_list)) {

src/addr.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ unsigned long addr_hash(const struct sockaddr *sa, bool with_port);
3333
typedef struct addr_record {
3434
struct sockaddr_storage addr;
3535
socklen_t len;
36+
int socktype;
3637
} addr_record_t;
3738

38-
int addr_resolve(const char *hostname, const char *service, addr_record_t *records, size_t count);
39+
int addr_resolve(const char *hostname, const char *service, int socktype, addr_record_t *records, size_t count);
3940
bool addr_is_numeric_hostname(const char *hostname);
4041

4142
bool addr_record_is_equal(const addr_record_t *a, const addr_record_t *b, bool compare_ports);

src/agent.c

Lines changed: 95 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ juice_agent_t *agent_create(const juice_config_t *config) {
9292
}
9393

9494
bool alloc_failed = false;
95+
agent->ice_tcp_mode = JUICE_ICE_TCP_MODE_NONE;
9596
agent->config.concurrency_mode = config->concurrency_mode;
9697
agent->config.stun_server_host = alloc_string_copy(config->stun_server_host, &alloc_failed);
9798
agent->config.stun_server_port = config->stun_server_port;
@@ -211,6 +212,20 @@ static thread_return_t THREAD_CALL resolver_thread_entry(void *arg) {
211212
return (thread_return_t)0;
212213
}
213214

215+
void agent_add_ice_tcp_local_candidate(juice_agent_t *agent, addr_record_t *record) {
216+
ice_candidate_t candidate;
217+
if (ice_create_local_candidate(ICE_CANDIDATE_TYPE_HOST, 1, agent->local.candidates_count, record, &candidate, ICE_CANDIDATE_TRANSPORT_TCP_TYPE_ACTIVE)) {
218+
JLOG_ERROR("Failed to create ice-tcp candidate");
219+
return;
220+
}
221+
222+
if (ice_add_candidate(&candidate, &agent->local)) {
223+
JLOG_ERROR("Failed to add ice-tcp candidate to local description");
224+
}
225+
226+
return;
227+
}
228+
214229
int agent_gather_candidates(juice_agent_t *agent) {
215230
JLOG_VERBOSE("Gathering candidates");
216231
if (agent->conn_impl) {
@@ -252,7 +267,7 @@ int agent_gather_candidates(juice_agent_t *agent) {
252267
for (int i = 0; i < records_count; ++i) {
253268
ice_candidate_t candidate;
254269
if (ice_create_local_candidate(ICE_CANDIDATE_TYPE_HOST, 1, agent->local.candidates_count,
255-
records + i, &candidate)) {
270+
records + i, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
256271
JLOG_ERROR("Failed to create host candidate");
257272
continue;
258273
}
@@ -266,6 +281,15 @@ int agent_gather_candidates(juice_agent_t *agent) {
266281
}
267282
}
268283

284+
if (agent->ice_tcp_mode == JUICE_ICE_TCP_MODE_ACTIVE) {
285+
if (records_count > 0) {
286+
addr_set_port((struct sockaddr *)&records[0].addr, 9);
287+
agent_add_ice_tcp_local_candidate(agent, &records[0]);
288+
} else {
289+
JLOG_WARN("No local host candidates gathered, unable to add ice-tcp");
290+
}
291+
}
292+
269293
ice_sort_candidates(&agent->local);
270294

271295
for (int i = 0; i < agent->entries_count; ++i)
@@ -341,7 +365,7 @@ int agent_resolve_servers(juice_agent_t *agent) {
341365
conn_unlock(agent);
342366

343367
addr_record_t records[DEFAULT_MAX_RECORDS_COUNT];
344-
int records_count = addr_resolve(hostname, service, records, DEFAULT_MAX_RECORDS_COUNT);
368+
int records_count = addr_resolve(hostname, service, SOCK_DGRAM, records, DEFAULT_MAX_RECORDS_COUNT);
345369

346370
conn_lock(agent);
347371

@@ -426,7 +450,7 @@ int agent_resolve_servers(juice_agent_t *agent) {
426450
conn_unlock(agent);
427451

428452
addr_record_t records[MAX_STUN_SERVER_RECORDS_COUNT];
429-
int records_count = addr_resolve(hostname, service, records, MAX_STUN_SERVER_RECORDS_COUNT);
453+
int records_count = addr_resolve(hostname, service, SOCK_DGRAM, records, MAX_STUN_SERVER_RECORDS_COUNT);
430454

431455
conn_lock(agent);
432456

@@ -825,6 +849,34 @@ int agent_input(juice_agent_t *agent, char *buf, size_t len, const addr_record_t
825849
return -1;
826850
}
827851

852+
void agent_register_entry_for_candidate_pair(juice_agent_t *agent, ice_candidate_pair_t *pair, agent_stun_entry_t *relay_entry) {
853+
JLOG_VERBOSE("Registering STUN entry %d for candidate pair checking", agent->entries_count);
854+
agent_stun_entry_t *entry = agent->entries + agent->entries_count;
855+
entry->type = AGENT_STUN_ENTRY_TYPE_CHECK;
856+
entry->state = AGENT_STUN_ENTRY_STATE_IDLE;
857+
entry->mode = AGENT_MODE_UNKNOWN;
858+
entry->pair = pair;
859+
entry->record = pair->remote->resolved;
860+
entry->relay_entry = relay_entry;
861+
juice_random(entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
862+
entry->transaction_id_expired = false;
863+
++agent->entries_count;
864+
865+
if (pair->remote->type == ICE_CANDIDATE_TYPE_HOST)
866+
agent_translate_host_candidate_entry(agent, entry);
867+
}
868+
869+
void agent_tcp_conn_connected(juice_agent_t *agent) {
870+
for (int i = 0; i < agent->entries_count; ++i) {
871+
agent_stun_entry_t *entry = agent->entries + i;
872+
if (entry->pair->remote->transport != ICE_CANDIDATE_TRANSPORT_UDP) {
873+
entry->pair->tcp_connected = true;
874+
entry->next_transmission = current_timestamp();
875+
}
876+
}
877+
conn_interrupt(agent);
878+
}
879+
828880
int agent_bookkeeping(juice_agent_t *agent, timestamp_t *next_timestamp) {
829881
JLOG_VERBOSE("Bookkeeping...");
830882

@@ -837,8 +889,10 @@ int agent_bookkeeping(juice_agent_t *agent, timestamp_t *next_timestamp) {
837889
for (int i = 0; i < agent->entries_count; ++i) {
838890
agent_stun_entry_t *entry = agent->entries + i;
839891

840-
// STUN requests transmission or retransmission
841-
if (entry->state == AGENT_STUN_ENTRY_STATE_PENDING) {
892+
if (entry->pair && entry->pair->remote->transport != ICE_CANDIDATE_TRANSPORT_UDP && entry->pair->tcp_connected == false) {
893+
conn_tcp_connect(agent, &entry->pair->remote->resolved, agent_tcp_conn_connected);
894+
entry->next_transmission = now + LAST_STUN_RETRANSMISSION_TIMEOUT;
895+
} else if (entry->state == AGENT_STUN_ENTRY_STATE_PENDING) { // STUN requests transmission or retransmission
842896
if (entry->next_transmission > now)
843897
continue;
844898

@@ -1629,7 +1683,7 @@ int agent_send_stun_binding(juice_agent_t *agent, agent_stun_entry_t *entry, stu
16291683
? (int)(entry->pair->local - agent->local.candidates)
16301684
: 0;
16311685
msg.priority =
1632-
ice_compute_priority(ICE_CANDIDATE_TYPE_PEER_REFLEXIVE, family, 1, index);
1686+
ice_compute_priority(ICE_CANDIDATE_TYPE_PEER_REFLEXIVE, family, 1, index, false);
16331687

16341688
// RFC 8445 8.1.1. Nominating Pairs:
16351689
// Once the controlling agent has picked a valid pair for nomination, it repeats the
@@ -2177,7 +2231,7 @@ int agent_add_local_relayed_candidate(juice_agent_t *agent, const addr_record_t
21772231
}
21782232
ice_candidate_t candidate;
21792233
if (ice_create_local_candidate(ICE_CANDIDATE_TYPE_RELAYED, 1, agent->local.candidates_count,
2180-
record, &candidate)) {
2234+
record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
21812235
JLOG_ERROR("Failed to create relayed candidate");
21822236
return -1;
21832237
}
@@ -2220,7 +2274,7 @@ int agent_add_local_reflexive_candidate(juice_agent_t *agent, ice_candidate_type
22202274
return 0;
22212275
}
22222276
ice_candidate_t candidate;
2223-
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate)) {
2277+
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
22242278
JLOG_ERROR("Failed to create reflexive candidate");
22252279
return -1;
22262280
}
@@ -2260,7 +2314,7 @@ int agent_add_remote_reflexive_candidate(juice_agent_t *agent, ice_candidate_typ
22602314
return 0;
22612315
}
22622316
ice_candidate_t candidate;
2263-
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate)) {
2317+
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
22642318
JLOG_ERROR("Failed to create reflexive candidate");
22652319
return -1;
22662320
}
@@ -2297,6 +2351,26 @@ int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local, // lo
22972351
return -1;
22982352
}
22992353

2354+
if (remote->transport != ICE_CANDIDATE_TRANSPORT_UDP) {
2355+
if (agent->ice_tcp_mode != JUICE_ICE_TCP_MODE_ACTIVE) {
2356+
JLOG_WARN("ICE-TCP is disabled ignoring TCP Candidate");
2357+
return 0;
2358+
}
2359+
2360+
if (remote->transport != ICE_CANDIDATE_TRANSPORT_TCP_TYPE_PASSIVE && remote->transport != ICE_CANDIDATE_TRANSPORT_TCP_TYPE_SO) {
2361+
JLOG_INFO("Ignoring ICE-TCP Candidate that is not passive or simultaneous open");
2362+
return 0;
2363+
}
2364+
2365+
for (int i = 0; i < agent->candidate_pairs_count; ++i) {
2366+
ice_candidate_pair_t *pair = &agent->candidate_pairs[i];
2367+
if (pair->remote->transport != ICE_CANDIDATE_TRANSPORT_UDP) {
2368+
JLOG_INFO("Only one ICE-TCP remote candidate is supported ignoring TCP Candidate");
2369+
return 0;
2370+
}
2371+
}
2372+
}
2373+
23002374
JLOG_VERBOSE("Adding new candidate pair, priority=%" PRIu64, pair.priority);
23012375

23022376
// Add pair
@@ -2327,20 +2401,7 @@ int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local, // lo
23272401
}
23282402
}
23292403

2330-
JLOG_VERBOSE("Registering STUN entry %d for candidate pair checking", agent->entries_count);
2331-
agent_stun_entry_t *entry = agent->entries + agent->entries_count;
2332-
entry->type = AGENT_STUN_ENTRY_TYPE_CHECK;
2333-
entry->state = AGENT_STUN_ENTRY_STATE_IDLE;
2334-
entry->mode = AGENT_MODE_UNKNOWN;
2335-
entry->pair = pos;
2336-
entry->record = pos->remote->resolved;
2337-
entry->relay_entry = relay_entry;
2338-
juice_random(entry->transaction_id, STUN_TRANSACTION_ID_SIZE);
2339-
entry->transaction_id_expired = false;
2340-
++agent->entries_count;
2341-
2342-
if (remote->type == ICE_CANDIDATE_TYPE_HOST)
2343-
agent_translate_host_candidate_entry(agent, entry);
2404+
agent_register_entry_for_candidate_pair(agent, pos, relay_entry);
23442405

23452406
if (agent->mode == AGENT_MODE_CONTROLLING) {
23462407
for (int i = 0; i < agent->candidate_pairs_count; ++i) {
@@ -2623,6 +2684,17 @@ agent_stun_entry_t *agent_find_entry_from_record(juice_agent_t *agent, const add
26232684
return NULL;
26242685
}
26252686

2687+
int agent_set_ice_tcp_mode(juice_agent_t *agent, juice_ice_tcp_mode_t ice_tcp_mode)
2688+
{
2689+
if (agent->conn_impl) {
2690+
JLOG_WARN("Unable to set ICE attributes, candidates gathering already started");
2691+
return JUICE_ERR_FAILED;
2692+
}
2693+
2694+
agent->ice_tcp_mode = ice_tcp_mode;
2695+
return JUICE_ERR_SUCCESS;
2696+
}
2697+
26262698
void agent_translate_host_candidate_entry(juice_agent_t *agent, agent_stun_entry_t *entry) {
26272699
if (!entry->pair || entry->pair->remote->type != ICE_CANDIDATE_TYPE_HOST)
26282700
return;

src/agent.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ struct juice_agent {
126126
juice_config_t config;
127127
juice_state_t state;
128128
agent_mode_t mode;
129+
juice_ice_tcp_mode_t ice_tcp_mode;
129130

130131
ice_description_t local;
131132
ice_description_t remote;
@@ -236,5 +237,6 @@ agent_stun_entry_t *
236237
agent_find_entry_from_record(juice_agent_t *agent, const addr_record_t *record,
237238
const addr_record_t *relayed); // relayed may be NULL
238239
void agent_translate_host_candidate_entry(juice_agent_t *agent, agent_stun_entry_t *entry);
240+
int agent_set_ice_tcp_mode(juice_agent_t *agent, juice_ice_tcp_mode_t ice_tcp_mode);
239241

240242
#endif

src/conn.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222

2323
static conn_mode_entry_t mode_entries[MODE_ENTRIES_SIZE] = {
2424
{conn_poll_registry_init, conn_poll_registry_cleanup, conn_poll_init, conn_poll_cleanup,
25-
conn_poll_lock, conn_poll_unlock, conn_poll_interrupt, conn_poll_send, conn_poll_get_addrs,
25+
conn_poll_lock, conn_poll_unlock, conn_poll_interrupt, conn_poll_send, conn_poll_tcp_connect_func, conn_poll_get_addrs,
2626
NULL, NULL, NULL, MUTEX_INITIALIZER, NULL},
2727
{conn_mux_registry_init, conn_mux_registry_cleanup, conn_mux_init, conn_mux_cleanup,
28-
conn_mux_lock, conn_mux_unlock, conn_mux_interrupt, conn_mux_send, conn_mux_get_addrs,
28+
conn_mux_lock, conn_mux_unlock, conn_mux_interrupt, conn_mux_send, NULL, conn_mux_get_addrs,
2929
conn_mux_listen, conn_mux_get_registry, conn_mux_can_release_registry, MUTEX_INITIALIZER, NULL},
3030
{NULL, NULL, conn_thread_init, conn_thread_cleanup,
31-
conn_thread_lock, conn_thread_unlock, conn_thread_interrupt, conn_thread_send, conn_thread_get_addrs,
31+
conn_thread_lock, conn_thread_unlock, conn_thread_interrupt, conn_thread_send, NULL, conn_thread_get_addrs,
3232
NULL, NULL, NULL, MUTEX_INITIALIZER, NULL}
3333
};
3434

@@ -253,6 +253,17 @@ int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data,
253253
return get_agent_mode_entry(agent)->send_func(agent, dst, data, size, ds);
254254
}
255255

256+
void conn_tcp_connect(juice_agent_t *agent, const addr_record_t *dst, void (*callback)(juice_agent_t*)) {
257+
if (!agent->conn_impl)
258+
return;
259+
260+
tcp_connect_func *tcp_connect_func = get_agent_mode_entry(agent)->tcp_connect_func;
261+
if (tcp_connect_func == NULL)
262+
return;
263+
264+
tcp_connect_func(agent, dst, callback);
265+
}
266+
256267
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size) {
257268
if (!agent->conn_impl)
258269
return -1;

src/conn.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ typedef struct conn_registry {
3232
int agents_count;
3333
} conn_registry_t;
3434

35+
typedef void tcp_connect_func(juice_agent_t *agent, const addr_record_t *dst, void (*callback)(juice_agent_t*));
36+
3537
typedef struct conn_mode_entry {
3638
int (*registry_init_func)(conn_registry_t *registry, udp_socket_config_t *config);
3739
void (*registry_cleanup_func)(conn_registry_t *registry);
@@ -44,6 +46,7 @@ typedef struct conn_mode_entry {
4446
int (*interrupt_func)(juice_agent_t *agent);
4547
int (*send_func)(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
4648
int ds);
49+
tcp_connect_func *tcp_connect_func;
4750
int (*get_addrs_func)(juice_agent_t *agent, addr_record_t *records, size_t size);
4851
int (*mux_listen_func)(conn_registry_t *registry, juice_cb_mux_incoming_t cb, void *user_ptr);
4952
conn_registry_t *(*get_registry_func)(udp_socket_config_t *config);
@@ -61,6 +64,7 @@ void conn_unlock(juice_agent_t *agent);
6164
int conn_interrupt(juice_agent_t *agent);
6265
int conn_send(juice_agent_t *agent, const addr_record_t *dst, const char *data, size_t size,
6366
int ds);
67+
void conn_tcp_connect(juice_agent_t *agent, const addr_record_t *dst, void (*callback)(juice_agent_t*));
6468
int conn_get_addrs(juice_agent_t *agent, addr_record_t *records, size_t size);
6569

6670
#endif

0 commit comments

Comments
 (0)