stunc.c 11.9 KB
Newer Older
Pekka Pessi's avatar
Pekka Pessi committed
1 2 3
/*
 * This file is part of the Sofia-SIP package
 *
4
 * Copyright (C) 2005,2006 Nokia Corporation.
Pekka Pessi's avatar
Pekka Pessi committed
5 6 7
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
8
 * This library is free software; you can redistribute it and/or
Pekka Pessi's avatar
Pekka Pessi committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**
26
 * STUN test client
27
 *
Pekka Pessi's avatar
Pekka Pessi committed
28
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
29 30
 * @author Martti Mela <Martti.Mela@nokia.com>
 * @author Kai Vehmanen <Kai.Vehmanen@nokia.com>
31
 *
Pekka Pessi's avatar
Pekka Pessi committed
32 33 34
 * @date Created: Thu Jul 24 17:21:00 2003 ppessi
 */

35
/**@page stunc STUN test client.
36
 *
37 38 39 40 41
 * @section stunc_synopsis Synopsis
 * <tt>stunc [OPTIONS] \<stun-server-address\></tt>
 *
 * @section stunc_description Description
 * The @em stunc utility can be used to gather information about possible
42
 * NAT devices that are located between the client and STUN server.
43 44 45 46 47 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
 *
 * @em stunc can provide the following information: the IP address and
 * port as seen by the STUN server, detecting presence of NATs, and
 * hints on the type of address translation done. It should be noted
 * that the results of NAT type and life-time detection should be
 * considered as hints. There is no guarantee that NAT(s) will handle
 * future packets in the same way.
 *
 * @section stunc_options Command Line Options
 * The @em stunc utility accepts following command line options:
 *
 * <dl>
 *
 * <dt>-b</dt>
 * <dd>Perform a STUN binding discovery. @em stunc will report the
 * client transport address (IP:port) as seen by the STUN server. In
 * the presence of NATs, this address is allocated by the NAT closest
 * to the STUN server.
 * </dd>
 *
 * <dt>-l</dt>
 * <dd>Perform a STUN binding life-time check.
 * </dd>
 *
 * <dt>-n</dt>
 * <dd>Perform a STUN binding type check. Notice that the results
 * are only hints. Nondeterministic behaviour, resource exhaustion,
 * or reboots of network elements can cause changes in NAT behaviour
 * between successive runs of stunc.
 * </dd>
 *
 * <dt>-r</dt>
 * <dd>Randomize the local port. Otherwise @em stunc let's the
 * operating system select a free port.
 * </dd>
 *
 * <dt>-s</dt>
 * <dd>Request a shared-secret over TLS. Tests whether the STUN server
81
 * supports the shared-secret mechanism (needed to protect message
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
 * integrity). Can be combined with @em -b, @em -l and @em -n.
 * </dd>
 *
 * </dl>
 *
 * @section stunc_return Return Codes
 * <table>
 * <tr><td>0</td><td>when successful</td></tr>
 * <tr><td>1</td><td>when any errors detected</td></tr>
 * </table>
 *
 * @section stunc_examples Examples
 *
 * Discover the NAT binding, use a random local port:
 * @code
 * $ stunc stunserver.org -b -r
 * @endcode
 *
 * @section stunc_environment Environment
 * #STUN_DEBUG
102
 *
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
 * @section stunc_bugs Reporting Bugs
 * Report bugs to <sofia-sip-devel@lists.sourceforge.net>.
 *
 * @section stunc_author Authors
 * - Pekka Pessi <pekka -dot pessi -at- nokia -dot- com>
 * - Martti Mela <martti -dot mela -at- nokia -dot- com>
 * - Kai Vehmanen <kai -dot vehmanen -at- nokia -dot- com>
 *
 * @section stunc_copyright Copyright
 * Copyright (C) 2005,2006 Nokia Corporation.
 *
 * This program is free software; see the source for copying conditions.
 * There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.
 */

119
#include "config.h"
Pekka Pessi's avatar
Pekka Pessi committed
120 121 122 123

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
124
#include <time.h>
Pekka Pessi's avatar
Pekka Pessi committed
125

126 127 128
typedef struct stunc_s stunc_t;
#define SU_ROOT_MAGIC  stunc_t
#define STUN_MAGIC_T   stunc_t
129
#define STUN_DISCOVERY_MAGIC_T  stunc_t
130

