Statistics
| Branch: | Revision:

janus-gateway / ice.c @ 5e9e29e0

History | View | Annotate | Download (34.1 KB)

1 be35facb meetecho
/*! \file    ice.c
2
 * \author   Lorenzo Miniero <lorenzo@meetecho.com>
3
 * \copyright GNU Affero General Public License v3
4
 * \brief    ICE/STUN/TURN processing
5
 * \details  Implementation (based on libnice) of the ICE process. The
6
 * code handles the whole ICE process, from the gathering of candidates
7
 * to the final setup of a virtual channel RTP and RTCP can be transported
8
 * on. Incoming RTP and RTCP packets from peers are relayed to the associated
9
 * plugins by means of the incoming_rtp and incoming_rtcp callbacks. Packets
10
 * to be sent to peers are relayed by peers invoking the relay_rtp and
11
 * relay_rtcp gateway callbacks instead. 
12
 * 
13
 * \ingroup protocols
14
 * \ref protocols
15
 */
16
 
17
#include <sys/socket.h>
18
#include <netdb.h>
19 5e9e29e0 meetecho
#include <stun/usages/bind.h>
20 be35facb meetecho
21
#include "janus.h"
22
#include "debug.h"
23
#include "ice.h"
24
#include "dtls.h"
25
#include "rtp.h"
26
#include "rtcp.h"
27
#include "apierror.h"
28
29
30
/* STUN server/port, if any */
31
static char *janus_stun_server;
32
static uint16_t janus_stun_port;
33
34
char *janus_ice_get_stun_server() {
35
        return janus_stun_server;
36
}
37
uint16_t janus_ice_get_stun_port() {
38
        return janus_stun_port;
39
}
40
41
42 5e9e29e0 meetecho
/* RTP/RTCP port range */
43
uint16_t rtp_range_min = 0;
44
uint16_t rtp_range_max = 0;
45
46
47 be35facb meetecho
/* libnice initialization */
48 5e9e29e0 meetecho
gint janus_ice_init(gchar *stun_server, uint16_t stun_port, uint16_t rtp_min_port, uint16_t rtp_max_port) {
49 be35facb meetecho
        if(stun_server == NULL)
50
                return 0;        /* No initialization needed */
51
        if(stun_port == 0)
52
                stun_port = 3478;
53 5e9e29e0 meetecho
        /*! \todo The RTP/RTCP port range configuration may be just a placeholder: for
54
         * instance, libnice supports this since 0.1.0, but the 0.1.3 on Fedora fails
55
         * when linking with an undefined reference to \c nice_agent_set_port_range 
56
         * so this is checked by the install.sh script in advance. */
57
        rtp_range_min = rtp_min_port;
58
        rtp_range_max = rtp_max_port;
59
#ifndef HAVE_PORTRANGE
60
        JANUS_DEBUG("nice_agent_set_port_range unavailable, port range disabled\n");
61
#endif
62 be35facb meetecho
        JANUS_PRINT("STUN server to use: %s:%u\n", stun_server, stun_port);
63
        /* Resolve address to get an IP */
64
        struct hostent *he = gethostbyname(stun_server);
65
        if(he == NULL) {
66
                JANUS_DEBUG("Could not resolve %s...\n", stun_server);
67
                return -1;
68
        }
69
        struct in_addr **addr_list = (struct in_addr **)he->h_addr_list;
70
        if(addr_list[0] == NULL) {
71
                JANUS_DEBUG("Could not resolve %s...\n", stun_server);
72
                return -1;
73
        }
74
        janus_stun_server = g_strdup(inet_ntoa(*addr_list[0]));
75
        if(janus_stun_server == NULL) {
76
                JANUS_DEBUG("Memory error!\n");
77
                return -1;
78
        }
79
        janus_stun_port = stun_port;
80
        JANUS_PRINT("  >> %s:%u\n", janus_stun_server, janus_stun_port);
81 5e9e29e0 meetecho
        /* Test the STUN server */
82
        StunAgent stun;
83
        stun_agent_init (&stun, STUN_ALL_KNOWN_ATTRIBUTES, STUN_COMPATIBILITY_RFC5389, 0);
84
        StunMessage msg;
85
        uint8_t buf[1500];
86
        size_t len = stun_usage_bind_create(&stun, &msg, buf, 1500);
87
        JANUS_PRINT("Testing STUN server: message is of %zu bytes\n", len);
88
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
89
        int yes = 1;        /* For setsockopt() SO_REUSEADDR */
90
        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
91
        struct sockaddr_in address, remote;
92
        address.sin_family = AF_INET;
93
        address.sin_port = 0;
94
        address.sin_addr.s_addr = INADDR_ANY;
95
        remote.sin_family = AF_INET;
96
        remote.sin_port = htons(janus_stun_port);
97
        remote.sin_addr.s_addr = inet_addr(janus_stun_server);
98
        if(bind(fd, (struct sockaddr *)(&address), sizeof(struct sockaddr)) < 0) {
99
                JANUS_PRINT("Bind failed for STUN BINDING test\n");
100
                return -1;
101
        }
102
        int bytes = sendto(fd, buf, len, 0, (struct sockaddr*)&remote, sizeof(remote));
103
        if(bytes < 0) {
104
                JANUS_PRINT("Error sending STUN BINDING test\n");
105
                return -1;
106
        }
107
        JANUS_PRINT("  >> Sent %d bytes %s:%u, waiting for reply...\n", bytes, janus_stun_server, janus_stun_port);
108
        struct timeval timeout;
109
        fd_set readfds;
110
        FD_SET(fd, &readfds);
111
        timeout.tv_sec = 5;        /* FIXME Don't wait forever */
112
        timeout.tv_usec = 0;
113
        select(fd+1, &readfds, NULL, NULL, &timeout);
114
        if(!FD_ISSET(fd, &readfds)) {
115
                JANUS_PRINT("No response to our STUN BINDING test\n");
116
                return -1;
117
        }
118
        socklen_t addrlen = sizeof(remote);
119
        bytes = recvfrom(fd, buf, 1500, 0, (struct sockaddr*)&remote, &addrlen);
120
        JANUS_PRINT("  >> Got %d bytes...\n", bytes);
121
        if(stun_agent_validate (&stun, &msg, buf, bytes, NULL, NULL) < 0) {
122
                JANUS_PRINT("Failed to validate STUN BINDING response\n");
123
                return -1;
124
        }
125
        StunClass class = stun_message_get_class(&msg);
126
        StunMethod method = stun_message_get_method(&msg);
127
        if(class != STUN_RESPONSE || method != STUN_BINDING) {
128
                JANUS_PRINT("Unexpected STUN response: %d/%d\n", class, method);
129
                return -1;
130
        }
131
        StunMessageReturn ret = stun_message_find_xor_addr(&msg, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, (struct sockaddr *)&address, &addrlen);
132
        JANUS_PRINT("  >> XOR-MAPPED-ADDRESS: %d\n", ret);
133
        if(ret == STUN_MESSAGE_RETURN_SUCCESS) {
134
                janus_set_public_ip(inet_ntoa(address.sin_addr));
135
                JANUS_PRINT("  >> Our public address is %s\n", janus_get_public_ip());
136
                return 0;
137
        }
138
        ret = stun_message_find_addr(&msg, STUN_ATTRIBUTE_MAPPED_ADDRESS, (struct sockaddr *)&address, &addrlen);
139
        JANUS_PRINT("  >> MAPPED-ADDRESS: %d\n", ret);
140
        if(ret == STUN_MESSAGE_RETURN_SUCCESS) {
141
                janus_set_public_ip(inet_ntoa(address.sin_addr));
142
                JANUS_PRINT("  >> Our public address is %s\n", janus_get_public_ip());
143
                return 0;
144
        }
145
        return -1;
146 be35facb meetecho
}
147
148
/* ICE stuff */
149
static const gchar *janus_ice_state_name[] = 
150
{
151
        "disconnected",
152
        "gathering",
153
        "connecting",
154
        "connected",
155
        "ready",
156
        "failed"
157
};
158
const gchar *janus_get_ice_state_name(gint state) {
159
        if(state < 0 || state > 5)
160
                return NULL;
161
        return janus_ice_state_name[state];
162
}
163
164
/* ICE Handless */
165
janus_ice_handle *janus_ice_handle_create(void *gateway_session) {
166
        if(gateway_session == NULL)
167
                return NULL;
168
        janus_session *session = (janus_session *)gateway_session;
169
        guint64 handle_id = 0;
170
        while(handle_id == 0) {
171
                handle_id = g_random_int();
172
                if(janus_ice_handle_find(gateway_session, handle_id) != NULL) {
173
                        /* Handle ID already taken, try another one */
174
                        handle_id = 0;
175
                }
176
        }
177
        JANUS_PRINT("Creating new handle in session %"SCNu64": %"SCNu64"\n", session->session_id, handle_id);
178
        janus_ice_handle *handle = (janus_ice_handle *)calloc(1, sizeof(janus_ice_handle));
179
        if(handle == NULL) {
180
                JANUS_DEBUG("Memory error!\n");
181
                return NULL;
182
        }
183
        handle->session = gateway_session;
184
        handle->handle_id = handle_id;
185
        handle->app = NULL;
186
        handle->app_handle = NULL;
187
        janus_mutex_init(&handle->mutex);
188
                /* Setup other stuff */
189
        if(session->ice_handles == NULL)
190
                session->ice_handles = g_hash_table_new(NULL, NULL);
191
        g_hash_table_insert(session->ice_handles, GUINT_TO_POINTER(handle_id), handle);
192
        return handle;
193
}
194
195
janus_ice_handle *janus_ice_handle_find(void *gateway_session, guint64 handle_id) {
196
        if(gateway_session == NULL)
197
                return NULL;
198
        janus_session *session = (janus_session *)gateway_session;
199
        return session->ice_handles ? g_hash_table_lookup(session->ice_handles, GUINT_TO_POINTER(handle_id)) : NULL;
200
}
201
202
gint janus_ice_handle_attach_plugin(void *gateway_session, guint64 handle_id, janus_plugin *plugin) {
203
        if(gateway_session == NULL)
204
                return JANUS_ERROR_SESSION_NOT_FOUND;
205
        if(plugin == NULL)
206
                return JANUS_ERROR_PLUGIN_NOT_FOUND;
207
        janus_session *session = (janus_session *)gateway_session;
208
        janus_ice_handle *handle = janus_ice_handle_find(session, handle_id);
209
        if(handle == NULL)
210
                return JANUS_ERROR_HANDLE_NOT_FOUND;
211
        int error = 0;
212
        janus_pluginession *session_handle = calloc(1, sizeof(janus_pluginession));
213
        if(session_handle == NULL) {
214
                JANUS_DEBUG("Memory error!\n");
215
                return JANUS_ERROR_UNKNOWN;        /* FIXME Do we need something like "Internal Server Error"? */
216
        }
217
        session_handle->gateway_handle = handle;
218
        session_handle->plugin_handle = NULL;
219
        plugin->create_session(session_handle, &error);
220
        if(error) {
221
                /* TODO Make error struct to pass verbose information */
222
                return error;
223
        }
224
        handle->app = plugin;
225
        handle->app_handle = session_handle;
226
        return 0;
227
}
228
229
gint janus_ice_handle_destroy(void *gateway_session, guint64 handle_id) {
230
        if(gateway_session == NULL)
231
                return JANUS_ERROR_SESSION_NOT_FOUND;
232
        janus_session *session = (janus_session *)gateway_session;
233
        janus_ice_handle *handle = janus_ice_handle_find(session, handle_id);
234
        if(handle == NULL)
235
                return JANUS_ERROR_HANDLE_NOT_FOUND;
236
        handle->stop = 1;
237
        janus_plugin *plugin_t = (janus_plugin *)handle->app;
238
        JANUS_PRINT("Detaching handle from %s\n", plugin_t->get_name());
239
        /* TODO Actually detach handle... */
240
        int error = 0;
241
        handle->app_handle->gateway_handle = NULL;
242
        plugin_t->destroy_session(handle->app_handle, &error);
243
        g_hash_table_remove(session->ice_handles, GUINT_TO_POINTER(handle_id));
244
        /* TODO Actually destroy handle */
245
        return error;
246
}
247
248
249
/* Callbacks */
250
void janus_ice_cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, gpointer user_data) {
251
        janus_ice_handle *handle = (janus_ice_handle *)user_data;
252
        if(!handle)
253
                return;
254
        JANUS_PRINT("[%"SCNu64"] Gathering done for stream %d\n", handle->handle_id, stream_id);
255
        handle->cdone++;
256
        janus_ice_stream *stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(stream_id));
