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
Pekka Pessi's avatar
Pekka Pessi committed
27 28
 * 
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
29 30
 * @author Martti Mela <Martti.Mela@nokia.com>
 * @author Kai Vehmanen <Kai.Vehmanen@nokia.com>
Pekka Pessi's avatar
Pekka Pessi committed
31 32 33 34
 * 
 * @date Created: Thu Jul 24 17:21:00 2003 ppessi
 */

35 36 37 38 39 40 41 42 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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
/**@page stunc STUN test client.
 * 
 * @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
 * NAT devices that are located between the client and STUN server. 
 *
 * @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
 * supports the shared-secret mechanism (needed to protect message 
 * 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
 * 
 * @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.
 */

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

#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 219 220 221
    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());
    
      if (err < 0) {
	SU_DEBUG_0(("%s: %s  failed\n", __func__, "stun_handle_bind()"));
222
	su_root_break(stun_root(sh));
223
      }
224
    }
225 226
    break;
    
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 272 273 274
    addrlen = sizeof(*sa);
    memset(sa, 0, addrlen);
    
    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 317
    SU_DEBUG_3(("%s: NAT type determined to be '%s' (%d).\n", 
		__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 370 371 372 373
  if (su_init() != 0) 
    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 396
  sh = stun_handle_init(root,
			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 427 428

    SU_DEBUG_3(("stunc: Binding to local port %u.\n", ntohs(sockaddr.su_port)));
  
    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 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
  /* 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());
    
    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());
    
    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 479 480 481
    }
  }

  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());
    
    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;
}