131 132
#include "sofia-sip/stun.h"
#include "sofia-sip/stun_tag.h"
133
#include "sofia-sip/sofia_features.h"
134
#include <sofia-sip/su.h>
135

136 137 138 139 140
enum {
  do_secret = 1,
  do_bind = 2,
  do_nat_check = 4,
  do_life_check = 8,
141
  do_randomize_port = 16
142 143
};

144 145 146 147
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
148
static char const __func__[] = "stunc";
149
#endif
150

151
#ifndef SU_DEBUG
152
#define SU_DEBUG 0
153 154
#endif
#define SU_LOG (stun_log)
155
#include <sofia-sip/su_debug.h>
Pekka Pessi's avatar
Pekka Pessi committed
156

157
void usage(char *name)
Pekka Pessi's avatar
Pekka Pessi committed
158
{
159
  fprintf(stderr,
160
	  "stunc (%s)\n"
161 162 163 164
	  "usage: %s <server> [-b] [-n] [-l] [-r] [-s]\n"
	  "  -b\tmake a binding request\n"
	  "  -l\tperform NAT lifetime check\n"
	  "  -n\tperform NAT type check\n"
165
	  "  -r\trandomize the local port\n",
166
	  "  -s\trequest shared-secret over TLS (combined with -[bln])\n"
167
	  SOFIA_SIP_NAME_VERSION, name);
168
  exit(1);
Pekka Pessi's avatar
Pekka Pessi committed
169 170
}

171
struct stunc_s {
172
  su_socket_t  sc_socket;
173
  int          sc_flags;
174 175 176
};


177 178 179 180 181 182 183
static
void stunc_lifetime_cb(stunc_t *stunc,
		       stun_handle_t *sh,
		       stun_discovery_t *sd,
		       stun_action_t action,
		       stun_state_t event);

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
static
void stunc_nattype_cb(stunc_t *stunc,
		      stun_handle_t *sh,
		      stun_discovery_t *sd,
		      stun_action_t action,
		      stun_state_t event);

static
void stunc_bind_cb(stunc_t *stunc,
		   stun_handle_t *sh,
		   stun_discovery_t *sd,
		   stun_action_t action,
		   stun_state_t event);

static
void stunc_ss_cb(stunc_t *stunc,
		 stun_handle_t *sh,
		 stun_discovery_t *sd,
		 stun_action_t action,
		 stun_state_t event)
204
{
205
  int err;
Martti Mela's avatar
Martti Mela committed
206
  SU_DEBUG_3(("%s: %s\n", __func__, stun_str_state(event)));
Martti Mela's avatar
Martti Mela committed
207

208 209
  stunc->sc_flags &= ~do_secret;
  if (!stunc->sc_flags)
210
    su_root_break(stun_root(sh));
211

212 213
  switch (event) {
  case stun_tls_done:
214 215 216 217 218
    if (stunc->sc_flags & do_bind) {
      err = stun_bind(sh, stunc_bind_cb, stunc,
		      STUNTAG_SOCKET(stunc->sc_socket),
		      STUNTAG_REGISTER_EVENTS(1),
		      TAG_NULL());
219

220 221
      if (err < 0) {
	SU_DEBUG_0(("%s: %s  failed\n", __func__, "stun_handle_bind()"));
222
	su_root_break(stun_root(sh));
223
      }
224
    }
225
    break;
226

227
  case stun_tls_connection_failed:
228
    SU_DEBUG_0(("%s: Obtaining shared secret failed.\n",
229
		__func__));
230
    stunc->sc_flags &= ~do_bind;
231
    if (!stunc->sc_flags)
232
      su_root_break(stun_root(sh));
233

234 235
    break;

236
  case stun_tls_connection_timeout:
237
    SU_DEBUG_0(("%s: Timeout when obtaining shared secret.\n",
238
		__func__));
239
    stunc->sc_flags &= ~do_bind;
240
    break;
241

242
  default:
243
    break;
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
  }

  return;
}