257
        if(!stream) {
258
                JANUS_DEBUG("[%"SCNu64"]  No stream %d??\n", handle->handle_id, stream_id);
259
                janus_mutex_unlock(&handle->mutex);
260
                return;
261
        }
262
        stream->cdone = 1;
263
}
264
void janus_ice_cb_component_state_changed(NiceAgent *agent, guint stream_id, guint component_id, guint state, gpointer ice) {
265
        janus_ice_handle *handle = (janus_ice_handle *)ice;
266
        JANUS_PRINT("[%"SCNu64"] Component state changed for component %d in stream %d: %d (%s)\n",
267
                handle ? handle->handle_id : -1, component_id, stream_id, state, janus_get_ice_state_name(state));
268
        if(!handle)
269
                return;
270
        if(state == NICE_COMPONENT_STATE_READY) {
271
                /* Now we can start the DTLS handshake */
272
                JANUS_PRINT("[%"SCNu64"]   Component is ready, starting DTLS handshake...\n", handle->handle_id);
273
                janus_ice_stream *stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(stream_id));
274
                if(!stream) {
275
                        JANUS_DEBUG("[%"SCNu64"]     No stream %d??\n", handle->handle_id, stream_id);
276
                        return;
277
                }
278
                janus_ice_component *component = g_hash_table_lookup(stream->components, GUINT_TO_POINTER(component_id));
279
                if(!component) {
280
                        JANUS_DEBUG("[%"SCNu64"]     No component %d in stream %d??\n", handle->handle_id, component_id, stream_id);
281
                        return;
282
                }
283
                /* Create DTLS-SRTP context, at last */
284
                component->dtls = janus_dtls_srtp_create(component, stream->dtls_role);
285
                if(!component->dtls) {
286
                        JANUS_DEBUG("[%"SCNu64"]     No component DTLS-SRTP session??\n", handle->handle_id);
287
                        return;
288
                }
289
                /* Create retransmission timer */
290
                //~ SSL_set_mode(component->ssl, SSL_MODE_AUTO_RETRY);
291
                //~ SSL_set_read_ahead(component->ssl, 1);
292
                //~ guint id = g_timeout_add_seconds(1, janus_dtls_retry, component);
293
                GSource *source = g_timeout_source_new_seconds(1);
294
                g_source_set_callback(source, janus_dtls_retry, component->dtls, NULL);
295
                guint id = g_source_attach(source, handle->icectx);
296
                JANUS_PRINT("[%"SCNu64"] Creating retransmission timer with ID %u\n", handle->handle_id, id);
297
                /* Do DTLS handshake */
298
                janus_dtls_srtp_handshake(component->dtls);
299
        } else if(state == NICE_COMPONENT_STATE_FAILED) {
300
                if(handle && !handle->stop) {
301
                        /* FIXME Should we really give up for what may be a failure in only one of the media? */
302
                        handle->stop = 1;
303
                        janus_plugin *plugin = (janus_plugin *)handle->app;
304
                        if(plugin != NULL) {
305
                                JANUS_PRINT("[%"SCNu64"] Telling the plugin about it (%s)\n", handle->handle_id, plugin->get_name());
306
                                if(plugin && plugin->hangup_media)
307
                                        plugin->hangup_media(handle->app_handle);
308
                        }
309
                }
310
        }
