2
* Copyright 2013 University of Chicago
4
* Copyright 2013 Collabora Ltd.
5
* Contact: Youness Alaoui
7
* The contents of this file are subject to the Mozilla Public License Version
8
* 1.1 (the "License"); you may not use this file except in compliance with
9
* the License. You may obtain a copy of the License at
10
* http://www.mozilla.org/MPL/
12
* Software distributed under the License is distributed on an "AS IS" basis,
13
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14
* for the specific language governing rights and limitations under the
17
* Alternatively, the contents of this file may be used under the terms of the
18
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
19
* case the provisions of LGPL are applicable instead of those above. If you
20
* wish to allow use of your version of this file only under the terms of the
21
* LGPL and not to allow others to use your version of this file under the
22
* MPL, indicate your decision by deleting the provisions above and replace
23
* them with the notice and other provisions required by the LGPL. If you do
24
* not delete the provisions above, a recipient may use your version of this
25
* file under either the MPL or the LGPL.
29
* Example using libnice to negotiate a UDP connection between two clients,
30
* possibly on the same network or behind different NATs and/or stateful
34
* gcc -o simple-example simple-example.c `pkg-config --cflags --libs nice`
36
* Run two clients, one controlling and one controlled:
37
* simple-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
38
* simple-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
47
static GMainLoop *gloop;
48
static GIOChannel* io_stdin;
49
static guint stream_id;
51
static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
53
static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
54
"connected", "ready", "failed"};
56
static int print_local_data(NiceAgent *agent, guint stream_id,
58
static int parse_remote_data(NiceAgent *agent, guint stream_id,
59
guint component_id, char *line);
60
static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
62
static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
63
guint component_id, gchar *lfoundation,
64
gchar *rfoundation, gpointer data);
65
static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
66
guint component_id, guint state,
68
static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
69
guint len, gchar *buf, gpointer data);
70
static gboolean stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
72
static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
76
main(int argc, char *argv[])
79
gchar *stun_addr = NULL;
84
if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
85
fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
88
controlling = argv[1][0] - '0';
89
if (controlling != 0 && controlling != 1) {
90
fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
97
stun_port = atoi(argv[3]);
101
g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
106
gloop = g_main_loop_new(NULL, FALSE);
107
io_stdin = g_io_channel_unix_new(fileno(stdin));
109
// Create the nice agent
110
agent = nice_agent_new(g_main_loop_get_context (gloop),
111
NICE_COMPATIBILITY_RFC5245);
113
g_error("Failed to create agent");
115
// Set the STUN settings and controlling mode
117
g_object_set(G_OBJECT(agent), "stun-server", stun_addr, NULL);
118
g_object_set(G_OBJECT(agent), "stun-server-port", stun_port, NULL);
120
g_object_set(G_OBJECT(agent), "controlling-mode", controlling, NULL);
122
// Connect to the signals
123
g_signal_connect(G_OBJECT(agent), "candidate-gathering-done",
124
G_CALLBACK(cb_candidate_gathering_done), NULL);
125
g_signal_connect(G_OBJECT(agent), "new-selected-pair",
126
G_CALLBACK(cb_new_selected_pair), NULL);
127
g_signal_connect(G_OBJECT(agent), "component-state-changed",
128
G_CALLBACK(cb_component_state_changed), NULL);
130
// Create a new stream with one component
131
stream_id = nice_agent_add_stream(agent, 1);
133
g_error("Failed to add stream");
135
// Attach to the component to receive the data
136
// Without this call, candidates cannot be gathered
137
nice_agent_attach_recv(agent, stream_id, 1,
138
g_main_loop_get_context (gloop), cb_nice_recv, NULL);
140
// Start gathering local candidates
141
if (!nice_agent_gather_candidates(agent, stream_id))
142
g_error("Failed to start candidate gathering");
144
g_debug("waiting for candidate-gathering-done signal...");
146
// Run the mainloop. Everything else will happen asynchronously
147
// when the candidates are done gathering.
148
g_main_loop_run (gloop);
150
g_main_loop_unref(gloop);
151
g_object_unref(agent);
152
g_io_channel_unref (io_stdin);
158
cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
162
g_debug("SIGNAL candidate gathering done\n");
164
// Candidate gathering is done. Send our local candidates on stdout
165
printf("Copy this line to remote client:\n");
167
print_local_data(agent, stream_id, 1);
170
// Listen on stdin for the remote candidate list
171
printf("Enter remote data (single line, no wrapping):\n");
172
g_io_add_watch(io_stdin, G_IO_IN, stdin_remote_info_cb, agent);
178
stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
181
NiceAgent *agent = data;
186
if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
187
G_IO_STATUS_NORMAL) {
189
// Parse remote candidate list and set it on the agent
190
rval = parse_remote_data(agent, stream_id, 1, line);
191
if (rval == EXIT_SUCCESS) {
192
// Return FALSE so we stop listening to stdin since we parsed the
193
// candidates correctly
195
g_debug("waiting for state READY or FAILED signal...");
197
fprintf(stderr, "ERROR: failed to parse remote data\n");
198
printf("Enter remote data (single line, no wrapping):\n");
209
cb_component_state_changed(NiceAgent *agent, guint stream_id,
210
guint component_id, guint state,
214
g_debug("SIGNAL: state changed %d %d %s[%d]\n",
215
stream_id, component_id, state_name[state], state);
217
if (state == NICE_COMPONENT_STATE_READY) {
218
NiceCandidate *local, *remote;
220
// Get current selected candidate pair and print IP address used
221
if (nice_agent_get_selected_pair (agent, stream_id, component_id,
223
gchar ipaddr[INET6_ADDRSTRLEN];
225
nice_address_to_string(&local->addr, ipaddr);
226
printf("\nNegotiation complete: ([%s]:%d,",
227
ipaddr, nice_address_get_port(&local->addr));
228
nice_address_to_string(&remote->addr, ipaddr);
229
printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr));
232
// Listen to stdin and send data written to it
233
printf("\nSend lines to remote (Ctrl-D to quit):\n");
234
g_io_add_watch(io_stdin, G_IO_IN, stdin_send_data_cb, agent);
237
} else if (state == NICE_COMPONENT_STATE_FAILED) {
238
g_main_loop_quit (gloop);
243
stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
246
NiceAgent *agent = data;
249
if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
250
G_IO_STATUS_NORMAL) {
251
nice_agent_send(agent, stream_id, 1, strlen(line), line);
256
nice_agent_send(agent, stream_id, 1, 1, "\0");
257
// Ctrl-D was pressed.
258
g_main_loop_quit (gloop);
265
cb_new_selected_pair(NiceAgent *agent, guint stream_id,
266
guint component_id, gchar *lfoundation,
267
gchar *rfoundation, gpointer data)
269
g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
273
cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
274
guint len, gchar *buf, gpointer data)
276
if (len == 1 && buf[0] == '\0')
277
g_main_loop_quit (gloop);
278
printf("%.*s", len, buf);
282
static NiceCandidate *
283
parse_candidate(char *scand, guint stream_id)
285
NiceCandidate *cand = NULL;
286
NiceCandidateType ntype;
287
gchar **tokens = NULL;
290
tokens = g_strsplit (scand, ",", 5);
291
for (i = 0; tokens && tokens[i]; i++);
295
for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
296
if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
301
if (i == G_N_ELEMENTS (candidate_type_name))
304
cand = nice_candidate_new(ntype);
305
cand->component_id = 1;
306
cand->stream_id = stream_id;
307
cand->transport = NICE_CANDIDATE_TRANSPORT_UDP;
308
strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION);
309
cand->priority = atoi (tokens[1]);
311
if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
312
g_message("failed to parse addr: %s", tokens[2]);
313
nice_candidate_free(cand);
318
nice_address_set_port(&cand->addr, atoi (tokens[3]));
328
print_local_data (NiceAgent *agent, guint stream_id, guint component_id)
330
int result = EXIT_FAILURE;
331
gchar *local_ufrag = NULL;
332
gchar *local_password = NULL;
333
gchar ipaddr[INET6_ADDRSTRLEN];
334
GSList *cands = NULL, *item;
336
if (!nice_agent_get_local_credentials(agent, stream_id,
337
&local_ufrag, &local_password))
340
cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
344
printf("%s %s", local_ufrag, local_password);
346
for (item = cands; item; item = item->next) {
347
NiceCandidate *c = (NiceCandidate *)item->data;
349
nice_address_to_string(&c->addr, ipaddr);
351
// (foundation),(prio),(addr),(port),(type)
352
printf(" %s,%u,%s,%u,%s",
356
nice_address_get_port(&c->addr),
357
candidate_type_name[c->type]);
360
result = EXIT_SUCCESS;
366
g_free(local_password);
368
g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
375
parse_remote_data(NiceAgent *agent, guint stream_id,
376
guint component_id, char *line)
378
GSList *remote_candidates = NULL;
379
gchar **line_argv = NULL;
380
const gchar *ufrag = NULL;
381
const gchar *passwd = NULL;
382
int result = EXIT_FAILURE;
385
line_argv = g_strsplit_set (line, " \t\n", 0);
386
for (i = 0; line_argv && line_argv[i]; i++) {
387
if (strlen (line_argv[i]) == 0)
390
// first two args are remote ufrag and password
392
ufrag = line_argv[i];
393
} else if (!passwd) {
394
passwd = line_argv[i];
396
// Remaining args are serialized canidates (at least one is required)
397
NiceCandidate *c = parse_candidate(line_argv[i], stream_id);
400
g_message("failed to parse candidate: %s", line_argv[i]);
403
remote_candidates = g_slist_prepend(remote_candidates, c);
406
if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
407
g_message("line must have at least ufrag, password, and one candidate");
411
if (!nice_agent_set_remote_credentials(agent, stream_id, ufrag, passwd)) {
412
g_message("failed to set remote credentials");
416
// Note: this will trigger the start of negotiation.
417
if (nice_agent_set_remote_candidates(agent, stream_id, component_id,
418
remote_candidates) < 1) {
419
g_message("failed to set remote candidates");
423
result = EXIT_SUCCESS;
426
if (line_argv != NULL)
427
g_strfreev(line_argv);
428
if (remote_candidates != NULL)
429
g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);