static
void stunc_bind_cb(stunc_t *stunc,
		   stun_handle_t *sh,
		   stun_discovery_t *sd,
		   stun_action_t action,
		   stun_state_t event)
{
  su_sockaddr_t sa[1];
  char ipaddr[48];
  socklen_t addrlen;

  SU_DEBUG_3(("%s: %s\n", __func__, stun_str_state(event)));

263 264 265
  stunc->sc_flags &= ~do_bind;

  if (!stunc->sc_flags)
266
    su_root_break(stun_root(sh));
267

268
  switch (event) {
269
  case stun_discovery_done:
270 271
    addrlen = sizeof(*sa);
    memset(sa, 0, addrlen);
272

273 274
    if (stun_discovery_get_address(sd, sa, &addrlen) < 0) {
      SU_DEBUG_0(("%s: stun_discovery_get_address() failed", __func__));
275
      return;
276 277
    }

Martti Mela's avatar
Martti Mela committed
278
    SU_DEBUG_0(("%s: local address NATed as %s:%u\n", __func__,
279 280
		su_inet_ntop(sa->su_family, SU_ADDR(sa),
			     ipaddr, sizeof(ipaddr)),
Martti Mela's avatar
Martti Mela committed
281
		(unsigned) ntohs(sa->su_port)));
282

283 284
  break;

285
  case stun_discovery_timeout:
286
  case stun_discovery_error:
287 288
  case stun_error:
  default:
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
    break;
  }

  return;
}


static
void stunc_nattype_cb(stunc_t *stunc,
		      stun_handle_t *sh,
		      stun_discovery_t *sd,
		      stun_action_t action,
		      stun_state_t event)
{
  SU_DEBUG_3(("%s: %s\n", __func__, stun_str_state(event)));

305 306 307
  stunc->sc_flags &= ~do_nat_check;

  if (!stunc->sc_flags)
308
    su_root_break(stun_root(sh));
309

310
  switch (event) {
311
  case stun_discovery_timeout:
312
    SU_DEBUG_3(("%s: NAT type determination timeout.\n", __func__));
313 314 315
    break;

  case stun_discovery_done:
316
    SU_DEBUG_3(("%s: NAT type determined to be '%s' (%d).\n",
317
		__func__, stun_nattype_str(sd), (int)stun_nattype(sd)));
318
    break;
319 320 321

  case stun_error:
  default:
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
    break;
  }

  return;
}


static
void stunc_lifetime_cb(stunc_t *stunc,
		       stun_handle_t *sh,
		       stun_discovery_t *sd,
		       stun_action_t action,
		       stun_state_t event)
{
  SU_DEBUG_3(("%s: %s\n", __func__, stun_str_state(event)));

  stunc->sc_flags &= ~do_life_check;

  if (!stunc->sc_flags)
341
    su_root_break(stun_root(sh));
342 343 344 345 346 347 348 349 350 351 352 353

  switch (event) {
  case stun_discovery_timeout:
    SU_DEBUG_3(("%s: Lifetime determination timeout.\n", __func__));
    break;

  case stun_discovery_done:
    SU_DEBUG_3(("%s: Lifetime determined to be %d.\n", __func__, stun_lifetime(sd)));
    break;

  case stun_error:
  default:
354 355
    break;
  }
Martti Mela's avatar
Martti Mela committed
356

357
  return;
358 359 360
}


