~ubuntu-branches/debian/experimental/libnice/experimental

« back to all changes in this revision

Viewing changes to examples/simple-example.c

  • Committer: Package Import Robot
  • Author(s): Simon McVittie
  • Date: 2013-06-06 10:41:08 UTC
  • mfrom: (1.4.1)
  • mto: (10.1.6 sid) (1.4.2)
  • mto: This revision was merged to the branch mainline in revision 14.
  • Revision ID: package-import@ubuntu.com-20130606104108-f8m5io9wnt7bswc2
Tags: upstream-0.1.4
ImportĀ upstreamĀ versionĀ 0.1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
static GMainLoop *gloop;
 
48
static GIOChannel* io_stdin;
 
49
static guint stream_id;
 
50
 
 
51
static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
 
52
 
 
53
static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
 
54
                                    "connected", "ready", "failed"};
 
55
 
 
56
static int print_local_data(NiceAgent *agent, guint stream_id,
 
57
    guint component_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,
 
61
    gpointer data);
 
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,
 
67
    gpointer data);
 
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,
 
71
    gpointer data);
 
72
static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
 
73
    gpointer data);
 
74
 
 
75
int
 
76
main(int argc, char *argv[])
 
77
{
 
78
  NiceAgent *agent;
 
79
  gchar *stun_addr = NULL;
 
80
  guint stun_port;
 
81
  gboolean controlling;
 
82
 
 
83
  // Parse arguments
 
84
  if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
 
85
    fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
 
86
    return EXIT_FAILURE;
 
87
  }
 
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]);
 
91
    return EXIT_FAILURE;
 
92
  }
 
93
 
 
94
  if (argc > 2) {
 
95
    stun_addr = argv[2];
 
96
    if (argc > 3)
 
97
      stun_port = atoi(argv[3]);
 
98
    else
 
99
      stun_port = 3478;
 
100
 
 
101
    g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
 
102
  }
 
103
 
 
104
  g_type_init();
 
105
 
 
106
  gloop = g_main_loop_new(NULL, FALSE);
 
107
  io_stdin = g_io_channel_unix_new(fileno(stdin));
 
108
 
 
109
  // Create the nice agent
 
110
  agent = nice_agent_new(g_main_loop_get_context (gloop),
 
111
      NICE_COMPATIBILITY_RFC5245);
 
112
  if (agent == NULL)
 
113
    g_error("Failed to create agent");
 
114
 
 
115
  // Set the STUN settings and controlling mode
 
116
  if (stun_addr) {
 
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);
 
119
  }
 
120
  g_object_set(G_OBJECT(agent), "controlling-mode", controlling, NULL);
 
121
 
 
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);
 
129
 
 
130
  // Create a new stream with one component
 
131
  stream_id = nice_agent_add_stream(agent, 1);
 
132
  if (stream_id == 0)
 
133
    g_error("Failed to add stream");
 
134
 
 
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);
 
139
 
 
140
  // Start gathering local candidates
 
141
  if (!nice_agent_gather_candidates(agent, stream_id))
 
142
    g_error("Failed to start candidate gathering");
 
143
 
 
144
  g_debug("waiting for candidate-gathering-done signal...");
 
145
 
 
146
  // Run the mainloop. Everything else will happen asynchronously
 
147
  // when the candidates are done gathering.
 
148
  g_main_loop_run (gloop);
 
149
 
 
150
  g_main_loop_unref(gloop);
 
151
  g_object_unref(agent);
 
152
  g_io_channel_unref (io_stdin);
 
153
 
 
154
  return EXIT_SUCCESS;
 
155
}
 
156
 
 
157
static void
 
158
cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
 
159
    gpointer data)
 
160
{
 
161
 
 
162
  g_debug("SIGNAL candidate gathering done\n");
 
163
 
 
164
  // Candidate gathering is done. Send our local candidates on stdout
 
165
  printf("Copy this line to remote client:\n");
 
166
  printf("\n  ");
 
167
  print_local_data(agent, stream_id, 1);
 
168
  printf("\n");
 
169
 
 
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);
 
173
  printf("> ");
 
174
  fflush (stdout);
 
175
}
 
176
 
 
177
static gboolean
 
178
stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
 
179
    gpointer data)
 
180
{
 
181
  NiceAgent *agent = data;
 
182
  gchar *line = NULL;
 
183
  int rval;
 
184
  gboolean ret = TRUE;
 
185
 
 
186
  if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
 
187
      G_IO_STATUS_NORMAL) {
 
188
 
 
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
 
194
      ret = FALSE;
 
195
      g_debug("waiting for state READY or FAILED signal...");
 
196
    } else {
 
197
      fprintf(stderr, "ERROR: failed to parse remote data\n");
 
198
      printf("Enter remote data (single line, no wrapping):\n");
 
199
      printf("> ");
 
200
      fflush (stdout);
 
201
    }
 
202
    g_free (line);
 
203
  }
 
204
 
 
205
  return ret;
 
206
}
 
207
 
 
208
static void
 
209
cb_component_state_changed(NiceAgent *agent, guint stream_id,
 
210
    guint component_id, guint state,
 
211
    gpointer data)
 
212
{
 
213
 
 
214
  g_debug("SIGNAL: state changed %d %d %s[%d]\n",
 
215
      stream_id, component_id, state_name[state], state);
 
216
 
 
217
  if (state == NICE_COMPONENT_STATE_READY) {
 
218
    NiceCandidate *local, *remote;
 
219
 
 
220
    // Get current selected candidate pair and print IP address used
 
221
    if (nice_agent_get_selected_pair (agent, stream_id, component_id,
 
222
                &local, &remote)) {
 
223
      gchar ipaddr[INET6_ADDRSTRLEN];
 
224
 
 
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));
 
230
    }
 
