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 threaded-example threaded-example.c `pkg-config --cflags --libs nice`
36 * Run two clients, one controlling and one controlled:
37 * threaded-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
38 * threaded-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
47 #include <gio/gnetworking.h>
49 static GMainLoop *gloop;
50 static gchar *stun_addr = NULL;
51 static guint stun_port;
52 static gboolean controlling;
53 static gboolean exit_thread, candidate_gathering_done, negotiation_done;
54 static GMutex gather_mutex, negotiate_mutex;
55 static GCond gather_cond, negotiate_cond;
57 static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
59 static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
60 "connected", "ready", "failed"};
62 static int print_local_data(NiceAgent *agent, guint stream_id,
64 static int parse_remote_data(NiceAgent *agent, guint stream_id,
65 guint component_id, char *line);
66 static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
68 static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
69 guint component_id, gchar *lfoundation,
70 gchar *rfoundation, gpointer data);
71 static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
72 guint component_id, guint state,
74 static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
75 guint len, gchar *buf, gpointer data);
77 static void * example_thread(void *data);
80 main(int argc, char *argv[])
82 GThread *gexamplethread;
85 if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
86 fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
89 controlling = argv[1][0] - '0';
90 if (controlling != 0 && controlling != 1) {
91 fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
98 stun_port = atoi(argv[3]);
102 g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
107 gloop = g_main_loop_new(NULL, FALSE);
109 // Run the mainloop and the example thread
111 gexamplethread = g_thread_new("example thread", &example_thread, NULL);
112 g_main_loop_run (gloop);
115 g_thread_join (gexamplethread);
116 g_main_loop_unref(gloop);
122 example_thread(void *data)
125 NiceCandidate *local, *remote;
126 GIOChannel* io_stdin;
132 io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
134 io_stdin = g_io_channel_unix_new(fileno(stdin));
136 g_io_channel_set_flags (io_stdin, G_IO_FLAG_NONBLOCK, NULL);
138 // Create the nice agent
139 agent = nice_agent_new(g_main_loop_get_context (gloop),
140 NICE_COMPATIBILITY_RFC5245);
142 g_error("Failed to create agent");
144 // Set the STUN settings and controlling mode
146 g_object_set(agent, "stun-server", stun_addr, NULL);
147 g_object_set(agent, "stun-server-port", stun_port, NULL);
149 g_object_set(agent, "controlling-mode", controlling, NULL);
151 // Connect to the signals
152 g_signal_connect(agent, "candidate-gathering-done",
153 G_CALLBACK(cb_candidate_gathering_done), NULL);
154 g_signal_connect(agent, "new-selected-pair",
155 G_CALLBACK(cb_new_selected_pair), NULL);
156 g_signal_connect(agent, "component-state-changed",
157 G_CALLBACK(cb_component_state_changed), NULL);
159 // Create a new stream with one component
160 stream_id = nice_agent_add_stream(agent, 1);
162 g_error("Failed to add stream");
164 // Attach to the component to receive the data
165 // Without this call, candidates cannot be gathered
166 nice_agent_attach_recv(agent, stream_id, 1,
167 g_main_loop_get_context (gloop), cb_nice_recv, NULL);
169 // Start gathering local candidates
170 if (!nice_agent_gather_candidates(agent, stream_id))
171 g_error("Failed to start candidate gathering");
173 g_debug("waiting for candidate-gathering-done signal...");
175 g_mutex_lock(&gather_mutex);
176 while (!exit_thread && !candidate_gathering_done)
177 g_cond_wait(&gather_cond, &gather_mutex);
178 g_mutex_unlock(&gather_mutex);
182 // Candidate gathering is done. Send our local candidates on stdout
183 printf("Copy this line to remote client:\n");
185 print_local_data(agent, stream_id, 1);
188 // Listen on stdin for the remote candidate list
189 printf("Enter remote data (single line, no wrapping):\n");
192 while (!exit_thread) {
193 GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
194 if (s == G_IO_STATUS_NORMAL) {
195 // Parse remote candidate list and set it on the agent
196 rval = parse_remote_data(agent, stream_id, 1, line);
197 if (rval == EXIT_SUCCESS) {
201 fprintf(stderr, "ERROR: failed to parse remote data\n");
202 printf("Enter remote data (single line, no wrapping):\n");
207 } else if (s == G_IO_STATUS_AGAIN) {
212 g_debug("waiting for state READY or FAILED signal...");
213 g_mutex_lock(&negotiate_mutex);
214 while (!exit_thread && !negotiation_done)
215 g_cond_wait(&negotiate_cond, &negotiate_mutex);
216 g_mutex_unlock(&negotiate_mutex);
220 // Get current selected candidate pair and print IP address used
221 if (nice_agent_get_selected_pair (agent, stream_id, 1,
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");
236 while (!exit_thread) {
237 GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
238 if (s == G_IO_STATUS_NORMAL) {
239 nice_agent_send(agent, stream_id, 1, strlen(line), line);
243 } else if (s == G_IO_STATUS_AGAIN) {
246 // Ctrl-D was pressed.
247 nice_agent_send(agent, stream_id, 1, 1, "\0");
253 g_io_channel_unref (io_stdin);
254 g_object_unref(agent);
255 g_main_loop_quit (gloop);
261 cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
264 g_debug("SIGNAL candidate gathering done\n");
266 g_mutex_lock(&gather_mutex);
267 candidate_gathering_done = TRUE;
268 g_cond_signal(&gather_cond);
269 g_mutex_unlock(&gather_mutex);
273 cb_component_state_changed(NiceAgent *agent, guint stream_id,
274 guint component_id, guint state,
277 g_debug("SIGNAL: state changed %d %d %s[%d]\n",
278 stream_id, component_id, state_name[state], state);
280 if (state == NICE_COMPONENT_STATE_READY) {
281 g_mutex_lock(&negotiate_mutex);
282 negotiation_done = TRUE;
283 g_cond_signal(&negotiate_cond);
284 g_mutex_unlock(&negotiate_mutex);
285 } else if (state == NICE_COMPONENT_STATE_FAILED) {
286 g_main_loop_quit (gloop);
292 cb_new_selected_pair(NiceAgent *agent, guint stream_id,
293 guint component_id, gchar *lfoundation,
294 gchar *rfoundation, gpointer data)
296 g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
300 cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
301 guint len, gchar *buf, gpointer data)
303 if (len == 1 && buf[0] == '\0')
304 g_main_loop_quit (gloop);
306 printf("%.*s", len, buf);
310 static NiceCandidate *
311 parse_candidate(char *scand, guint stream_id)
313 NiceCandidate *cand = NULL;
314 NiceCandidateType ntype = NICE_CANDIDATE_TYPE_HOST;
315 gchar **tokens = NULL;
318 tokens = g_strsplit (scand, ",", 5);
319 for (i = 0; tokens[i]; i++);
323 for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
324 if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
329 if (i == G_N_ELEMENTS (candidate_type_name))
332 cand = nice_candidate_new(ntype);
333 cand->component_id = 1;
334 cand->stream_id = stream_id;
335 cand->transport = NICE_CANDIDATE_TRANSPORT_UDP;
336 strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION - 1);
337 cand->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
338 cand->priority = atoi (tokens[1]);
340 if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
341 g_message("failed to parse addr: %s", tokens[2]);
342 nice_candidate_free(cand);
347 nice_address_set_port(&cand->addr, atoi (tokens[3]));
357 print_local_data (NiceAgent *agent, guint stream_id, guint component_id)
359 int result = EXIT_FAILURE;
360 gchar *local_ufrag = NULL;
361 gchar *local_password = NULL;
362 gchar ipaddr[INET6_ADDRSTRLEN];
363 GSList *cands = NULL, *item;
365 if (!nice_agent_get_local_credentials(agent, stream_id,
366 &local_ufrag, &local_password))
369 cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
373 printf("%s %s", local_ufrag, local_password);
375 for (item = cands; item; item = item->next) {
376 NiceCandidate *c = (NiceCandidate *)item->data;
378 nice_address_to_string(&c->addr, ipaddr);
380 // (foundation),(prio),(addr),(port),(type)
381 printf(" %s,%u,%s,%u,%s",
385 nice_address_get_port(&c->addr),
386 candidate_type_name[c->type]);
389 result = EXIT_SUCCESS;
395 g_free(local_password);
397 g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
404 parse_remote_data(NiceAgent *agent, guint stream_id,
405 guint component_id, char *line)
407 GSList *remote_candidates = NULL;
408 gchar **line_argv = NULL;
409 const gchar *ufrag = NULL;
410 const gchar *passwd = NULL;
411 int result = EXIT_FAILURE;
414 line_argv = g_strsplit_set (line, " \t\n", 0);
415 for (i = 0; line_argv && line_argv[i]; i++) {
416 if (strlen (line_argv[i]) == 0)
419 // first two args are remote ufrag and password
421 ufrag = line_argv[i];
422 } else if (!passwd) {
423 passwd = line_argv[i];
425 // Remaining args are serialized canidates (at least one is required)
426 NiceCandidate *c = parse_candidate(line_argv[i], stream_id);
429 g_message("failed to parse candidate: %s", line_argv[i]);
432 remote_candidates = g_slist_prepend(remote_candidates, c);
435 if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
436 g_message("line must have at least ufrag, password, and one candidate");
440 if (!nice_agent_set_remote_credentials(agent, stream_id, ufrag, passwd)) {
441 g_message("failed to set remote credentials");
445 // Note: this will trigger the start of negotiation.
446 if (nice_agent_set_remote_candidates(agent, stream_id, component_id,
447 remote_candidates) < 1) {
448 g_message("failed to set remote candidates");
452 result = EXIT_SUCCESS;
455 if (line_argv != NULL)
456 g_strfreev(line_argv);
457 if (remote_candidates != NULL)
458 g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);