1 /*
2 * Copyright 2013 University of Chicago
3 * Contact: Bryce Allen
4 * Copyright 2013 Collabora Ltd.
5 * Contact: Youness Alaoui
6 *
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/
11 *
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
15 * License.
16 *
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.
26 */
27
28 /*
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
31 * firewalls.
32 *
33 * Build:
34 * gcc -o sdp-example sdp-example.c `pkg-config --cflags --libs nice`
35 *
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 }')
39 */
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <ctype.h>
44
45 #include <agent.h>
46
47 #include <gio/gnetworking.h>
48
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;
56
57 static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
58 "connected", "ready", "failed"};
59
60 static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
61 gpointer data);
62 static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
63 guint component_id, guint state,
64 gpointer data);
65 static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
66 guint len, gchar *buf, gpointer data);
67
68 static void * example_thread(void *data);
69
70 int
main(int argc,char * argv[])71 main(int argc, char *argv[])
72 {
73 GThread *gexamplethread;
74
75 // Parse arguments
76 if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
77 fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
78 return EXIT_FAILURE;
79 }
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]);
83 return EXIT_FAILURE;
84 }
85
86 if (argc > 2) {
87 stun_addr = argv[2];
88 if (argc > 3)
89 stun_port = atoi(argv[3]);
90 else
91 stun_port = 3478;
92
93 g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
94 }
95
96 g_networking_init();
97
98 gloop = g_main_loop_new(NULL, FALSE);
99
100 // Run the mainloop and the example thread
101 exit_thread = FALSE;
102 gexamplethread = g_thread_new("example thread", &example_thread, NULL);
103 g_main_loop_run (gloop);
104 exit_thread = TRUE;
105
106 g_thread_join (gexamplethread);
107 g_main_loop_unref(gloop);
108
109 return EXIT_SUCCESS;
110 }
111
112 static void *
example_thread(void * data)113 example_thread(void *data)
114 {
115 NiceAgent *agent;
116 GIOChannel* io_stdin;
117 guint stream_id;
118 gchar *line = NULL;
119 gchar *sdp, *sdp64;
120
121 #ifdef G_OS_WIN32
122 io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
123 #else
124 io_stdin = g_io_channel_unix_new(fileno(stdin));
125 #endif
126 g_io_channel_set_flags(io_stdin, G_IO_FLAG_NONBLOCK, NULL);
127
128 // Create the nice agent
129 agent = nice_agent_new(g_main_loop_get_context (gloop),
130 NICE_COMPATIBILITY_RFC5245);
131 if (agent == NULL)
132 g_error("Failed to create agent");
133
134 // Set the STUN settings and controlling mode
135 if (stun_addr) {
136 g_object_set(agent, "stun-server", stun_addr, NULL);
137 g_object_set(agent, "stun-server-port", stun_port, NULL);
138 }
139 g_object_set(agent, "controlling-mode", controlling, NULL);
140
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);
146
147 // Create a new stream with one component
148 stream_id = nice_agent_add_stream(agent, 1);
149 if (stream_id == 0)
150 g_error("Failed to add stream");
151 nice_agent_set_stream_name (agent, stream_id, "text");
152
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);
157
158 // Start gathering local candidates
159 if (!nice_agent_gather_candidates(agent, stream_id))
160 g_error("Failed to start candidate gathering");
161
162 g_debug("waiting for candidate-gathering-done signal...");
163
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);
168 if (exit_thread)
169 goto end;
170
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);
177 g_free (sdp);
178 g_free (sdp64);
179
180 // Listen on stdin for the remote candidate list
181 printf("Enter remote data (single line, no wrapping):\n");
182 printf("> ");
183 fflush (stdout);
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) {
187 gsize sdp_len;
188
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) {
192 g_free (sdp);
193 g_free (line);
194 break;
195 } else {
196 fprintf(stderr, "ERROR: failed to parse remote data\n");
197 printf("Enter remote data (single line, no wrapping):\n");
198 printf("> ");
199 fflush (stdout);
200 }
201 g_free (sdp);
202 g_free (line);
203 } else if (s == G_IO_STATUS_AGAIN) {
204 g_usleep (100000);
205 }
206 }
207
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);
213 if (exit_thread)
214 goto end;
215
216 // Listen to stdin and send data written to it
217 printf("\nSend lines to remote (Ctrl-D to quit):\n");
218 printf("> ");
219 fflush (stdout);
220 while (!exit_thread) {
221 GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
222
223 if (s == G_IO_STATUS_NORMAL) {
224 nice_agent_send(agent, stream_id, 1, strlen(line), line);
225 g_free (line);
226 printf("> ");
227 fflush (stdout);
228 } else if (s == G_IO_STATUS_AGAIN) {
229 g_usleep (100000);
230 } else {
231 // Ctrl-D was pressed.
232 nice_agent_send(agent, stream_id, 1, 1, "\0");
233 break;
234 }
235 }
236
237 end:
238 g_object_unref(agent);
239 g_io_channel_unref (io_stdin);
240 g_main_loop_quit (gloop);
241
242 return NULL;
243 }
244
245 static void
cb_candidate_gathering_done(NiceAgent * agent,guint stream_id,gpointer data)246 cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
247 gpointer data)
248 {
249 g_debug("SIGNAL candidate gathering done\n");
250
251 g_mutex_lock(&gather_mutex);
252 candidate_gathering_done = TRUE;
253 g_cond_signal(&gather_cond);
254 g_mutex_unlock(&gather_mutex);
255 }
256
257 static void
cb_component_state_changed(NiceAgent * agent,guint stream_id,guint component_id,guint state,gpointer data)258 cb_component_state_changed(NiceAgent *agent, guint stream_id,
259 guint component_id, guint state,
260 gpointer data)
261 {
262 g_debug("SIGNAL: state changed %d %d %s[%d]\n",
263 stream_id, component_id, state_name[state], state);
264
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);
272 }
273 }
274
275 static void
cb_nice_recv(NiceAgent * agent,guint stream_id,guint component_id,guint len,gchar * buf,gpointer data)276 cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
277 guint len, gchar *buf, gpointer data)
278 {
279 if (len == 1 && buf[0] == '\0')
280 g_main_loop_quit (gloop);
281
282 printf("%.*s", len, buf);
283 fflush(stdout);
284 }
285