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 sdp-example sdp-example.c `pkg-config --cflags --libs nice`
36 * Run two clients, one controlling and one controlled:
37 * sdp-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
38 * sdp-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 *state_name[] = {"disconnected", "gathering", "connecting",
58 "connected", "ready", "failed"};
60 static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
62 static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
63 guint component_id, guint state,
65 static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
66 guint len, gchar *buf, gpointer data);
68 static void * example_thread(void *data);
71 main(int argc, char *argv[])
73 GThread *gexamplethread;
76 if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
77 fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
80 controlling = argv[1][0] - '0';
81 if (controlling != 0 && controlling != 1) {
82 fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
89 stun_port = atoi(argv[3]);
93 g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
98 gloop = g_main_loop_new(NULL, FALSE);
100 // Run the mainloop and the example thread
102 gexamplethread = g_thread_new("example thread", &example_thread, NULL);
103 g_main_loop_run (gloop);
106 g_thread_join (gexamplethread);
107 g_main_loop_unref(gloop);
113 example_thread(void *data)
116 GIOChannel* io_stdin;
122 io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
124 io_stdin = g_io_channel_unix_new(fileno(stdin));
126 g_io_channel_set_flags(io_stdin, G_IO_FLAG_NONBLOCK, NULL);
128 // Create the nice agent
129 agent = nice_agent_new(g_main_loop_get_context (gloop),
130 NICE_COMPATIBILITY_RFC5245);
132 g_error("Failed to create agent");
134 // Set the STUN settings and controlling mode
136 g_object_set(agent, "stun-server", stun_addr, NULL);
137 g_object_set(agent, "stun-server-port", stun_port, NULL);
139 g_object_set(agent, "controlling-mode", controlling, NULL);
141 // Connect to the signals
142 g_signal_connect(agent, "candidate-gathering-done",
143 G_CALLBACK(cb_candidate_gathering_done), NULL);
144 g_signal_connect(agent, "component-state-changed",
145 G_CALLBACK(cb_component_state_changed), NULL);
147 // Create a new stream with one component
148 stream_id = nice_agent_add_stream(agent, 1);
150 g_error("Failed to add stream");
151 nice_agent_set_stream_name (agent, stream_id, "text");
153 // Attach to the component to receive the data
154 // Without this call, candidates cannot be gathered
155 nice_agent_attach_recv(agent, stream_id, 1,
156 g_main_loop_get_context (gloop), cb_nice_recv, NULL);
158 // Start gathering local candidates
159 if (!nice_agent_gather_candidates(agent, stream_id))
160 g_error("Failed to start candidate gathering");
162 g_debug("waiting for candidate-gathering-done signal...");
164 g_mutex_lock(&gather_mutex);
165 while (!exit_thread && !candidate_gathering_done)
166 g_cond_wait(&gather_cond, &gather_mutex);
167 g_mutex_unlock(&gather_mutex);
171 // Candidate gathering is done. Send our local candidates on stdout
172 sdp = nice_agent_generate_local_sdp (agent);
173 printf("Generated SDP from agent :\n%s\n\n", sdp);
174 printf("Copy the following line to remote client:\n");
175 sdp64 = g_base64_encode ((const guchar *)sdp, strlen (sdp));
176 printf("\n %s\n", sdp64);
180 // Listen on stdin for the remote candidate list
181 printf("Enter remote data (single line, no wrapping):\n");
184 while (!exit_thread) {
185 GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
186 if (s == G_IO_STATUS_NORMAL) {
189 sdp = (gchar *) g_base64_decode (line, &sdp_len);
190 // Parse remote candidate list and set it on the agent
191 if (sdp && nice_agent_parse_remote_sdp (agent, sdp) > 0) {
196 fprintf(stderr, "ERROR: failed to parse remote data\n");
197 printf("Enter remote data (single line, no wrapping):\n");
203 } else if (s == G_IO_STATUS_AGAIN) {
208 g_debug("waiting for state READY or FAILED signal...");
209 g_mutex_lock(&negotiate_mutex);
210 while (!exit_thread && !negotiation_done)
211 g_cond_wait(&negotiate_cond, &negotiate_mutex);
212 g_mutex_unlock(&negotiate_mutex);
216 // Listen to stdin and send data written to it
217 printf("\nSend lines to remote (Ctrl-D to quit):\n");
220 while (!exit_thread) {
221 GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
223 if (s == G_IO_STATUS_NORMAL) {
224 nice_agent_send(agent, stream_id, 1, strlen(line), line);
228 } else if (s == G_IO_STATUS_AGAIN) {
231 // Ctrl-D was pressed.
232 nice_agent_send(agent, stream_id, 1, 1, "\0");
238 g_object_unref(agent);
239 g_io_channel_unref (io_stdin);
240 g_main_loop_quit (gloop);
246 cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
249 g_debug("SIGNAL candidate gathering done\n");
251 g_mutex_lock(&gather_mutex);
252 candidate_gathering_done = TRUE;
253 g_cond_signal(&gather_cond);
254 g_mutex_unlock(&gather_mutex);
258 cb_component_state_changed(NiceAgent *agent, guint stream_id,
259 guint component_id, guint state,
262 g_debug("SIGNAL: state changed %d %d %s[%d]\n",
263 stream_id, component_id, state_name[state], state);
265 if (state == NICE_COMPONENT_STATE_READY) {
266 g_mutex_lock(&negotiate_mutex);
267 negotiation_done = TRUE;
268 g_cond_signal(&negotiate_cond);
269 g_mutex_unlock(&negotiate_mutex);
270 } else if (state == NICE_COMPONENT_STATE_FAILED) {
271 g_main_loop_quit (gloop);
276 cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
277 guint len, gchar *buf, gpointer data)
279 if (len == 1 && buf[0] == '\0')
280 g_main_loop_quit (gloop);
282 printf("%.*s", len, buf);