simple-example.c 12.7 KB
Newer Older
1 2 3 4 5 6
/*
 * Copyright 2013 University of Chicago
 *  Contact: Bryce Allen
 * Copyright 2013 Collabora Ltd.
 *  Contact: Youness Alaoui
 *
7 8 9 10
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
11
 *
12 13 14 15
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
16
 *
17 18 19 20 21 22 23 24 25
 * Alternatively, the contents of this file may be used under the terms of the
 * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
 * case the provisions of LGPL are applicable instead of those above. If you
 * wish to allow use of your version of this file only under the terms of the
 * LGPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replace
 * them with the notice and other provisions required by the LGPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the LGPL.
26 27 28 29 30 31 32 33
 */

/*
 * Example using libnice to negotiate a UDP connection between two clients,
 * possibly on the same network or behind different NATs and/or stateful
 * firewalls.
 *
 * 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 38
 *   simple-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
 *   simple-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
39 40 41 42 43 44 45 46
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <agent.h>

47
#include <gio/gnetworking.h>
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
static GMainLoop *gloop;
static GIOChannel* io_stdin;
static guint stream_id;

static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};

static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
                                    "connected", "ready", "failed"};

static int print_local_data(NiceAgent *agent, guint stream_id,
    guint component_id);
static int parse_remote_data(NiceAgent *agent, guint stream_id,
    guint component_id, char *line);
static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
    gpointer data);
static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
    guint component_id, gchar *lfoundation,
    gchar *rfoundation, gpointer data);
static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
    guint component_id, guint state,
    gpointer data);
static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
    guint len, gchar *buf, gpointer data);
static gboolean stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
    gpointer data);
static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
    gpointer data);

int
main(int argc, char *argv[])
{
  NiceAgent *agent;
  gchar *stun_addr = NULL;
82
  guint stun_port = 0;
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
  gboolean controlling;

  // Parse arguments
  if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
    fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
    return EXIT_FAILURE;
  }
  controlling = argv[1][0] - '0';
  if (controlling != 0 && controlling != 1) {
    fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
    return EXIT_FAILURE;
  }

  if (argc > 2) {
    stun_addr = argv[2];
    if (argc > 3)
      stun_port = atoi(argv[3]);
    else
      stun_port = 3478;

    g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
  }

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 116 117 118 119 120 121 122

  // Create the nice agent
  agent = nice_agent_new(g_main_loop_get_context (gloop),
      NICE_COMPATIBILITY_RFC5245);
  if (agent == NULL)
    g_error("Failed to create agent");

  // Set the STUN settings and controlling mode
  if (stun_addr) {
123 124
    g_object_set(agent, "stun-server", stun_addr, NULL);
    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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
      G_CALLBACK(cb_component_state_changed), NULL);

  // Create a new stream with one component
  stream_id = nice_agent_add_stream(agent, 1);
  if (stream_id == 0)
    g_error("Failed to add stream");

  // Attach to the component to receive the data
  // Without this call, candidates cannot be gathered
  nice_agent_attach_recv(agent, stream_id, 1,
      g_main_loop_get_context (gloop), cb_nice_recv, NULL);

  // Start gathering local candidates
  if (!nice_agent_gather_candidates(agent, stream_id))
    g_error("Failed to start candidate gathering");

  g_debug("waiting for candidate-gathering-done signal...");

  // Run the mainloop. Everything else will happen asynchronously
  // when the candidates are done gathering.
  g_main_loop_run (gloop);

  g_main_loop_unref(gloop);
  g_object_unref(agent);
  g_io_channel_unref (io_stdin);

  return EXIT_SUCCESS;
}

static void
Philip Withnall's avatar
Philip Withnall committed
164
cb_candidate_gathering_done(NiceAgent *agent, guint _stream_id,
165 166 167 168 169 170 171 172
    gpointer data)
{

  g_debug("SIGNAL candidate gathering done\n");

  // Candidate gathering is done. Send our local candidates on stdout
  printf("Copy this line to remote client:\n");
  printf("\n  ");
Philip Withnall's avatar
Philip Withnall committed
173
  print_local_data(agent, _stream_id, 1);
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
  printf("\n");

  // Listen on stdin for the remote candidate list
  printf("Enter remote data (single line, no wrapping):\n");
  g_io_add_watch(io_stdin, G_IO_IN, stdin_remote_info_cb, agent);
  printf("> ");
  fflush (stdout);
}

static gboolean
stdin_remote_info_cb (GIOChannel *source, GIOCondition cond,
    gpointer data)
{
  NiceAgent *agent = data;
  gchar *line = NULL;
  int rval;
  gboolean ret = TRUE;

  if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
      G_IO_STATUS_NORMAL) {

    // Parse remote candidate list and set it on the agent
    rval = parse_remote_data(agent, stream_id, 1, line);
    if (rval == EXIT_SUCCESS) {
      // Return FALSE so we stop listening to stdin since we parsed the
      // candidates correctly
      ret = FALSE;
      g_debug("waiting for state READY or FAILED signal...");
    } else {
      fprintf(stderr, "ERROR: failed to parse remote data\n");
      printf("Enter remote data (single line, no wrapping):\n");
      printf("> ");
      fflush (stdout);
    }
    g_free (line);
  }

  return ret;
}

static void
Philip Withnall's avatar
Philip Withnall committed
215
cb_component_state_changed(NiceAgent *agent, guint _stream_id,
216 217 218 219 220
    guint component_id, guint state,
    gpointer data)
{

  g_debug("SIGNAL: state changed %d %d %s[%d]\n",
Philip Withnall's avatar
Philip Withnall committed
221
      _stream_id, component_id, state_name[state], state);
222

223
  if (state == NICE_COMPONENT_STATE_CONNECTED) {
224 225 226
    NiceCandidate *local, *remote;

    // Get current selected candidate pair and print IP address used
Philip Withnall's avatar
Philip Withnall committed
227
    if (nice_agent_get_selected_pair (agent, _stream_id, component_id,
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
                &local, &remote)) {
      gchar ipaddr[INET6_ADDRSTRLEN];

      nice_address_to_string(&local->addr, ipaddr);
      printf("\nNegotiation complete: ([%s]:%d,",
          ipaddr, nice_address_get_port(&local->addr));
      nice_address_to_string(&remote->addr, ipaddr);
      printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr));
    }

    // Listen to stdin and send data written to it
    printf("\nSend lines to remote (Ctrl-D to quit):\n");
    g_io_add_watch(io_stdin, G_IO_IN, stdin_send_data_cb, agent);
    printf("> ");
    fflush (stdout);
  } else if (state == NICE_COMPONENT_STATE_FAILED) {
    g_main_loop_quit (gloop);
  }
}

static gboolean
stdin_send_data_cb (GIOChannel *source, GIOCondition cond,
    gpointer data)
{
  NiceAgent *agent = data;
  gchar *line = NULL;

  if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) ==
      G_IO_STATUS_NORMAL) {
    nice_agent_send(agent, stream_id, 1, strlen(line), line);
    g_free (line);
    printf("> ");
    fflush (stdout);
261 262 263 264
  } else {
    nice_agent_send(agent, stream_id, 1, 1, "\0");
    // Ctrl-D was pressed.
    g_main_loop_quit (gloop);
265 266 267 268 269 270
  }

  return TRUE;
}

static void
Philip Withnall's avatar
Philip Withnall committed
271
cb_new_selected_pair(NiceAgent *agent, guint _stream_id,
272 273 274 275 276 277 278
    guint component_id, gchar *lfoundation,
    gchar *rfoundation, gpointer data)
{
  g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
}

static void
Philip Withnall's avatar
Philip Withnall committed
279
cb_nice_recv(NiceAgent *agent, guint _stream_id, guint component_id,
280 281
    guint len, gchar *buf, gpointer data)
{
282 283
  if (len == 1 && buf[0] == '\0')
    g_main_loop_quit (gloop);
284 285 286 287 288
  printf("%.*s", len, buf);
  fflush(stdout);
}

static NiceCandidate *
Philip Withnall's avatar
Philip Withnall committed
289
parse_candidate(char *scand, guint _stream_id)
290 291
{
  NiceCandidate *cand = NULL;
292
  NiceCandidateType ntype = NICE_CANDIDATE_TYPE_HOST;
293 294 295 296
  gchar **tokens = NULL;
  guint i;

  tokens = g_strsplit (scand, ",", 5);
297
  for (i = 0; tokens[i]; i++);
298 299 300 301 302 303 304 305 306 307 308 309 310 311
  if (i != 5)
    goto end;

  for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
    if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
      ntype = i;
      break;
    }
  }
  if (i == G_N_ELEMENTS (candidate_type_name))
    goto end;

  cand = nice_candidate_new(ntype);
  cand->component_id = 1;
Philip Withnall's avatar
Philip Withnall committed
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 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  cand->priority = atoi (tokens[1]);

  if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
    g_message("failed to parse addr: %s", tokens[2]);
    nice_candidate_free(cand);
    cand = NULL;
    goto end;
  }

  nice_address_set_port(&cand->addr, atoi (tokens[3]));

 end:
  g_strfreev(tokens);

  return cand;
}


static int
Philip Withnall's avatar
Philip Withnall committed
335
print_local_data (NiceAgent *agent, guint _stream_id, guint component_id)
336 337 338 339 340 341 342
{
  int result = EXIT_FAILURE;
  gchar *local_ufrag = NULL;
  gchar *local_password = NULL;
  gchar ipaddr[INET6_ADDRSTRLEN];
  GSList *cands = NULL, *item;

Philip Withnall's avatar
Philip Withnall committed
343
  if (!nice_agent_get_local_credentials(agent, _stream_id,
344 345 346
      &local_ufrag, &local_password))
    goto end;

Philip Withnall's avatar
Philip Withnall committed
347
  cands = nice_agent_get_local_candidates(agent, _stream_id, component_id);
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
  if (cands == NULL)
    goto end;

  printf("%s %s", local_ufrag, local_password);

  for (item = cands; item; item = item->next) {
    NiceCandidate *c = (NiceCandidate *)item->data;

    nice_address_to_string(&c->addr, ipaddr);

    // (foundation),(prio),(addr),(port),(type)
    printf(" %s,%u,%s,%u,%s",
        c->foundation,
        c->priority,
        ipaddr,
        nice_address_get_port(&c->addr),
        candidate_type_name[c->type]);
  }
  printf("\n");
  result = EXIT_SUCCESS;

 end:
  if (local_ufrag)
    g_free(local_ufrag);
  if (local_password)
    g_free(local_password);
  if (cands)
    g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);

  return result;
}


static int
Philip Withnall's avatar
Philip Withnall committed
382
parse_remote_data(NiceAgent *agent, guint _stream_id,
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
    guint component_id, char *line)
{
  GSList *remote_candidates = NULL;
  gchar **line_argv = NULL;
  const gchar *ufrag = NULL;
  const gchar *passwd = NULL;
  int result = EXIT_FAILURE;
  int i;

  line_argv = g_strsplit_set (line, " \t\n", 0);
  for (i = 0; line_argv && line_argv[i]; i++) {
    if (strlen (line_argv[i]) == 0)
      continue;

    // first two args are remote ufrag and password
    if (!ufrag) {
      ufrag = line_argv[i];
    } else if (!passwd) {
      passwd = line_argv[i];
    } else {
      // Remaining args are serialized canidates (at least one is required)
Philip Withnall's avatar
Philip Withnall committed
404
      NiceCandidate *c = parse_candidate(line_argv[i], _stream_id);
405 406 407 408 409 410 411 412 413 414 415 416 417

      if (c == NULL) {
        g_message("failed to parse candidate: %s", line_argv[i]);
        goto end;
      }
      remote_candidates = g_slist_prepend(remote_candidates, c);
    }
  }
  if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
    g_message("line must have at least ufrag, password, and one candidate");
    goto end;
  }

Philip Withnall's avatar
Philip Withnall committed
418
  if (!nice_agent_set_remote_credentials(agent, _stream_id, ufrag, passwd)) {
419 420 421 422 423
    g_message("failed to set remote credentials");
    goto end;
  }

  // Note: this will trigger the start of negotiation.
Philip Withnall's avatar
Philip Withnall committed
424
  if (nice_agent_set_remote_candidates(agent, _stream_id, component_id,
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
      remote_candidates) < 1) {
    g_message("failed to set remote candidates");
    goto end;
  }

  result = EXIT_SUCCESS;

 end:
  if (line_argv != NULL)
    g_strfreev(line_argv);
  if (remote_candidates != NULL)
    g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);

  return result;
}