231
 
 
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);
 
235
    printf("> ");
 
236
    fflush (stdout);
 
237
  } else if (state == NICE_COMPONENT_STATE_FAILED) {
 
238
    g_main_loop_quit (gloop);
 
239
  }
 
240
}
 
241
 
 
242
static gboolean
 
243
stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
 
244
    gpointer data)
 
245
{
 
246
  NiceAgent *agent = data;
 
247
  gchar *line = NULL;
 
248
 
 
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);
 
252
    g_free (line);
 
253
    printf("> ");
 
254
    fflush (stdout);
 
255
  } else {
 
256
    nice_agent_send(agent, stream_id, 1, 1, "\0");
 
257
    // Ctrl-D was pressed.
 
258
    g_main_loop_quit (gloop);
 
259
  }
 
260
 
 
261
  return TRUE;
 
262
}
 
263
 
 
264
static void
 
265
cb_new_selected_pair(NiceAgent *agent, guint stream_id,
 
266
    guint component_id, gchar *lfoundation,
 
267
    gchar *rfoundation, gpointer data)
 
268
{
 
269
  g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
 
270
}
 
271
 
 
272
static void
 
273
cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
 
274
    guint len, gchar *buf, gpointer data)
 
275
{
 
276
  if (len == 1 && buf[0] == '\0')
 
277
    g_main_loop_quit (gloop);
 
278
  printf("%.*s", len, buf);
 
279
  fflush(stdout);
 
280
}
 
281
 
 
282
static NiceCandidate *
 
283
parse_candidate(char *scand, guint stream_id)
 
284
{
 
285
  NiceCandidate *cand = NULL;
 
286
  NiceCandidateType ntype;
 
287
  gchar **tokens = NULL;
 
288
  guint i;
 
289
 
 
290
  tokens = g_strsplit (scand, ",", 5);
 
291
  for (i = 0; tokens && tokens[i]; i++);
 
292
  if (i != 5)
 
293
    goto end;
 
294
 
 
295
  for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
 
296
    if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
 
297
      ntype = i;
 
298
      break;
 
299
    }
 
300
  }
 
301
  if (i == G_N_ELEMENTS (candidate_type_name))
 
302
    goto end;
 
303
 
 
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]);
 
310
 
 
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);
 
314
    cand = NULL;
 
315
    goto end;
 
316
  }
 
317
 
 
318
  nice_address_set_port(&cand->addr, atoi (tokens[3]));
 
319
 
 
320
 end:
 
321
  g_strfreev(tokens);
 
322
 
 
323
  return cand;
 
324
}
 
325
 
 
326
 
 
327
static int
 
328
print_local_data (NiceAgent *agent, guint stream_id, guint component_id)
 
329
{
 
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;
 
335
 
 
336
  if (!nice_agent_get_local_credentials(agent, stream_id,
 
337
      &local_ufrag, &local_password))
 
338
    goto end;
 
339
 
 
340
  cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
 
341
  if (cands == NULL)
 
342
    goto end;
 
343
 
 
344
  printf("%s %s", local_ufrag, local_password);
 
345
 
 
346
  for (item = cands; item; item = item->next) {
 
347
    NiceCandidate *c = (NiceCandidate *)item->data;
 
348
 
 
349
    nice_address_to_string(&c->addr, ipaddr);
 
350
 
 
351
    // (foundation),(prio),(addr),(port),(type)
 
352
    printf(" %s,%u,%s,%u,%s",
 
353
        c->foundation,
 
354
        c->priority,
 
355
        ipaddr,
 
356
        nice_address_get_port(&c->addr),
 
357
        candidate_type_name[c->type]);
 
358
  }
 
359
  printf("\n");
 
360
  result = EXIT_SUCCESS;
 
361
 
 
362
 end:
 
363
  if (local_ufrag)
 
364
    g_free(local_ufrag);
 
365
  if (local_password)
 
366
    g_free(local_password);
 
367
  if (cands)
 
368
    g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
 
369
 
 
370
  return result;
 
371
}
 
372
 
 
373
 
 
374
static int
 
375
parse_remote_data(NiceAgent *agent, guint stream_id,
 
376
    guint component_id, char *line)
 
377
{
 
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;
 
383
  int i;
 
384
 
 
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)
 
388
      continue;
 
389
 
 
390
    // first two args are remote ufrag and password
 
391
    if (!ufrag) {
 
392
      ufrag = line_argv[i];
 
393
    } else if (!passwd) {
 
394
      passwd = line_argv[i];
 
395
    } else {
 
396
      // Remaining args are serialized canidates (at least one is required)
 
397
      NiceCandidate *c = parse_candidate(line_argv[i], stream_id);
 
398
 
 
399
      if (c == NULL) {
 
400
        g_message("failed to parse candidate: %s", line_argv[i]);
 
401
        goto end;
 
402
      }
 
403
      remote_candidates = g_slist_prepend(remote_candidates, c);
 
404
    }
 
405
  }
 
406
  if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
 
407
    g_message("line must have at least ufrag, password, and one candidate");
 
408
    goto end;
 
409
  }
 
410
 
 
411
  if (!nice_agent_set_remote_credentials(agent, stream_id, ufrag, passwd)) {
 
412
    g_message("failed to set remote credentials");
 
413
    goto end;
 
414
  }
 
415
 
 
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");
 
420
    goto end;
 
421
  }
 
422
 
 
423
  result = EXIT_SUCCESS;
 
424
 
 
425
 end:
 
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);
 
430
 
 
431
  return result;
 
432
}