311
}
312
313
void janus_ice_cb_new_selected_pair(NiceAgent *agent, guint stream_id, guint component_id, gchar *lfoundation, gchar *rfoundation, gpointer ice) {
314
        JANUS_PRINT("New selected pair for component %d in stream %d: %s <-> %s\n", component_id, stream_id, lfoundation, rfoundation);
315
}
316
317
void janus_ice_cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id, guint len, gchar *buf, gpointer ice) {
318
        //~ JANUS_PRINT("Got data (%d bytes) for component %d in stream %d\n", len, component_id, stream_id);
319
        janus_ice_component *component = (janus_ice_component *)ice;
320
        if(!component) {
321
                JANUS_DEBUG("janus_ice_cb_nice_recv:     No component %d in stream %d??\n", component_id, stream_id);
322
                return;
323
        }
324
        janus_ice_stream *stream = component->stream;
325
        if(!stream) {
326
                JANUS_DEBUG("janus_ice_cb_nice_recv:     No stream %d??\n", stream_id);
327
                return;
328
        }
329
        janus_ice_handle *handle = stream->handle;
330
        if(!handle)
331
                return;
332
        /* What is this? */
333
        if ((*buf >= 20) && (*buf <= 64)) {
334
                //~ JANUS_PRINT("  Looks like DTLS!\n");
335
                janus_dtls_srtp_incoming_msg(component->dtls, buf, len);
336
                return;
337
        }
338
        /* Not DTLS... RTP or RTCP? (http://tools.ietf.org/html/draft-ietf-avt-rtp-and-rtcp-mux-07#page-11) */
339
        if(len < 12)
340
                return;        /* Definitely nothing useful */
341
        if(component_id == 1) {
342
                /* TODO Actually check if this is RTP or RTCP: right now we assume the first component is RTP */
343
                if(!component->dtls || !component->dtls->srtp_valid) {
344
                        JANUS_DEBUG("[%"SCNu64"]     Missing valid SRTP session, skipping...\n", handle->handle_id);
345
                } else {
346
                        int buflen = len;
347
                        err_status_t res = srtp_unprotect(component->dtls->srtp_in, buf, &buflen);
348
                        if(res != err_status_ok) {
349
                                JANUS_DEBUG("[%"SCNu64"]     SRTP unprotect error: %s (len=%d-->%d)\n", handle->handle_id, janus_get_srtp_error(res), len, buflen);
350
                        } else {
351
                                if(stream->ssrc_peer == 0) {
352
                                        rtp_header *header = (rtp_header *)buf;
353
                                        stream->ssrc_peer = ntohl(header->ssrc);
354
                                        JANUS_PRINT("[%"SCNu64"]     Peer %s SSRC: %u\n", handle->handle_id, stream->stream_id == handle->audio_id ? "audio" : "video", stream->ssrc_peer);
355
                                }
356
                                /* TODO Should we store the packet in a circular buffer, in case we get a NACK we can handle ourselves without relaying? */
357
                                janus_plugin *plugin = (janus_plugin *)handle->app;
358
                                if(plugin && plugin->incoming_rtp)
359
                                        plugin->incoming_rtp(handle->app_handle, stream->stream_id == handle->video_id ? 1 : 0, buf, buflen);
360
                        }
361
                }
362
                return;
363
        }
364
        if(component_id == 2) {
365
                /* TODO Actually check if this is RTP or RTCP: right now we assume the second component is RTCP */
366
                JANUS_PRINT("[%"SCNu64"]  Got an RTCP packet (%s stream)!\n", handle->handle_id, stream->stream_id == handle->audio_id ? "audio" : "video");
367
                if(!component->dtls || !component->dtls->srtp_valid) {
368
                        JANUS_DEBUG("[%"SCNu64"]     Missing valid SRTP session, skipping...\n", handle->handle_id);
369
                } else {
370
                        int buflen = len;
371
                        err_status_t res = srtp_unprotect_rtcp(component->dtls->srtp_in, buf, &buflen);
372
                        if(res != err_status_ok) {
373
                                JANUS_DEBUG("[%"SCNu64"]     SRTCP unprotect error: %s (len=%d-->%d)\n", handle->handle_id, janus_get_srtp_error(res), len, buflen);
374
                        } else {
375
                                GSList *nacks = janus_rtcp_get_nacks(buf, buflen);
376
                                if(nacks != NULL) {
377
                                        /* TODO Actually handle NACK */
378
                                        JANUS_PRINT("[%"SCNu64"]     Just got some NACKS we should probably handle...\n", handle->handle_id);
379
                                        JANUS_PRINT("           >>");
380
                                        GSList *list = nacks;
381
                                        while(list->next) {
382
                                                JANUS_PRINT(" %u", GPOINTER_TO_UINT(list->data));
383
                                                list = list->next;
384
                                        }
385
                                        JANUS_PRINT("\n");
386
                                }
387
                                janus_plugin *plugin = (janus_plugin *)handle->app;
388
                                if(plugin && plugin->incoming_rtcp)
389
                                        plugin->incoming_rtcp(handle->app_handle, stream->stream_id == handle->video_id ? 1 : 0, buf, buflen);
390
                        }
391
                }
392
        }
393
}
394
395
/* Thread to create agent */
396
void *janus_ice_thread(void *data) {
397
        janus_ice_handle *handle = data;
398
        JANUS_PRINT("[%"SCNu64"] ICE thread started, looping...\n", handle->handle_id);
399
        GMainLoop *loop = handle->iceloop;
400
        g_usleep (100000);
401
        g_main_loop_run (loop);
402
        if(handle->cdone == 0)
403
                handle->cdone = -1;
404
        JANUS_PRINT("[%"SCNu64"] ICE thread ended!\n", handle->handle_id);
405
        return NULL;
406
}
407
408
/* Helper: candidates */
409
void janus_ice_setup_candidate(janus_ice_handle *handle, char *sdp, guint stream_id, guint component_id)
410
{
411
        if(!handle || !handle->agent || !sdp)
412
                return;
413
        janus_ice_stream *stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(stream_id));
