Skip to content

Commit 0509e6a

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

File tree

20 files changed

+767
-158
lines changed

20 files changed

+767
-158
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: 98 additions & 22 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,14 @@ 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+
agent_add_ice_tcp_local_candidate(agent, &records[0]);
287+
} else {
288+
JLOG_WARN("No local host candidates gathered, unable to add ice-tcp");
289+
}
290+
}
291+
269292
ice_sort_candidates(&agent->local);
270293

271294
for (int i = 0; i < agent->entries_count; ++i)
@@ -341,7 +364,7 @@ int agent_resolve_servers(juice_agent_t *agent) {
341364
conn_unlock(agent);
342365

343366
addr_record_t records[DEFAULT_MAX_RECORDS_COUNT];
344-
int records_count = addr_resolve(hostname, service, records, DEFAULT_MAX_RECORDS_COUNT);
367+
int records_count = addr_resolve(hostname, service, SOCK_DGRAM, records, DEFAULT_MAX_RECORDS_COUNT);
345368

346369
conn_lock(agent);
347370

@@ -426,7 +449,7 @@ int agent_resolve_servers(juice_agent_t *agent) {
426449
conn_unlock(agent);
427450

428451
addr_record_t records[MAX_STUN_SERVER_RECORDS_COUNT];
429-
int records_count = addr_resolve(hostname, service, records, MAX_STUN_SERVER_RECORDS_COUNT);
452+
int records_count = addr_resolve(hostname, service, SOCK_DGRAM, records, MAX_STUN_SERVER_RECORDS_COUNT);
430453

431454
conn_lock(agent);
432455

@@ -825,6 +848,34 @@ int agent_input(juice_agent_t *agent, char *buf, size_t len, const addr_record_t
825848
return -1;
826849
}
827850

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

@@ -998,6 +1049,10 @@ int agent_bookkeeping(juice_agent_t *agent, timestamp_t *next_timestamp) {
9981049
++pending_count;
9991050
}
10001051
}
1052+
1053+
if (pair->remote->transport != ICE_CANDIDATE_TRANSPORT_UDP) {
1054+
conn_tcp_connect(agent, &pair->remote->resolved, agent_tcp_conn_connected);
1055+
}
10011056
}
10021057

10031058
if (agent->mode == AGENT_MODE_CONTROLLING && nominated_pair) {
@@ -1629,7 +1684,7 @@ int agent_send_stun_binding(juice_agent_t *agent, agent_stun_entry_t *entry, stu
16291684
? (int)(entry->pair->local - agent->local.candidates)
16301685
: 0;
16311686
msg.priority =
1632-
ice_compute_priority(ICE_CANDIDATE_TYPE_PEER_REFLEXIVE, family, 1, index);
1687+
ice_compute_priority(ICE_CANDIDATE_TYPE_PEER_REFLEXIVE, family, 1, index, false);
16331688

16341689
// RFC 8445 8.1.1. Nominating Pairs:
16351690
// Once the controlling agent has picked a valid pair for nomination, it repeats the
@@ -2177,7 +2232,7 @@ int agent_add_local_relayed_candidate(juice_agent_t *agent, const addr_record_t
21772232
}
21782233
ice_candidate_t candidate;
21792234
if (ice_create_local_candidate(ICE_CANDIDATE_TYPE_RELAYED, 1, agent->local.candidates_count,
2180-
record, &candidate)) {
2235+
record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
21812236
JLOG_ERROR("Failed to create relayed candidate");
21822237
return -1;
21832238
}
@@ -2220,7 +2275,7 @@ int agent_add_local_reflexive_candidate(juice_agent_t *agent, ice_candidate_type
22202275
return 0;
22212276
}
22222277
ice_candidate_t candidate;
2223-
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate)) {
2278+
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
22242279
JLOG_ERROR("Failed to create reflexive candidate");
22252280
return -1;
22262281
}
@@ -2260,7 +2315,7 @@ int agent_add_remote_reflexive_candidate(juice_agent_t *agent, ice_candidate_typ
22602315
return 0;
22612316
}
22622317
ice_candidate_t candidate;
2263-
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate)) {
2318+
if (ice_create_local_candidate(type, 1, agent->local.candidates_count, record, &candidate, ICE_CANDIDATE_TRANSPORT_UDP)) {
22642319
JLOG_ERROR("Failed to create reflexive candidate");
22652320
return -1;
22662321
}
@@ -2297,6 +2352,27 @@ int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local, // lo
22972352
return -1;
22982353
}
22992354

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

23022378
// Add pair
@@ -2327,20 +2403,9 @@ int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local, // lo
23272403
}
23282404
}
23292405

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);
2406+
if (!is_tcp) {
2407+
agent_register_entry_for_candidate_pair(agent, pos, relay_entry);
2408+
}
23442409

23452410
if (agent->mode == AGENT_MODE_CONTROLLING) {
23462411
for (int i = 0; i < agent->candidate_pairs_count; ++i) {
@@ -2358,7 +2423,7 @@ int agent_add_candidate_pair(juice_agent_t *agent, ice_candidate_t *local, // lo
23582423
}
23592424

23602425
// There is only one component, therefore we can unfreeze if no pair is nominated
2361-
if (*agent->remote.ice_ufrag != '\0' &&
2426+
if (!is_tcp && *agent->remote.ice_ufrag != '\0' &&
23622427
(!agent->selected_pair || !agent->selected_pair->nominated)) {
23632428
JLOG_VERBOSE("Unfreezing the new candidate pair");
23642429
agent_unfreeze_candidate_pair(agent, pos);
@@ -2623,6 +2688,17 @@ agent_stun_entry_t *agent_find_entry_from_record(juice_agent_t *agent, const add
26232688
return NULL;
26242689
}
26252690

2691+
int agent_set_ice_tcp_mode(juice_agent_t *agent, juice_ice_tcp_mode_t ice_tcp_mode)
2692+
{
2693+
if (agent->conn_impl) {
2694+
JLOG_WARN("Unable to set ICE attributes, candidates gathering already started");
2695+
return JUICE_ERR_FAILED;
2696+
}
2697+
2698+
agent->ice_tcp_mode = ice_tcp_mode;
2699+
return JUICE_ERR_SUCCESS;
2700+
}
2701+
26262702
void agent_translate_host_candidate_entry(juice_agent_t *agent, agent_stun_entry_t *entry) {
26272703
if (!entry->pair || entry->pair->remote->type != ICE_CANDIDATE_TYPE_HOST)
26282704
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)