Pekka Pessi's avatar
Pekka Pessi committed
361 362
int main(int argc, char *argv[])
{
363
  int err = 0, i, sflags = 0;
364
  stunc_t stunc[1];
365
  su_root_t *root;
366
  stun_handle_t *sh;
367 368
  su_socket_t s;

369
  if (su_init() != 0)
370 371 372 373
    return -1;

  root = su_root_create(stunc);

374
  if (argc < 3)
375 376 377 378 379 380 381 382 383 384 385
    usage(argv[0]);

  for (i = 2; argv[i]; i++) {
    if (strcmp(argv[i], "-s") == 0)
      sflags |= do_secret;
    else if (strcmp(argv[i], "-b") == 0)
      sflags |= do_bind;
    else if (strcmp(argv[i], "-n") == 0)
      sflags |= do_nat_check;
    else if (strcmp(argv[i], "-l") == 0)
      sflags |= do_life_check;
386 387
    else if (strcmp(argv[i], "-r") == 0)
      sflags |= do_randomize_port;
388 389
    else {
      fprintf(stderr, "Unable to parse option %s.\n", argv[i]);
390
      usage(argv[0]);
391
    }
392
  }
Martti Mela's avatar
Martti Mela committed
393

Pekka Pessi's avatar
Pekka Pessi committed
394
  /* Running this test requires a local STUN server on default port */
395
  sh = stun_handle_init(root,
396
			STUNTAG_SERVER(argv[1]),
397
			STUNTAG_REQUIRE_INTEGRITY(sflags & do_secret),
398
			TAG_NULL());
399

400
  if (!sh) {
401
    SU_DEBUG_0(("%s: %s failed\n", __func__, "stun_handle_init()"));
402 403
    return -1;
  }
Pekka Pessi's avatar
Pekka Pessi committed
404

405
  s = su_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Martti Mela's avatar
Martti Mela committed
406
  if (s == -1) {
407 408
    SU_DEBUG_0(("%s: %s  failed: %s\n", __func__,
		"su_socket()", su_gli_strerror(errno)));
Martti Mela's avatar
Martti Mela committed
409 410
    return -1;
  }
Pekka Pessi's avatar
Pekka Pessi committed
411

412
  stunc->sc_socket = s;
413
  stunc->sc_flags = sflags;
414

415 416 417 418 419 420
  if (sflags & do_randomize_port) {
    su_sockaddr_t sockaddr;
    char ipaddr[SU_ADDRSIZE + 2] = { 0 };
    socklen_t socklen = sizeof(sockaddr);

    srand((unsigned int)time((time_t *)NULL));
421 422

    memset(&sockaddr, 0, sizeof(su_sockaddr_t));
423
    sockaddr.su_port = htons((rand() % (65536 - 1024)) + 1024);
424
    sockaddr.su_family = AF_INET;
425 426

    SU_DEBUG_3(("stunc: Binding to local port %u.\n", ntohs(sockaddr.su_port)));
427

428
    err = bind(s, (struct sockaddr *)&sockaddr, socklen);
429 430
    if (err < 0) {
      SU_DEBUG_1(("%s: Error %d binding to %s:%u\n", __func__, err,
431 432
		  su_inet_ntop(sockaddr.su_family, SU_ADDR(&sockaddr),
			       ipaddr, sizeof(ipaddr)),
433 434 435 436 437 438 439
		  (unsigned) ntohs(sockaddr.su_port)));
      return -1;
    }

    stunc->sc_flags &= ~do_randomize_port;
  }

440
  if (sflags & do_secret) {
441
    if (stun_obtain_shared_secret(sh, stunc_ss_cb, stunc, TAG_NULL()) < 0) {
442 443
      SU_DEBUG_3(("%s: %s failed\n", __func__,
		  "stun_handle_request_shared_secret()"));
444 445 446 447
      return -1;
    }
  }

448

449 450 451 452 453 454
  /* If we want to bind and no integrity required */
  if ((sflags & do_bind) && !(sflags & do_secret)) {
    err = stun_bind(sh, stunc_bind_cb, stunc,
		    STUNTAG_SOCKET(s),
		    STUNTAG_REGISTER_EVENTS(1),
		    TAG_NULL());
455

456 457 458 459 460 461 462 463 464 465 466
    if (err < 0) {
      SU_DEBUG_0(("%s: %s  failed\n", __func__, "stun_bind()"));
      return -1;
    }
  }

  if (sflags & do_nat_check) {
    err = stun_test_nattype(sh, stunc_nattype_cb, stunc,
			    STUNTAG_REGISTER_EVENTS(1),
			    STUNTAG_SOCKET(stunc->sc_socket),
			    TAG_NULL());
467

468 469
    if (err < 0) {
      SU_DEBUG_0(("%s: %s  failed\n", __func__, "stun_test_nattype()"));
470
      su_root_break(stun_root(sh));
471 472 473 474 475 476 477 478
    }
  }

  if (sflags & do_life_check) {
    err = stun_test_lifetime(sh, stunc_lifetime_cb, stunc,
			     STUNTAG_REGISTER_EVENTS(1),
			     STUNTAG_SOCKET(stunc->sc_socket),
			     TAG_NULL());
479

480 481
    if (err < 0) {
      SU_DEBUG_0(("%s: %s  failed\n", __func__, "stun_test_lifetime()"));
482
      su_root_break(stun_root(sh));
483
    }
Martti Mela's avatar
Martti Mela committed
484
  }
485

486 487
  if (err == 0)
    su_root_run(root);
488

489
  stun_handle_destroy(sh);
490
  su_root_destroy(root);
Pekka Pessi's avatar
Pekka Pessi committed
491 492 493

  return 0;
}