b4da1e23cfe1fd757e5c0acd444064541689a84c
[platform/upstream/libnice.git] / examples / simple-example.c
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 simple-example simple-example.c `pkg-config --cflags --libs nice`
35  *
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 }')
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 GIOChannel* io_stdin;
51 static guint stream_id;
52
53 static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
54
55 static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
56                                     "connected", "ready", "failed"};
57
58 static int print_local_data(NiceAgent *agent, guint stream_id,
59     guint component_id);
60 static int parse_remote_data(NiceAgent *agent, guint stream_id,
61     guint component_id, char *line);
62 static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
63     gpointer data);
64 static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
65     guint component_id, gchar *lfoundation,
66     gchar *rfoundation, gpointer data);
67 static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
68     guint component_id, guint state,
69     gpointer data);
70 static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
71     guint len, gchar *buf, gpointer data);
72 static gboolean stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
73     gpointer data);
74 static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
75     gpointer data);
76
77 int
78 main(int argc, char *argv[])
79 {
80   NiceAgent *agent;
81   gchar *stun_addr = NULL;
82   guint stun_port = 0;
83   gboolean controlling;
84
85   // Parse arguments
86   if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
87     fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
88     return EXIT_FAILURE;
89   }
90   controlling = argv[1][0] - '0';
91   if (controlling != 0 && controlling != 1) {
92     fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
93     return EXIT_FAILURE;
94   }
95
96   if (argc > 2) {
97     stun_addr = argv[2];
98     if (argc > 3)
99       stun_port = atoi(argv[3]);
100     else
101       stun_port = 3478;
102
103     g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
104   }
105
106   g_networking_init();
107
108   gloop = g_main_loop_new(NULL, FALSE);
109 #ifdef G_OS_WIN32
110   io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
111 #else
112   io_stdin = g_io_channel_unix_new(fileno(stdin));
113 #endif
114
115   // Create the nice agent
116   agent = nice_agent_new(g_main_loop_get_context (gloop),
117       NICE_COMPATIBILITY_RFC5245);
118   if (agent == NULL)
119     g_error("Failed to create agent");
120
121   // Set the STUN settings and controlling mode
122   if (stun_addr) {
123     g_object_set(agent, "stun-server", stun_addr, NULL);
124     g_object_set(agent, "stun-server-port", stun_port, NULL);
125   }
126   g_object_set(agent, "controlling-mode", controlling, NULL);
127
128   // Connect to the signals
129   g_signal_connect(agent, "candidate-gathering-done",
130       G_CALLBACK(cb_candidate_gathering_done), NULL);
131   g_signal_connect(agent, "new-selected-pair",
132       G_CALLBACK(cb_new_selected_pair), NULL);
133   g_signal_connect(agent, "component-state-changed",
134       G_CALLBACK(cb_component_state_changed), NULL);
135
136   // Create a new stream with one component
137   stream_id = nice_agent_add_stream(agent, 1);
138   if (stream_id == 0)
139     g_error("Failed to add stream");
140
141   // Attach to the component to receive the data
142   // Without this call, candidates cannot be gathered
143   nice_agent_attach_recv(agent, stream_id, 1,
144       g_main_loop_get_context (gloop), cb_nice_recv, NULL);
145
146   // Start gathering local candidates
147   if (!nice_agent_gather_candidates(agent, stream_id))
148     g_error("Failed to start candidate gathering");
149
150   g_debug("waiting for candidate-gathering-done signal...");
151
152   // Run the mainloop. Everything else will happen asynchronously
153   // when the candidates are done gathering.
154   g_main_loop_run (gloop);
155
156   g_main_loop_unref(gloop);
157   g_object_unref(agent);
158   g_io_channel_unref (io_stdin);
159
160   return EXIT_SUCCESS;
161 }
162
163 static void
164 cb_candidate_gathering_done(NiceAgent *agent, guint _stream_id,
165     gpointer data)
166 {
167
168   g_debug("SIGNAL candidate gathering done\n");
169
170   // Candidate gathering is done. Send our local candidates on stdout
171   printf("Copy this line to remote client:\n");
172   printf("\n  ");
173   print_local_data(agent, _stream_id, 1);
174   printf("\n");
175
176   // Listen on stdin for the remote candidate list
177   printf("Enter remote data (single line, no wrapping):\n");
178   g_io_add_watch(io_stdin, G_IO_IN, stdin_remote_info_cb, agent);
179   printf("> ");
180   fflush (stdout);
181 }
182
183 static gboolean
184 stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
185     gpointer data)
186 {
187   NiceAgent *agent = data;
188   gchar *line = NULL;
189   int rval;
190   gboolean ret = TRUE;
191
192   if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
193       G_IO_STATUS_NORMAL) {
194
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) {
198       // Return FALSE so we stop listening to stdin since we parsed the
199       // candidates correctly
200       ret = FALSE;
201       g_debug("waiting for state READY or FAILED signal...");
202     } else {
203       fprintf(stderr, "ERROR: failed to parse remote data\n");
204       printf("Enter remote data (single line, no wrapping):\n");
205       printf("> ");
206       fflush (stdout);
207     }
208     g_free (line);
209   }
210
211   return ret;
212 }
213
214 static void
215 cb_component_state_changed(NiceAgent *agent, guint _stream_id,
216     guint component_id, guint state,
217     gpointer data)
218 {
219
220   g_debug("SIGNAL: state changed %d %d %s[%d]\n",
221       _stream_id, component_id, state_name[state], state);
222
223   if (state == NICE_COMPONENT_STATE_CONNECTED) {
224     NiceCandidate *local, *remote;
225
226     // Get current selected candidate pair and print IP address used
227     if (nice_agent_get_selected_pair (agent, _stream_id, component_id,
228                 &local, &remote)) {
229       gchar ipaddr[INET6_ADDRSTRLEN];
230
231       nice_address_to_string(&local->addr, ipaddr);
232       printf("\nNegotiation complete: ([%s]:%d,",
233           ipaddr, nice_address_get_port(&local->addr));
234       nice_address_to_string(&remote->addr, ipaddr);
235       printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr));
236     }
237
238     // Listen to stdin and send data written to it
239     printf("\nSend lines to remote (Ctrl-D to quit):\n");
240     g_io_add_watch(io_stdin, G_IO_IN, stdin_send_data_cb, agent);
241     printf("> ");
242     fflush (stdout);
243   } else if (state == NICE_COMPONENT_STATE_FAILED) {
244     g_main_loop_quit (gloop);
245   }
246 }
247
248 static gboolean
249 stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
250     gpointer data)
251 {
252   NiceAgent *agent = data;
253   gchar *line = NULL;
254
255   if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
256       G_IO_STATUS_NORMAL) {
257     nice_agent_send(agent, stream_id, 1, strlen(line), line);
258     g_free (line);
259     printf("> ");
260     fflush (stdout);
261   } else {
262     nice_agent_send(agent, stream_id, 1, 1, "\0");
263     // Ctrl-D was pressed.
264     g_main_loop_quit (gloop);
265   }
266
267   return TRUE;
268 }
269
270 static void
271 cb_new_selected_pair(NiceAgent *agent, guint _stream_id,
272     guint component_id, gchar *lfoundation,
273     gchar *rfoundation, gpointer data)
274 {
275   g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
276 }
277
278 static void
279 cb_nice_recv(NiceAgent *agent, guint _stream_id, guint component_id,
280     guint len, gchar *buf, gpointer data)
281 {
282   if (len == 1 && buf[0] == '\0')
283     g_main_loop_quit (gloop);
284   printf("%.*s", len, buf);
285   fflush(stdout);
286 }
287
288 static NiceCandidate *
289 parse_candidate(char *scand, guint _stream_id)
290 {
291   NiceCandidate *cand = NULL;
292   NiceCandidateType ntype = NICE_CANDIDATE_TYPE_HOST;
293   gchar **tokens = NULL;
294   guint i;
295
296   tokens = g_strsplit (scand, ",", 5);
297   for (i = 0; tokens[i]; i++);
298   if (i != 5)
299     goto end;
300
301   for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
302     if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
303       ntype = i;
304       break;
305     }
306   }
307   if (i == G_N_ELEMENTS (candidate_type_name))
308     goto end;
309
310   cand = nice_candidate_new(ntype);
311   cand->component_id = 1;
312   cand->stream_id = _stream_id;
313   cand->transport = NICE_CANDIDATE_TRANSPORT_UDP;
314   strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION - 1);
315   cand->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
316   cand->priority = atoi (tokens[1]);
317
318   if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
319     g_message("failed to parse addr: %s", tokens[2]);
320     nice_candidate_free(cand);
321     cand = NULL;
322     goto end;
323   }
324
325   nice_address_set_port(&cand->addr, atoi (tokens[3]));
326
327  end:
328   g_strfreev(tokens);
329
330   return cand;
331 }
332
333
334 static int
335 print_local_data (NiceAgent *agent, guint _stream_id, guint component_id)
336 {
337   int result = EXIT_FAILURE;
338   gchar *local_ufrag = NULL;
339   gchar *local_password = NULL;
340   gchar ipaddr[INET6_ADDRSTRLEN];
341   GSList *cands = NULL, *item;
342
343   if (!nice_agent_get_local_credentials(agent, _stream_id,
344       &local_ufrag, &local_password))
345     goto end;
346
347   cands = nice_agent_get_local_candidates(agent, _stream_id, component_id);
348   if (cands == NULL)
349     goto end;
350
351   printf("%s %s", local_ufrag, local_password);
352
353   for (item = cands; item; item = item->next) {
354     NiceCandidate *c = (NiceCandidate *)item->data;
355
356     nice_address_to_string(&c->addr, ipaddr);
357
358     // (foundation),(prio),(addr),(port),(type)
359     printf(" %s,%u,%s,%u,%s",
360         c->foundation,
361         c->priority,
362         ipaddr,
363         nice_address_get_port(&c->addr),
364         candidate_type_name[c->type]);
365   }
366   printf("\n");
367   result = EXIT_SUCCESS;
368
369  end:
370   if (local_ufrag)
371     g_free(local_ufrag);
372   if (local_password)
373     g_free(local_password);
374   if (cands)
375     g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
376
377   return result;
378 }
379
380
381 static int
382 parse_remote_data(NiceAgent *agent, guint _stream_id,
383     guint component_id, char *line)
384 {
385   GSList *remote_candidates = NULL;
386   gchar **line_argv = NULL;
387   const gchar *ufrag = NULL;
388   const gchar *passwd = NULL;
389   int result = EXIT_FAILURE;
390   int i;
391
392   line_argv = g_strsplit_set (line, " \t\n", 0);
393   for (i = 0; line_argv && line_argv[i]; i++) {
394     if (strlen (line_argv[i]) == 0)
395       continue;
396
397     // first two args are remote ufrag and password
398     if (!ufrag) {
399       ufrag = line_argv[i];
400     } else if (!passwd) {
401       passwd = line_argv[i];
402     } else {
403       // Remaining args are serialized canidates (at least one is required)
404       NiceCandidate *c = parse_candidate(line_argv[i], _stream_id);
405
406       if (c == NULL) {
407         g_message("failed to parse candidate: %s", line_argv[i]);
408         goto end;
409       }
410       remote_candidates = g_slist_prepend(remote_candidates, c);
411     }
412   }
413   if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
414     g_message("line must have at least ufrag, password, and one candidate");
415     goto end;
416   }
417
418   if (!nice_agent_set_remote_credentials(agent, _stream_id, ufrag, passwd)) {
419     g_message("failed to set remote credentials");
420     goto end;
421   }
422
423   // Note: this will trigger the start of negotiation.
424   if (nice_agent_set_remote_candidates(agent, _stream_id, component_id,
425       remote_candidates) < 1) {
426     g_message("failed to set remote candidates");
427     goto end;
428   }
429
430   result = EXIT_SUCCESS;
431
432  end:
433   if (line_argv != NULL)
434     g_strfreev(line_argv);
435   if (remote_candidates != NULL)
436     g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
437
438   return result;
439 }