414
        if(!stream) {
415
                JANUS_DEBUG("[%"SCNu64"]     No stream %d??\n", handle->handle_id, stream_id);
416
                return;
417
        }
418
        janus_ice_component *component = g_hash_table_lookup(stream->components, GUINT_TO_POINTER(component_id));
419
        if(!component) {
420
                JANUS_DEBUG("[%"SCNu64"]     No component %d in stream %d??\n", handle->handle_id, component_id, stream_id);
421
                return;
422
        }
423
        NiceAgent* agent = handle->agent;
424
        /* adding a stream should cause host candidates to be generated */
425
        GSList *candidates, *i;
426
        candidates = nice_agent_get_local_candidates (agent, stream_id, component_id);
427
        JANUS_PRINT("[%"SCNu64"] We have %d candidates for Stream #%d, Component #%d\n", handle->handle_id, g_slist_length(candidates), stream_id, component_id);
428
        for (i = candidates; i; i = i->next) {
429
                NiceCandidate *c = (NiceCandidate *) i->data;
430
                JANUS_PRINT("[%"SCNu64"] Stream #%d, Component #%d\n", handle->handle_id, c->stream_id, c->component_id);
431
                gchar address[NICE_ADDRESS_STRING_LEN], base_address[NICE_ADDRESS_STRING_LEN];
432
                gint port = 0, base_port = 0;
433
                nice_address_to_string(&(c->addr), (gchar *)&address);
434
                port = nice_address_get_port(&(c->addr));
435
                nice_address_to_string(&(c->base_addr), (gchar *)&base_address);
436
                base_port = nice_address_get_port(&(c->base_addr));
437
                JANUS_PRINT("[%"SCNu64"]   Address:    %s:%d\n", handle->handle_id, address, port);
438
                JANUS_PRINT("[%"SCNu64"]   Priority:   %d\n", handle->handle_id, c->priority);
439
                JANUS_PRINT("[%"SCNu64"]   Foundation: %s\n", handle->handle_id, c->foundation);
440
                /* SDP time */
441
                gchar buffer[100];
442
                if(c->type == NICE_CANDIDATE_TYPE_HOST) {
443
                        /* 'host' candidate */
444
                        g_sprintf(buffer,
445
                                "a=candidate:%s %d %s %d %s %d typ host\r\n", 
446
                                        c->foundation,
447
                                        c->component_id,
448
                                        "udp",
449
                                        c->priority,
450
                                        address,
451
                                        port);
452
                } else if(c->type == NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) {
453
                        /* 'srflx' candidate */
454
                        nice_address_to_string(&(c->base_addr), (gchar *)&base_address);
455
                        gint base_port = nice_address_get_port(&(c->base_addr));
456
                        g_sprintf(buffer,
457
                                "a=candidate:%s %d %s %d %s %d typ srflx raddr %s rport %d\r\n", 
458
                                        c->foundation,
459
                                        c->component_id,
460
                                        "udp",
461
                                        c->priority,
462
                                        address,
463
                                        port,
464
                                        base_address,
465
                                        base_port);
466
                } else if(c->type == NICE_CANDIDATE_TYPE_PEER_REFLEXIVE) {
467
                        /* 'prflx' candidate */
468
                        g_sprintf(buffer,
469
                                "a=candidate:%s %d %s %d %s %d typ prflx raddr %s rport %d\r\n", 
470
                                        c->foundation,
471
                                        c->component_id,
472
                                        "udp",
473
                                        c->priority,
474
                                        address,
475
                                        port,
476
                                        base_address,
477
                                        base_port);
478
                } else if(c->type == NICE_CANDIDATE_TYPE_RELAYED) {
479
                        /* 'relay' candidate */
480
                        g_sprintf(buffer,
481
                                "a=candidate:%s %d %s %d %s %d typ relay raddr %s rport %d\r\n", 
482
                                        c->foundation,
483
                                        c->component_id,
484
                                        "udp",
485
                                        c->priority,
486
                                        address,
487
                                        port,
488
                                        base_address,
489
                                        base_port);
490
                }
491
                g_strlcat(sdp, buffer, BUFSIZE);
492
                JANUS_PRINT("[%"SCNu64"]     %s\n", handle->handle_id, buffer);
493
                /* RTP or RTCP? */
494
                gchar *search = NULL, replace[6];
495
                g_sprintf(replace, "%d", port);
496
                if(stream_id == handle->audio_id) {
497
                        if(component_id == 1) {
498
                                /* Audio RTP */
499
                                search = "ARTPP";
500
                        } else {
501
                                /* Audio RTCP */
502
                                search = "ARTCP";
503
                        }
504
                } else {        /* FIXME We assume this is video: there's nothing else right now */
505
                        if(component_id == 1) {
506
                                /* Video RTP */
507
                                search = "VRTPP";
508
                        } else {
509
                                /* Video RTCP */
510
                                search = "VRTCP";
511
                        }
512
                }
513
                /* FIXME This is a VERY ugly way to set ports in m-lines! */
514
                gchar *index = g_strstr_len(sdp, BUFSIZE, search);
515
                if(index) {
516
                        int j=0;
517
                        for(j=0; j<5; j++)
518
                                index[j] = replace[j];
519
                }
520
        }
521
}
522
523
void janus_ice_setup_remote_candidate(janus_ice_handle *handle, guint stream_id, guint component_id) {
524
        if(!handle || !handle->agent || !handle->streams)
525
                return;
526
        janus_ice_stream *stream = g_hash_table_lookup(handle->streams, GUINT_TO_POINTER(stream_id));
527
        if(!stream || !stream->components) {
528
                JANUS_DEBUG("[%"SCNu64"] No such stream %d: cannot setup remote candidates for component %d\n", handle->handle_id, stream_id, component_id);
529
                return;
530
        }
531
        janus_ice_component *component = g_hash_table_lookup(stream->components, GUINT_TO_POINTER(component_id));
532
        if(!component || !component->candidates) {
533
                JANUS_DEBUG("[%"SCNu64"] No such component %d in stream %d: cannot setup remote candidates\n", handle->handle_id, component_id, stream_id);
534
                return;
535
        }
536
        if(!component || !component->candidates || !component->candidates->data) {
537
                JANUS_DEBUG("[%"SCNu64"] No remote data for component %d in stream %d: was the remote SDP parsed?\n", handle->handle_id, component_id, stream_id);
538
                return;
539
        }
540
        JANUS_PRINT("[%"SCNu64"] ## Setting remote candidates: stream %d, component %d (%u in the list)\n",
541
                handle->handle_id, stream_id, component_id, g_slist_length(component->candidates));
542
        /* Add all candidates */
543
        NiceCandidate *c = NULL;
544
        GSList *gsc = component->candidates;
545
        gchar *rufrag = NULL, *rpwd = NULL;
546
        while(gsc) {
547
                c = (NiceCandidate *) gsc->data;
548
                JANUS_PRINT("[%"SCNu64"] >> Remote Stream #%d, Component #%d\n", handle->handle_id, c->stream_id, c->component_id);
549
                if(c->username && !rufrag)
550
                        rufrag = c->username;
551
                if(c->password && !rpwd)
552
                        rpwd = c->password;
553
                gchar address[NICE_ADDRESS_STRING_LEN];
554
                nice_address_to_string(&(c->addr), (gchar *)&address);
555
                gint port = nice_address_get_port(&(c->addr));
556
                JANUS_PRINT("[%"SCNu64"]   Address:    %s:%d\n", handle->handle_id, address, port);
557
                JANUS_PRINT("[%"SCNu64"]   Priority:   %d\n", handle->handle_id, c->priority);
558
                JANUS_PRINT("[%"SCNu64"]   Foundation: %s\n", handle->handle_id, c->foundation);
559
                JANUS_PRINT("[%"SCNu64"]   Username:   %s\n", handle->handle_id, c->username);
560
                JANUS_PRINT("[%"SCNu64"]   Password:   %s\n", handle->handle_id, c->password);
561
                gsc = gsc->next;
562
        }
563
        if(rufrag && rpwd) {
564
                JANUS_PRINT("[%"SCNu64"]  Setting remote credendials...\n", handle->handle_id);
565
                if(!nice_agent_set_remote_credentials(handle->agent, stream_id, rufrag, rpwd)) {
566
                        JANUS_DEBUG("[%"SCNu64"]  failed to set remote credentials!\n", handle->handle_id);
567
                }
568
        }
569
        if (nice_agent_set_remote_candidates(handle->agent, stream_id, component_id, component->candidates) < 1) {
570
                JANUS_DEBUG("[%"SCNu64"] Failed to set remote candidates :-(\n", handle->handle_id);
571
        } else {
572
                JANUS_PRINT("[%"SCNu64"] Remote candidates set!\n", handle->handle_id);
573
        }
574
}
575
576
int janus_ice_setup_local(janus_ice_handle *handle, int offer, int audio, int video) {
577
        if(!handle)
578
                return -1;
579
        JANUS_PRINT("[%"SCNu64"] Setting ICE locally: got %s (%d audios, %d videos)\n", handle->handle_id, offer ? "OFFER" : "ANSWER", audio, video);
580
        handle->stop = 0;        /* FIXME Reset handle */
581
        handle->icectx = g_main_context_new();
582
        handle->iceloop = g_main_loop_new(handle->icectx, FALSE);
583
        handle->icethread = g_thread_new("ice thread", &janus_ice_thread, handle);
584 5e9e29e0 meetecho
        /* Note: NICE_COMPATIBILITY_RFC5245 is only available in more recent versions of libnice */
585
        handle->agent = nice_agent_new(handle->icectx, NICE_COMPATIBILITY_DRAFT19);
586 be35facb meetecho
        /* Any STUN server to use? */
587
        if(janus_stun_server != NULL && janus_stun_port > 0) {
588
                g_object_set (G_OBJECT(handle->agent),
589
                        "stun-server", janus_stun_server,
590
                        "stun-server-port", janus_stun_port,
591
                        NULL);
592
        }
593
        g_object_set(G_OBJECT(handle->agent), "controlling-mode", !offer, NULL);
594
        JANUS_PRINT("[%"SCNu64"] Creating ICE agent (%s mode)\n", handle->handle_id, offer ? "controlled" : "controlling");
595
        g_signal_connect (G_OBJECT (handle->agent), "candidate-gathering-done",
596
                G_CALLBACK (janus_ice_cb_candidate_gathering_done), handle);
597
        g_signal_connect (G_OBJECT (handle->agent), "component-state-changed",
598
                G_CALLBACK (janus_ice_cb_component_state_changed), handle);
599
        g_signal_connect (G_OBJECT (handle->agent), "new-selected-pair",
600
                G_CALLBACK (janus_ice_cb_new_selected_pair), handle);
601
        /* Add one local address */
602
        NiceAddress addr_local;
603
        nice_address_init (&addr_local);
604
        nice_address_set_from_string (&addr_local, janus_get_local_ip());
605
        nice_agent_add_local_address (handle->agent, &addr_local);
606
        handle->streams_num = 0;
607
        handle->streams = g_hash_table_new(NULL, NULL);
608
        if(audio) {
609
                /* Add an audio stream */
610
                handle->streams_num++;
611
                handle->audio_id = nice_agent_add_stream (handle->agent, 2);
612
                janus_ice_stream *audio_stream = (janus_ice_stream *)calloc(1, sizeof(janus_ice_stream));
613
                if(audio_stream == NULL) {
614
                        JANUS_DEBUG("Memory error!\n");
615
                        return -1;
616
                }
617
                audio_stream->stream_id = handle->audio_id;
618
                audio_stream->handle = handle;
619
                audio_stream->cdone = 0;
620
                audio_stream->payload_type = -1;
621
                /* FIXME By default, if we're being called we're DTLS clients, but this may be changed by ICE... */
622
                audio_stream->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS;
623
                audio_stream->ssrc = 12345;        /* FIXME Should we make this dynamic? */
624
                audio_stream->ssrc_peer = 0;        /* FIXME Right now we don't know what this will be */
625
                janus_mutex_init(&audio_stream->mutex);
626
                audio_stream->components = g_hash_table_new(NULL, NULL);
627
                g_hash_table_insert(handle->streams, GUINT_TO_POINTER(handle->audio_id), audio_stream);
628
                handle->audio_stream = audio_stream;
629
                janus_ice_component *audio_rtp = (janus_ice_component *)calloc(1, sizeof(janus_ice_component));
630
                if(audio_rtp == NULL) {
631
                        JANUS_DEBUG("Memory error!\n");
632
                        return -1;
633
                }
634
                audio_rtp->stream = audio_stream;
635
                audio_rtp->candidates = NULL;
636
                janus_mutex_init(&audio_rtp->mutex);
637
                janus_ice_component *audio_rtcp = (janus_ice_component *)calloc(1, sizeof(janus_ice_component));
638
                if(audio_rtcp == NULL) {
639
                        JANUS_DEBUG("Memory error!\n");
640
                        return -1;
641
                }
642
                audio_rtcp->stream = audio_stream;
643
                audio_rtcp->candidates = NULL;
644
                janus_mutex_init(&audio_rtcp->mutex);
645
                g_hash_table_insert(audio_stream->components, GUINT_TO_POINTER(1), audio_rtp);
646
                audio_stream->rtp_component = audio_rtp;
647
                g_hash_table_insert(audio_stream->components, GUINT_TO_POINTER(2), audio_rtcp);
648
                audio_stream->rtcp_component = audio_rtcp;
649 5e9e29e0 meetecho
#ifdef HAVE_PORTRANGE
650
                /* FIXME: libnice supports this since 0.1.0, but the 0.1.3 on Fedora fails with an undefined reference! */
651
                nice_agent_set_port_range(handle->agent, handle->audio_id, 1, rtp_range_min, rtp_range_max);
652
                nice_agent_set_port_range(handle->agent, handle->audio_id, 2, rtp_range_min, rtp_range_max);
653
#endif
654 be35facb meetecho
                nice_agent_gather_candidates (handle->agent, handle->audio_id);
655
                nice_agent_attach_recv (handle->agent, handle->audio_id, 1, g_main_loop_get_context (handle->iceloop), janus_ice_cb_nice_recv, audio_rtp);
656
                nice_agent_attach_recv (handle->agent, handle->audio_id, 2, g_main_loop_get_context (handle->iceloop), janus_ice_cb_nice_recv, audio_rtcp);
657
        }
658
        if(video) {
659
                /* Add a video stream */
660
                handle->streams_num++;
661
                handle->video_id = nice_agent_add_stream (handle->agent, 2);
662
                janus_ice_stream *video_stream = (janus_ice_stream *)calloc(1, sizeof(janus_ice_stream));
663
                if(video_stream == NULL) {
664
                        JANUS_DEBUG("Memory error!\n");
665
                        return -1;
666
                }
667
                video_stream->handle = handle;
668
                video_stream->stream_id = handle->video_id;
669
                video_stream->cdone = 0;
670
                video_stream->payload_type = -1;
671
                /* FIXME By default, if we're being called we're DTLS clients, but this may be changed by ICE... */
672
                video_stream->dtls_role = offer ? JANUS_DTLS_ROLE_CLIENT : JANUS_DTLS_ROLE_ACTPASS;
673
                video_stream->ssrc = 54321;        /* FIXME Should we make this dynamic? */
674
                video_stream->ssrc_peer = 0;        /* FIXME Right now we don't know what this will be */
675
                video_stream->components = g_hash_table_new(NULL, NULL);
676
                janus_mutex_init(&video_stream->mutex);
677
                g_hash_table_insert(handle->streams, GUINT_TO_POINTER(handle->video_id), video_stream);
678
                handle->video_stream = video_stream;
679
                janus_ice_component *video_rtp = (janus_ice_component *)calloc(1, sizeof(janus_ice_component));
680
                if(video_rtp == NULL) {
681
                        JANUS_DEBUG("Memory error!\n");
682
                        return -1;
683
                }
684
                video_rtp->stream = video_stream;
685
                video_rtp->candidates = NULL;
686
                janus_mutex_init(&video_rtp->mutex);
687
                janus_ice_component *video_rtcp = (janus_ice_component *)calloc(1, sizeof(janus_ice_component));
688
                if(video_rtcp == NULL) {
689
                        JANUS_DEBUG("Memory error!\n");
690
                        return -1;
691
                }
692
                video_rtcp->stream = video_stream;
693
                video_rtcp->candidates = NULL;
694
                janus_mutex_init(&video_rtcp->mutex);
695
                g_hash_table_insert(video_stream->components, GUINT_TO_POINTER(1), video_rtp);
696
                video_stream->rtp_component = video_rtp;
697
                g_hash_table_insert(video_stream->components, GUINT_TO_POINTER(2), video_rtcp);
698
                video_stream->rtcp_component = video_rtcp;
699 5e9e29e0 meetecho
#ifdef HAVE_PORTRANGE
700
                /* FIXME: libnice supports this since 0.1.0, but the 0.1.3 on Fedora fails with an undefined reference! */
701
                nice_agent_set_port_range(handle->agent, handle->video_id, 1, rtp_range_min, rtp_range_max);
702
                nice_agent_set_port_range(handle->agent, handle->video_id, 2, rtp_range_min, rtp_range_max);
703
#endif
704 be35facb meetecho
                nice_agent_gather_candidates (handle->agent, handle->video_id);
705
                nice_agent_attach_recv (handle->agent, handle->video_id, 1, g_main_loop_get_context (handle->iceloop), janus_ice_cb_nice_recv, video_rtp);
706
                nice_agent_attach_recv (handle->agent, handle->video_id, 2, g_main_loop_get_context (handle->iceloop), janus_ice_cb_nice_recv, video_rtcp);
707
        }
708
        return 0;
709
}
710
711
void janus_ice_relay_rtp(janus_ice_handle *handle, int video, char *buf, int len) {
712
        /* TODO Should we fix something in RTP header stuff too? */
713
        if(!handle)
714
                return;
715
        janus_ice_stream *stream = video ? handle->video_stream : handle->audio_stream;
716
        if(!stream)
717
                return;
718
        janus_ice_component *component = stream->rtp_component;
719
        if(!component)
720
                return;
721
        if(!stream->cdone) {
722
                if(!stream->noerrorlog) {
723
                        JANUS_DEBUG("[%"SCNu64"]     %s candidates not gathered yet for stream??\n", handle->handle_id, video ? "video" : "audio");
724
                        stream->noerrorlog = 1;        /* Don't flood with thre same error all over again */
725
                }
726
                return;
727
        }
728
        stream->noerrorlog = 0;
729
        if(!component->dtls || !component->dtls->srtp_valid) {
730
                if(!component->noerrorlog) {
731
                        JANUS_DEBUG("[%"SCNu64"]     %s stream component has no valid SRTP session (yet?)\n", handle->handle_id, video ? "video" : "audio");
732
                        component->noerrorlog = 1;        /* Don't flood with thre same error all over again */
733
                }
734
                return;
735
        }
736
        component->noerrorlog = 0;
737
        /* FIXME Copy in a buffer and fix SSRC */
738
        char sbuf[BUFSIZE];
739
        memcpy(&sbuf, buf, len);
740
        rtp_header *header = (rtp_header *)&sbuf;
741
        header->ssrc = htonl(stream->ssrc);
742
        int protected = len;
743
        int res = srtp_protect(component->dtls->srtp_out, &sbuf, &protected);
744
        //~ JANUS_PRINT("[%"SCNu64"] ... SRTP protect %s (len=%d-->%d)...\n", handle->handle_id, janus_get_srtp_error(res), len, protected);
745
        if(res != err_status_ok) {
746
                JANUS_DEBUG("[%"SCNu64"] ... SRTP protect error... %s (len=%d-->%d)...\n", handle->handle_id, janus_get_srtp_error(res), len, protected);
747
        } else {
748
                /* Shoot! */
749
                //~ JANUS_PRINT("[%"SCNu64"] ... Sending SRTP packet (pt=%u, ssrc=%u, seq=%u, ts=%u)...\n", handle->handle_id,
750
                        //~ header->type, ntohl(header->ssrc), ntohs(header->seq_number), ntohl(header->timestamp));
751
                int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, protected, (const gchar *)&sbuf);
752
                if(sent < protected)
753
                        JANUS_DEBUG("[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, protected);
754
        }
755
}
756
757
void janus_ice_relay_rtcp(janus_ice_handle *handle, int video, char *buf, int len) {
758
        if(!handle)
759
                return;
760
        janus_ice_stream *stream = video ? handle->video_stream : handle->audio_stream;
761
        if(!stream)
762
                return;
763
        janus_ice_component *component = stream->rtcp_component;
764
        if(!component)
765
                return;
766
        if(!stream->cdone) {
767
                if(!stream->noerrorlog) {
768
                        JANUS_DEBUG("[%"SCNu64"]     %s candidates not gathered yet for stream??\n", handle->handle_id, video ? "video" : "audio");
769
                        stream->noerrorlog = 1;        /* Don't flood with thre same error all over again */
770
                }
771
                return;
772
        }
773
        stream->noerrorlog = 0;
774
        if(!component->dtls || !component->dtls->srtp_valid) {
775
                if(!component->noerrorlog) {
776
                        JANUS_DEBUG("[%"SCNu64"]     %s stream component has no valid SRTP session (yet?)\n", handle->handle_id, video ? "video" : "audio");
777
                        component->noerrorlog = 1;        /* Don't flood with thre same error all over again */
778
                }
779
                return;
780
        }
781
        component->noerrorlog = 0;
782
        /* FIXME Copy in a buffer and fix SSRC */
783
        char sbuf[BUFSIZE];
784
        memcpy(&sbuf, buf, len);
785
        /* Fix all SSRCs! */
786
        JANUS_PRINT("[%"SCNu64"] Fixing SSRCs (local %u, peer %u)\n", handle->handle_id, stream->ssrc, stream->ssrc_peer);
787
        janus_rtcp_fix_ssrc((char *)&sbuf, len, 1, stream->ssrc, stream->ssrc_peer);
788
        int protected = len;
789
        int res = srtp_protect_rtcp(component->dtls->srtp_out, &sbuf, &protected);
790
        //~ JANUS_PRINT("[%"SCNu64"] ... SRTCP protect %s (len=%d-->%d)...\n", handle->handle_id, janus_get_srtp_error(res), len, protected);
791
        if(res != err_status_ok) {
792
                JANUS_DEBUG("[%"SCNu64"] ... SRTCP protect error... %s (len=%d-->%d)...\n", handle->handle_id, janus_get_srtp_error(res), len, protected);
793
        } else {
794
                /* Shoot! */
795
                //~ JANUS_PRINT("[%"SCNu64"] ... Sending SRTCP packet (pt=%u, seq=%u, ts=%u)...\n", handle->handle_id,
796
                        //~ header->paytype, ntohs(header->seq_number), ntohl(header->timestamp));
797
                int sent = nice_agent_send(handle->agent, stream->stream_id, component->component_id, protected, (const gchar *)&sbuf);
798
                if(sent < protected)
799
                        JANUS_DEBUG("[%"SCNu64"] ... only sent %d bytes? (was %d)\n", handle->handle_id, sent, protected);
800
        }
801
}
802
803
void janus_ice_dtls_handshake_done(janus_ice_handle *handle, janus_ice_component *component) {
804
        if(!handle || !component)
805
                return;
806
        JANUS_PRINT("[%"SCNu64"] The DTLS handshake for the component %d in stream %d has been completed\n",
807
                handle->handle_id, component->component_id, component->stream_id);
808
        /* Check if all components are ready */
809
        if(handle->audio_stream) {
810
                if(handle->audio_stream->rtp_component &&  handle->audio_stream->rtp_component->dtls &&
811
                                !handle->audio_stream->rtp_component->dtls->srtp_valid) {
812
                        /* Still waiting for this component to become ready */
813
                        return;
814
                }
815
                if(handle->audio_stream->rtcp_component &&  handle->audio_stream->rtcp_component->dtls &&
816
                                !handle->audio_stream->rtcp_component->dtls->srtp_valid) {
817
                        /* Still waiting for this component to become ready */
818
                        return;
819
                }
820
        }
821
        if(handle->video_stream) {
822
                if(handle->video_stream->rtp_component &&  handle->video_stream->rtp_component->dtls &&
823
                                !handle->video_stream->rtp_component->dtls->srtp_valid) {
824
                        /* Still waiting for this component to become ready */
825
                        return;
826
                }
827
                if(handle->video_stream->rtcp_component &&  handle->video_stream->rtcp_component->dtls &&
828
                                !handle->video_stream->rtcp_component->dtls->srtp_valid) {
829
                        /* Still waiting for this component to become ready */
830
                        return;
831
                }
832
        }
833
        /* Notify the plugin that the WebRTC PeerConnection is ready to be used */
834
        janus_plugin *plugin = (janus_plugin *)handle->app;
835
        if(plugin != NULL) {
836
                JANUS_PRINT("[%"SCNu64"] Telling the plugin about it (%s)\n", handle->handle_id, plugin->get_name());
837
                if(plugin && plugin->setup_media)
838
                        plugin->setup_media(handle->app_handle);
839
        }
840
}