stun.c 77.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 26 27
 * 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
 *
 */

/**
 * @file stun.c STUN client module
 *
28
 * See RFC 3489/3489bis for further information.
Pekka Pessi's avatar
Pekka Pessi committed
29
 *
30
 * @author Martti Mela <Martti.Mela@nokia.com>
Martti Mela's avatar
Martti Mela committed
31
 * @author Tat Chan <Tat.Chan@nokia.com>
Pekka Pessi's avatar
Pekka Pessi committed
32 33 34 35 36 37 38 39
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 * @author Kai Vehmanen <Kai.Vehmanen@nokia.com>
 * 
 * @date Created: Thu Jul 24 17:21:00 2003 ppessi
 */

#include "config.h" 

40
#include <assert.h>
41
#include <string.h>
42

43
#define SU_ROOT_MAGIC_T struct stun_magic_t
44

45
#include "sofia-sip/stun.h"
46
#include "stun_internal.h"
47
#include "sofia-sip/stun_tag.h"
Pekka Pessi's avatar
Pekka Pessi committed
48

49 50 51 52 53
#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su_tagarg.h>
#include <sofia-sip/su_log.h>
#include <sofia-sip/su.h>
#include <sofia-sip/su_localinfo.h>
Pekka Pessi's avatar
Pekka Pessi committed
54

Martti Mela's avatar
Martti Mela committed
55
#if defined(HAVE_OPENSSL)
Pekka Pessi's avatar
Pekka Pessi committed
56
#include <openssl/opensslv.h>
Martti Mela's avatar
Martti Mela committed
57
#endif
Pekka Pessi's avatar
Pekka Pessi committed
58

59 60 61 62
#if !defined(ETIMEDOUT) && defined(_WIN32)
#define ETIMEDOUT WSAETIMEDOUT
#endif

Pekka Pessi's avatar
Pekka Pessi committed
63 64 65 66 67
/* Missing socket symbols */
#ifndef SOL_TCP
#define SOL_TCP IPPROTO_TCP
#endif

68 69 70 71 72 73 74
#if HAVE_FUNC
#elif HAVE_FUNCTION
#define __func__ __FUNCTION__
#else
static char const __func__[] = "stun";
#endif

Pekka Pessi's avatar
Pekka Pessi committed
75
/** STUN log. */
76
su_log_t stun_log[] = { SU_LOG_INIT("stun", "STUN_DEBUG", SU_DEBUG) }; 
Pekka Pessi's avatar
Pekka Pessi committed
77

78
enum {
79
  STUN_SENDTO_TIMEOUT = 1000,
Martti Mela's avatar
Martti Mela committed
80
  STUN_TLS_CONNECT_TIMEOUT = 8000,
81 82
};

83 84 85 86 87 88 89 90 91 92 93 94
/* NAT TYPES */
typedef enum stun_nattype_e {
  stun_nat_unknown,
  stun_open_internet,
  stun_udp_blocked,
  stun_sym_udp_fw,
  stun_nat_full_cone,
  stun_nat_sym,
  stun_nat_res_cone,
  stun_nat_port_res_cone,
} stun_nattype_t;

95 96 97
#define CHG_IP		0x001
#define CHG_PORT	0x004

98 99 100 101 102 103 104 105 106 107
#define x_insert(l, n, x) \
 ((l) ? (l)->x##_prev = &(n)->x##_next : 0, \
  (n)->x##_next = (l), (n)->x##_prev = &(l), (l) = (n))

#define x_remove(n, x) \
  ((*(n)->x##_prev = (n)->x##_next) ? \
   (n)->x##_next->x##_prev = (n)->x##_prev : 0)

#define x_is_inserted(n, x) ((n)->x##_prev != NULL)

108
struct stun_discovery_s {
109
  stun_discovery_t   *sd_next, **sd_prev; /**< Linked list */
110

111 112 113
  stun_handle_t          *sd_handle;
  stun_discovery_f        sd_callback;
  stun_discovery_magic_t *sd_magic;
114

115 116 117
  tagi_t          *sd_tags;          /** stored tags for the discovery */


118 119 120 121 122 123 124 125 126 127
  su_addrinfo_t    sd_pri_info;      /**< server primary info */
  su_sockaddr_t    sd_pri_addr[1];   /**< server primary address */

  su_addrinfo_t    sd_sec_info;      /**< server secondary info */
  su_sockaddr_t    sd_sec_addr[1];   /**< server secondary address */

  stun_action_t    sd_action;        /**< My action */
  stun_state_t     sd_state;         /**< Progress states */

  su_socket_t      sd_socket;        /**< target socket */
128 129
  su_sockaddr_t    sd_bind_addr[1]; /**< local address */

130
  su_socket_t      sd_socket2;       /**< Alternative socket */
131

132
  int              sd_index;         /**< root_register index */
133

Martti Mela's avatar
Martti Mela committed
134
  /* Binding discovery */
135
  su_sockaddr_t    sd_addr_seen_outside[1];   /**< local address */
Martti Mela's avatar
Martti Mela committed
136

137
  /* NAT type related */
138
  stun_nattype_t   sd_nattype;       /**< Determined NAT type */
139 140 141 142 143
  unsigned         sd_first:1;       /**< These are the requests  */
  unsigned         sd_second:1;
  unsigned         sd_third:1;
  unsigned         sd_fourth:1;
  unsigned         :0;
144 145 146 147 148

  /* Life time related */
  int              sd_lt_cur;
  int              sd_lt;
  int              sd_lt_max;
Martti Mela's avatar
Martti Mela committed
149 150 151 152

  /* Keepalive timeout */
  unsigned int     sd_timeout;
  su_timer_t      *sd_timer;
153 154
};

155
struct stun_request_s {
156
  su_timer_t       *sr_timer;
157 158 159 160
  stun_request_t   *sr_next, **sr_prev; /**< Linked list */
  stun_msg_t       *sr_msg;             /**< STUN message pointer */
  stun_handle_t    *sr_handle;          /**< backpointer, STUN object */

161 162 163
  su_socket_t       sr_socket;          /**< Alternative socket */
  su_localinfo_t    sr_localinfo;       /**< local addrinfo */
  su_sockaddr_t     sr_local_addr[1];   /**< local address */
Martti Mela's avatar
Martti Mela committed
164
  su_sockaddr_t     sr_destination[1];
165

166
  stun_state_t      sr_state;           /**< Progress states */
167 168
  int               sr_retry_count;     /**< current retry number */
  long              sr_timeout;         /**< timeout for next sendto() */
169 170

  int               sr_from_y;
171 172 173
  int               sr_request_mask;    /**< Mask consisting of chg_ip and chg_port */
  stun_discovery_t *sr_discovery;
};
174

Martti Mela's avatar
Martti Mela committed
175
struct stun_handle_s
Pekka Pessi's avatar
Pekka Pessi committed
176
{
177 178 179
  su_home_t       sh_home[1];
  su_root_t      *sh_root;          /**< event loop */
  int             sh_root_index;    /**< object index of su_root_register() */
180

181
  stun_request_t *sh_requests; /**< outgoing requests list */
182
  stun_discovery_t *sh_discoveries; /**< Actions list */
183

184
  int             sh_max_retries;   /**< max resend for sendto() */
185

186 187
  su_addrinfo_t   sh_pri_info;      /**< server primary info */
  su_sockaddr_t   sh_pri_addr[1];   /**< server primary address */
188

189 190
  su_addrinfo_t   sh_sec_info;      /**< server secondary info */
  su_sockaddr_t   sh_sec_addr[1];   /**< server secondary address */
191

192 193
  su_localinfo_t  sh_localinfo;     /**< local addrinfo */
  su_sockaddr_t   sh_local_addr[1]; /**< local address */
194

195 196
  char           *sh_domain;        /**< domain address for DNS-SRV lookups */

197 198
  stun_dns_lookup_t  *sh_dns_lookup;
  stun_action_t       sh_dns_pend_action; 
199 200 201
  stun_discovery_f    sh_dns_pend_cb;
  stun_discovery_magic_t *sh_dns_pend_ctx;
  tagi_t             *sh_dns_pend_tags;
202

Martti Mela's avatar
Martti Mela committed
203
#if defined(HAVE_OPENSSL)
204 205
  SSL_CTX        *sh_ctx;           /**< SSL context for TLS */
  SSL            *sh_ssl;           /**< SSL handle for TLS */
Martti Mela's avatar
Martti Mela committed
206 207 208 209 210
#else
  void           *sh_ctx;           /**< SSL context for TLS */
  void           *sh_ssl;           /**< SSL handle for TLS */
#endif

211 212
  stun_msg_t      sh_tls_request;
  stun_msg_t      sh_tls_response;
213
  int             sh_nattype;       /**< NAT-type, see stun_common.h */
Martti Mela's avatar
Martti Mela committed
214

215 216
  stun_buffer_t   sh_username;
  stun_buffer_t   sh_passwd;
217

218 219
  int             sh_use_msgint;    /**< try message integrity (TLS) */
  int             sh_req_msgint;    /**< require use of msg-int (TLS) */
Pekka Pessi's avatar
Pekka Pessi committed
220 221
};

Martti Mela's avatar
Martti Mela committed
222

223 224
#define STUN_STATE_STR(x) case x: return #x

225
char const *stun_str_state(stun_state_t state)
226 227 228
{
  switch (state) {
  STUN_STATE_STR(stun_no_assigned_event);
229
  STUN_STATE_STR(stun_dispose_me);
230 231 232 233 234
  STUN_STATE_STR(stun_tls_connecting);
  STUN_STATE_STR(stun_tls_writing);
  STUN_STATE_STR(stun_tls_closing);
  STUN_STATE_STR(stun_tls_reading);
  STUN_STATE_STR(stun_tls_done);
235 236 237
  STUN_STATE_STR(stun_discovery_init);
  STUN_STATE_STR(stun_discovery_processing);
  STUN_STATE_STR(stun_discovery_done);
238 239 240
  STUN_STATE_STR(stun_tls_connection_timeout);
  STUN_STATE_STR(stun_tls_connection_failed);
  STUN_STATE_STR(stun_tls_ssl_connect_failed);
241
  STUN_STATE_STR(stun_discovery_timeout);
242
  STUN_STATE_STR(stun_request_timeout);
243 244 245 246 247 248
  
  case stun_error:
  default: return "stun_error";
  }
}

249 250 251
/** 
 * Returns the NAT type attached to STUN discovery handle.
 */
252
char const *stun_nattype(stun_discovery_t *sd)
253
{
Martti Mela's avatar
Martti Mela committed
254 255 256 257 258 259 260 261 262 263 264
  char const *stun_nattype_str[] = {
    "NAT type undetermined",
    "Open Internet",
    "UDP traffic is blocked or server unreachable",
    "Symmetric UDP Firewall",
    "Full-Cone NAT",
    "Symmetric NAT",
    "Restricted Cone NAT",
    "Port Restricted Cone NAT",
  };

Pekka Pessi's avatar
Pekka Pessi committed
265 266 267 268
  if (sd)
    return stun_nattype_str[sd->sd_nattype];
  else
    return stun_nattype_str[stun_nat_unknown];
269 270
}

271 272 273 274
su_addrinfo_t const *stun_server_address(stun_handle_t *sh)
{
  return &sh->sh_pri_info;
}
275

276 277
int stun_lifetime(stun_discovery_t *sd)
{
Pekka Pessi's avatar
Pekka Pessi committed
278
  return sd ? sd->sd_lt_cur : -1;
279 280 281
}


Martti Mela's avatar
Martti Mela committed
282
#if defined(HAVE_OPENSSL)
Pekka Pessi's avatar
Pekka Pessi committed
283
char const stun_version[] = 
Pekka Pessi's avatar
Pekka Pessi committed
284
 "sofia-sip-stun using " OPENSSL_VERSION_TEXT;
Martti Mela's avatar
Martti Mela committed
285 286 287 288
#else
char const stun_version[] = 
 "sofia-sip-stun";
#endif
Pekka Pessi's avatar
Pekka Pessi committed
289

290 291 292 293 294 295 296 297 298 299
static int do_action(stun_handle_t *sh, stun_msg_t *binding_response);
static int stun_tls_callback(su_root_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg);
static int process_binding_request(stun_request_t *req, stun_msg_t *binding_response);
static stun_discovery_t *stun_discovery_create(stun_handle_t *sh,
					       stun_action_t action,
					       stun_discovery_f sdf,
					       stun_discovery_magic_t *magic);
static int stun_discovery_destroy(stun_discovery_t *sd);
static int action_bind(stun_request_t *req, stun_msg_t *binding_response);
static int action_determine_nattype(stun_request_t *req, stun_msg_t *binding_response);
300
static int process_test_lifetime(stun_request_t *req, stun_msg_t *binding_response);
301 302 303

static stun_request_t *stun_request_create(stun_discovery_t *sd);
static int stun_send_binding_request(stun_request_t *req,
304
			      su_sockaddr_t *srvr_addr);
305
static int stun_bind_callback(stun_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg);
306 307

/* timers */
308 309 310 311 312 313
static void stun_sendto_timer_cb(su_root_magic_t *magic, 
				 su_timer_t *t,
				 su_timer_arg_t *arg);
static void stun_tls_connect_timer_cb(su_root_magic_t *magic, 
				      su_timer_t *t,
				      su_timer_arg_t *arg);
314 315 316
static void stun_test_lifetime_timer_cb(su_root_magic_t *magic, 
					su_timer_t *t,
					su_timer_arg_t *arg);
317 318 319 320 321 322
static void stun_keepalive_timer_cb(su_root_magic_t *magic, 
				    su_timer_t *t,
				    su_timer_arg_t *arg);


static int priv_stun_bind_send(stun_handle_t *sh, stun_request_t *req, stun_discovery_t *sd);
323 324 325 326 327
static int priv_dns_queue_action(stun_handle_t *sh,
				 stun_action_t action,
				 stun_discovery_f sdf,
				 stun_discovery_magic_t *magic,
				 tag_type_t tag, tag_value_t value, ...);
Martti Mela's avatar
Martti Mela committed
328 329 330

/**
 * Return su_root_t assigned to stun_handle_t.
331
 *
Martti Mela's avatar
Martti Mela committed
332
 * @param self stun_handle_t object
333 334
 * @return su_root_t object, NULL if self not given.
 */
335
su_root_t *stun_root(stun_handle_t *self)
336
{
337
  return self ? self->sh_root : NULL;
338 339 340
}


Pekka Pessi's avatar
Pekka Pessi committed
341
/**
Martti Mela's avatar
Martti Mela committed
342
 * Check if a STUN handle should be created.
Pekka Pessi's avatar
Pekka Pessi committed
343
 *
344 345 346 347 348 349 350
 * Return true if STUNTAG_SERVER() or STUNTAG_DOMAIN() tags have 
 * been specified, or otherwise if STUN_SERVER environment variable 
 * is set.
 *
 * @TAGS
 * @TAG STUNTAG_DOMAIN() domain to use in DNS-SRV based STUN server
 * @TAG STUNTAG_SERVER() stun server hostname or dotted IPv4 address
Pekka Pessi's avatar
Pekka Pessi committed
351 352 353 354 355 356
 *
 * @param tag,value,... tag-value list
 */
int stun_is_requested(tag_type_t tag, tag_value_t value, ...)
{
  ta_list ta;
357
  tagi_t const *t, *t2;
Pekka Pessi's avatar
Pekka Pessi committed
358 359
  char const *stun_server;

Martti Mela's avatar
Martti Mela committed
360
  enter;
Martti Mela's avatar
Martti Mela committed
361

Pekka Pessi's avatar
Pekka Pessi committed
362 363
  ta_start(ta, tag, value);
  t = tl_find(ta_args(ta), stuntag_server);
364 365 366 367
  t2 = tl_find(ta_args(ta), stuntag_domain);
  if (t && t->t_value) 
    stun_server = (char *)t->t_value;
  else if (t2 && t2->t_value)
368
    stun_server = (char *)t2->t_value;
369 370
  else
    stun_server = getenv("STUN_SERVER");
Pekka Pessi's avatar
Pekka Pessi committed
371 372 373 374 375
  ta_end(ta);

  return stun_server != NULL;
}

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

/** 
 * Creates a STUN handle.
 *
 * The created handles can be used for STUN binding discovery, 
 * keepalives, and other STUN usages.
 *
 * @param root eventloop to used by the stun state machine
 * @param tag,value,... tag-value list 
 *
 * @TAGS
 * @TAG STUNTAG_DOMAIN() domain to use in DNS-SRV based STUN server
 * @TAG STUNTAG_SERVER() stun server hostname or dotted IPv4 address
 * @TAG STUNTAG_REQUIRE_INTEGRITY() true if msg integrity should be
 * used enforced
 *
 */
stun_handle_t *stun_handle_init(su_root_t *root,
				tag_type_t tag, tag_value_t value, ...)
{
  stun_handle_t *stun = NULL;
  char const *server = NULL, *domain = NULL;
  int req_msg_integrity = 1;
  int err;
  ta_list ta;
  
  enter;

  ta_start(ta, tag, value);

  tl_gets(ta_args(ta),
	  STUNTAG_SERVER_REF(server),
	  STUNTAG_DOMAIN_REF(domain),
	  STUNTAG_REQUIRE_INTEGRITY_REF(req_msg_integrity),
	  TAG_END());

  stun = su_home_clone(NULL, sizeof(*stun));

  if (!stun) {
    SU_DEBUG_3(("%s: %s failed\n", __func__, "su_home_clone()"));
    return NULL;
  }

  /* Enviroment overrides */
  if (getenv("STUN_SERVER")) {
    server = getenv("STUN_SERVER");
    SU_DEBUG_5(("%s: using STUN_SERVER=%s\n", __func__, server));
  }

  SU_DEBUG_5(("%s(\"%s\"): called\n", 
	      __func__, server));

  /* fail, if no server or a domain for a DNS-SRV lookup is specified */
  if (!server && !domain)
    return NULL;
  
  stun->sh_pri_info.ai_addrlen = 16;
  stun->sh_pri_info.ai_addr = &stun->sh_pri_addr->su_sa;

  stun->sh_sec_info.ai_addrlen = 16;
  stun->sh_sec_info.ai_addr = &stun->sh_sec_addr->su_sa;

  stun->sh_localinfo.li_addrlen = 16;
  stun->sh_localinfo.li_addr = stun->sh_local_addr;

  stun->sh_domain = su_strdup(stun->sh_home, domain);
  stun->sh_dns_lookup = NULL;
  
  if (server) {
445
    err = stun_atoaddr(stun->sh_home, AF_INET, &stun->sh_pri_info, server);
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474

    if (err < 0)
      return NULL;
  }

  stun->sh_nattype = stun_nat_unknown;

  stun->sh_root     = root;
  /* always try TLS: */
  stun->sh_use_msgint = 1;
  /* whether use of shared-secret msgint is required */
  stun->sh_req_msgint = req_msg_integrity;

  stun->sh_max_retries = STUN_MAX_RETRX;

  /* initialize username and password */
  stun_init_buffer(&stun->sh_username);
  stun_init_buffer(&stun->sh_passwd);
  
  stun->sh_nattype = stun_nat_unknown;
  
  /* initialize random number generator */
  srand(time(NULL));
  
  ta_end(ta);

  return stun;
}

475 476 477 478 479
/** 
 * Performs shared secret request/response processing.
 * Result will be trigged in STUN handle callback (state
 * one of stun_tls_*).
 **/
480 481 482 483
int stun_obtain_shared_secret(stun_handle_t *sh,
			      stun_discovery_f sdf,
			      stun_discovery_magic_t *magic,
			      tag_type_t tag, tag_value_t value, ...)
Martti Mela's avatar
Martti Mela committed
484
{
485
#if defined(HAVE_OPENSSL)
Martti Mela's avatar
Martti Mela committed
486 487 488 489 490 491
  int events = -1;
  int one, err = -1;
  su_wait_t wait[1] = { SU_WAIT_INIT };
  su_socket_t s = SOCKET_ERROR;
  int family;
  su_addrinfo_t *ai = NULL;
492
  stun_discovery_t *sd;
Martti Mela's avatar
Martti Mela committed
493
  /* stun_request_t *req; */
Martti Mela's avatar
Martti Mela committed
494

495 496
  ta_list ta;

Martti Mela's avatar
Martti Mela committed
497
  assert(sh);
Martti Mela's avatar
Martti Mela committed
498

Martti Mela's avatar
Martti Mela committed
499
  enter;
Martti Mela's avatar
Martti Mela committed
500

501 502
  if (!sh->sh_pri_addr[0].su_port) {
    /* no STUN server address, perform a DNS-SRV lookup */
503 504 505
   
    ta_list ta;
    ta_start(ta, tag, value);
506
    SU_DEBUG_5(("Delaying STUN shared-secret req. for DNS-SRV query.\n"));
507 508 509 510
    err = priv_dns_queue_action(sh, stun_action_tls_query, sdf, magic, ta_tags(ta));
    ta_end(ta);
       
    return err;
511 512
  }

Martti Mela's avatar
Martti Mela committed
513 514 515
  ai = &sh->sh_pri_info;

  if (sh->sh_use_msgint == 1) {
Martti Mela's avatar
Martti Mela committed
516
    SU_DEBUG_3(("%s: Obtaining shared secret.\n", __func__));
Martti Mela's avatar
Martti Mela committed
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
  }
  else {
    SU_DEBUG_3(("No message integrity enabled.\n"));
    return errno = EFAULT, -1;
  }

  /* open tcp connection to server */
  s = su_socket(family = AF_INET, SOCK_STREAM, 0);

  if (s == -1) {
    STUN_ERROR(errno, socket);
    return -1;
  }

  /* asynchronous connect() */
  if (su_setblocking(s, 0) < 0) {
    STUN_ERROR(errno, su_setblocking);
    return -1;
  }
536

Martti Mela's avatar
Martti Mela committed
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
  if (setsockopt(s, SOL_TCP, TCP_NODELAY,
		 (void *)&one, sizeof one) == -1) {
    STUN_ERROR(errno, setsockopt);
    return -1;
  }

  /* Do an asynchronous connect(). Three error codes are ok,
   * others cause return -1. */
  if (connect(s, (struct sockaddr *) &sh->sh_pri_addr, 
	      ai->ai_addrlen) == SOCKET_ERROR) {
    err = su_errno();
    if (err != EINPROGRESS && err != EAGAIN && err != EWOULDBLOCK) {
      STUN_ERROR(err, connect);
      return -1;
    }
  }

  SU_DEBUG_9(("%s: %s: %s\n", __func__, "connect",
	      su_strerror(err)));
  
557
  sd = stun_discovery_create(sh, stun_action_tls_query, sdf, magic);
Martti Mela's avatar
Martti Mela committed
558
  sd->sd_socket = s;
Martti Mela's avatar
Martti Mela committed
559
  /* req = stun_request_create(sd); */
Martti Mela's avatar
Martti Mela committed
560

Martti Mela's avatar
Martti Mela committed
561
  events = SU_WAIT_CONNECT | SU_WAIT_ERR;
Martti Mela's avatar
Martti Mela committed
562 563 564
  if (su_wait_create(wait, s, events) == -1)
    STUN_ERROR(errno, su_wait_create);

Martti Mela's avatar
Martti Mela committed
565
  /* su_root_eventmask(sh->sh_root, sh->sh_root_index, s, events); */
566 567

  if ((sd->sd_index =
Martti Mela's avatar
Martti Mela committed
568
       su_root_register(sh->sh_root, wait, stun_tls_callback, (su_wakeup_arg_t *) sd, 0)) == -1) {
Martti Mela's avatar
Martti Mela committed
569 570 571 572
    STUN_ERROR(errno, su_root_register);
    return -1;
  }

573 574 575 576
  ta_start(ta, tag, value);
  sd->sd_tags = tl_adup(sh->sh_home, ta_args(ta));
  ta_end(ta);

577
  sd->sd_state = stun_tls_connecting;
Martti Mela's avatar
Martti Mela committed
578 579 580 581

  /* Create and start timer for connect() timeout */
  SU_DEBUG_3(("%s: creating timeout timer for connect()\n", __func__));

582 583
  sd->sd_timer = su_timer_create(su_root_task(sh->sh_root),
				 STUN_TLS_CONNECT_TIMEOUT);
Martti Mela's avatar
Martti Mela committed
584

585
  su_timer_set(sd->sd_timer, stun_tls_connect_timer_cb, (su_wakeup_arg_t *) sd);
Martti Mela's avatar
Martti Mela committed
586 587

  return 0;
588 589 590
#else /* !HAVE_OPENSSL */
  return -1;
#endif
Martti Mela's avatar
Martti Mela committed
591 592
}

593
static stun_request_t *stun_request_create(stun_discovery_t *sd)
594
{
595
  stun_handle_t *sh = sd->sd_handle;
596 597
  stun_request_t *req = NULL;

Martti Mela's avatar
Martti Mela committed
598
  enter;
Martti Mela's avatar
Martti Mela committed
599

600
  req = calloc(sizeof(stun_request_t), 1);
601 602
  if (!req)
    return NULL;
603

604
  req->sr_handle = sh;
605
  req->sr_discovery = sd;
606 607

  /* This is the default */
608
  req->sr_socket = sd->sd_socket;
609 610 611 612 613 614 615
  
  req->sr_localinfo.li_addrlen = sizeof(su_sockaddr_t);
  req->sr_localinfo.li_addr = req->sr_local_addr;
  
  /* default timeout for next sendto() */
  req->sr_timeout = STUN_SENDTO_TIMEOUT;
  req->sr_retry_count = 0;
616
  /* req->sr_action = action; */
617 618 619
  req->sr_request_mask = 0;
  
  req->sr_msg = calloc(sizeof(stun_msg_t), 1);
620

621
  req->sr_state = stun_discovery_init;
622
  memcpy(req->sr_local_addr, sd->sd_bind_addr, sizeof(su_sockaddr_t));
623

624 625 626 627 628 629
  /* Insert this request to the request queue */
  if (sh->sh_requests)
    x_insert(sh->sh_requests, req, sr);
  else
    sh->sh_requests = req;

630 631 632
  return req;
}

633
void stun_request_destroy(stun_request_t *req)
634
{
635
  stun_handle_t *sh;
636 637
  assert(req);

Martti Mela's avatar
Martti Mela committed
638
  enter;
Martti Mela's avatar
Martti Mela committed
639

640 641
  sh = req->sr_handle;

642 643 644
  if (x_is_inserted(req, sr))
    x_remove(req, sr);

645 646 647
  if (!x_is_inserted(req, sr) && !req->sr_next)
    sh->sh_requests = NULL;

648
  req->sr_handle = NULL;
649
  req->sr_discovery = NULL;
Martti Mela's avatar
Martti Mela committed
650
  /* memset(req->sr_destination, 0, sizeof(su_sockaddr_t)); */
651

652 653 654 655 656 657 658 659 660 661
  if (req->sr_timer) {
    su_timer_destroy(req->sr_timer);
    req->sr_timer = NULL;
    SU_DEBUG_7(("%s: timer destroyed.\n", __func__));
  }

  if (req->sr_msg) {
    free(req->sr_msg);
    req->sr_msg = NULL;
  }
662

663
  free(req);
664 665 666

  SU_DEBUG_9(("%s: request destroyed.\n", __func__));

667 668 669
  return;
}

670

Pekka Pessi's avatar
Pekka Pessi committed
671
/** Destroy a STUN client */ 
Martti Mela's avatar
Martti Mela committed
672
void stun_handle_destroy(stun_handle_t *sh)
Pekka Pessi's avatar
Pekka Pessi committed
673
{ 
Martti Mela's avatar
Martti Mela committed
674
  stun_discovery_t *sd = NULL, *kill = NULL;
675
  stun_request_t *a, *b;
Pekka Pessi's avatar
Pekka Pessi committed
676

Martti Mela's avatar
Martti Mela committed
677
  enter;
Martti Mela's avatar
Martti Mela committed
678

679 680
  if (sh->sh_dns_lookup)
    stun_dns_lookup_destroy(sh->sh_dns_lookup);
681

682 683 684
  if (sh->sh_dns_pend_tags)
    su_free(sh->sh_home, sh->sh_dns_pend_tags);

685 686 687 688 689 690 691
  for (a = sh->sh_requests; a; ) {
    b = a->sr_next;
    stun_request_destroy(a);
    a = b;
  }


Martti Mela's avatar
Martti Mela committed
692 693
  /* There can be several discoveries using the same socket. It is
     still enough to deregister the socket in first of them */
Martti Mela's avatar
Martti Mela committed
694 695 696
  for (sd = sh->sh_discoveries; sd; ) {
    kill = sd;
    sd = sd->sd_next;
Martti Mela's avatar
Martti Mela committed
697

698 699 700
    /* Index has same value as sockfd, right? ... or not? */
    if (kill->sd_index != -1)
      su_root_deregister(sh->sh_root, kill->sd_index);
Martti Mela's avatar
Martti Mela committed
701 702
    if (kill->sd_action == stun_action_tls_query)
      su_close(kill->sd_socket);
703 704

    stun_discovery_destroy(kill);
Martti Mela's avatar
Martti Mela committed
705
  }
Martti Mela's avatar
Martti Mela committed
706

Martti Mela's avatar
Martti Mela committed
707
  su_home_zap(sh->sh_home);
Martti Mela's avatar
Martti Mela committed
708 709
}

Pekka Pessi's avatar
Pekka Pessi committed
710

Pekka Pessi's avatar
Pekka Pessi committed
711
/** Create wait object and register it to the handle callback */
712
int assign_socket(stun_discovery_t *sd, su_socket_t s, int reg_socket) 
713
{
714
  stun_handle_t *sh = sd->sd_handle;
715
  int events;
Martti Mela's avatar
Martti Mela committed
716
  stun_discovery_t *tmp;
717 718 719 720 721 722
  /* su_localinfo_t clientinfo[1]; */
  su_sockaddr_t bind_addr;
  socklen_t bind_len;
  char ipaddr[SU_ADDRSIZE + 2] = { 0 };
  su_sockaddr_t *sa;
  int err;
723
  
724 725
  su_wait_t wait[1] = { SU_WAIT_INIT };

Martti Mela's avatar
Martti Mela committed
726 727
  enter;

728 729 730 731 732
  if (s == -1) {
    SU_DEBUG_3(("%s: invalid socket.\n", __func__));
    return errno = EINVAL, -1;
  }

Martti Mela's avatar
Martti Mela committed
733
  for (tmp = sh->sh_discoveries; tmp; tmp = tmp->sd_next) {
734 735 736 737 738 739
    if (tmp->sd_socket == s) {
      sd->sd_socket = s;
      sd->sd_index = tmp->sd_index;
      memcpy(sd->sd_bind_addr, tmp->sd_bind_addr, sizeof(su_sockaddr_t));
      return 0;
    }
Martti Mela's avatar
Martti Mela committed
740
  }
741
  sd->sd_socket = s;
Martti Mela's avatar
Martti Mela committed
742

743 744 745
  if (reg_socket != 1)
    return 0;

746 747 748 749 750 751 752 753
  /* set socket asynchronous */
  if (su_setblocking(s, 0) < 0) {
    STUN_ERROR(errno, su_setblocking);

    su_close(s);
    return -1;
  }

754
  /* xxx -- check if socket is already assigned to this root */
755 756 757 758 759 760 761 762
  events = SU_WAIT_IN | SU_WAIT_ERR;

  if (su_wait_create(wait, s, events) == -1) {
    STUN_ERROR(su_errno(), su_wait_create);
    return -1;
  }

  /* Register receiving function with events specified above */
763 764
  if ((sd->sd_index = su_root_register(sh->sh_root,
				       wait, stun_bind_callback,
Martti Mela's avatar
Martti Mela committed
765
				       (su_wakeup_arg_t *) sd, 0)) < 0) {
766 767 768 769
    STUN_ERROR(errno, su_root_register);
    return -1;
  }

Martti Mela's avatar
Martti Mela committed
770 771
  SU_DEBUG_7(("%s: socket registered.\n", __func__));

772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
  bind_len = sizeof bind_addr;

  sa = (void *) &bind_addr;
  bind_len = sizeof bind_addr;
  memset(sa, 0, sizeof(bind_addr));
  /* if bound check the error */
  err = getsockname(s, (struct sockaddr *) sa, &bind_len);
  if (err < 0 && errno == SOCKET_ERROR) {
    STUN_ERROR(errno, getsockname);
    return -1;
  }

  /* Not bound - bind it */
  if (sa->su_port == 0) {
#if defined (__CYGWIN__)
    get_localinfo(clientinfo);
#endif

    if ((err = bind(s, (struct sockaddr *) sa, bind_len)) < 0) {
      STUN_ERROR(errno, bind);
      SU_DEBUG_3(("%s: Error binding to %s:%u\n", __func__, 
		  inet_ntop(sa->su_family, SU_ADDR(sa), 
			    ipaddr, sizeof(ipaddr)),
		  (unsigned) ntohs(sa->su_port)));
      return -1;
    }

    /* bind_len = clientinfo->li_addrlen; */
    /* clientinfo->li_addrlen = bind_len; */
    sa->su_len = bind_len; /* ? */
  }

  memcpy(&sd->sd_bind_addr, &bind_addr, sizeof bind_addr);

  if (getsockname(s, (struct sockaddr *) &bind_addr, &bind_len) != 0) {
    STUN_ERROR(errno, getsockname);
    return -1;
  }

  SU_DEBUG_3(("%s: local socket is bound to %s:%u\n", __func__,
	      inet_ntop(bind_addr.su_family, SU_ADDR(&bind_addr), 
			ipaddr, sizeof(ipaddr)),
	      (unsigned) ntohs(bind_addr.su_port)));


  return 0;
818 819
}

820

821 822 823 824
/**
 * Helper function needed by Cygwin builds.
 */
#if defined (__CYGWIN__)
825 826 827
static int get_localinfo(su_localinfo_t *clientinfo)
{
  su_localinfo_t  hints[1] = {{ LI_CANONNAME | LI_NUMERIC }}, *li, *res = NULL;
828
  su_sockaddr_t *sa;
829 830 831
  int i, error, found = 0;
  char ipaddr[SU_ADDRSIZE + 2] = { 0 };

832

Martti Mela's avatar
Martti Mela committed
833
  hints->li_family = AF_INET;
834
  if ((error = su_getlocalinfo(hints, &res)) == 0) {
835 836 837 838 839 840 841
    
    /* try to bind to the first available address */
    for (i = 0, li = res; li; li = li->li_next) {
      if (li->li_family != AF_INET)
	continue;
      
      clientinfo->li_family = li->li_family;
Martti Mela's avatar
Martti Mela committed
842
      clientinfo->li_addrlen = li->li_addrlen;
843
      
844
      sa = clientinfo->li_addr;
Martti Mela's avatar
Martti Mela committed
845
      memcpy(sa, li->li_addr, sizeof(su_sockaddr_t));
846 847 848 849
      SU_DEBUG_3(("%s: local address found to be %s.\n",
		  __func__, 
		  inet_ntop(clientinfo->li_family, SU_ADDR(sa),
			    ipaddr, sizeof(ipaddr))));
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
      found = 1;
      break;
    }
    
    if (!found) {
      STUN_ERROR(error, su_getlocalinfo);
      return -1;
    }
  }
  else {
    STUN_ERROR(error, su_getlocalinfo);
    return -1;
  }
  if (res)
    su_freelocalinfo(res);

  return 0;
}
Pekka Pessi's avatar
Pekka Pessi committed
868
#endif
Martti Mela's avatar
Martti Mela committed
869

870 871 872 873 874 875 876 877
static void priv_lookup_cb(stun_dns_lookup_t *self,
			   stun_magic_t *magic)
{
  const char *udp_target = NULL;
  uint16_t udp_port = 0;
  int res;
  stun_handle_t *sh = (stun_handle_t *)magic;

878
  res = stun_dns_lookup_udp_addr(self, &udp_target, &udp_port);
879
  if (res == 0 && udp_target) {
880
    /* XXX: assumption that same host and port used for UDP/TLS */
881 882 883 884 885 886
    stun_atoaddr(sh->sh_home, AF_INET, &sh->sh_pri_info, udp_target);
    
    if (udp_port) 
      sh->sh_pri_addr[0].su_port = htons(udp_port);
    else
      sh->sh_pri_addr[0].su_port = htons(STUN_DEFAULT_PORT);
887

888 889
    /* step: now that server address is known, continue 
     *       the pending action */
890

891 892
    SU_DEBUG_5(("STUN server address found, running queue actions (%d).\n",
		sh->sh_dns_pend_action));
893

894 895 896 897
    switch(sh->sh_dns_pend_action) {
    case stun_action_tls_query:
      stun_obtain_shared_secret(sh, sh->sh_dns_pend_cb, sh->sh_dns_pend_ctx, TAG_NEXT(sh->sh_dns_pend_tags));
      break;
898

899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
    case stun_action_binding_request:
      stun_bind(sh, sh->sh_dns_pend_cb, sh->sh_dns_pend_ctx, TAG_NEXT(sh->sh_dns_pend_tags));
      break;
      
    case stun_action_test_lifetime:
      stun_test_lifetime(sh, sh->sh_dns_pend_cb, sh->sh_dns_pend_ctx, TAG_NEXT(sh->sh_dns_pend_tags));
      break;
      
    case stun_action_test_nattype:
      stun_test_nattype(sh, sh->sh_dns_pend_cb, sh->sh_dns_pend_ctx, TAG_NEXT(sh->sh_dns_pend_tags));
      break;
      
    default:
      SU_DEBUG_5(("Warning: unknown pending STUN DNS-SRV action.\n"));
    }
914
      }
915 916 917 918 919 920
  else {
    /* DNS lookup failed */
    SU_DEBUG_5(("Warning: STUN DNS-SRV lookup failed.\n"));
    if (sh->sh_dns_pend_cb) {
      sh->sh_dns_pend_cb(sh->sh_dns_pend_ctx, sh, NULL,
			 sh->sh_dns_pend_action, stun_error);
921 922
    }
  }
923 924 925 926 927

  su_free(sh->sh_home, sh->sh_dns_pend_tags), sh->sh_dns_pend_tags = NULL;
  sh->sh_dns_pend_action = 0;
  sh->sh_dns_pend_cb = NULL;
  sh->sh_dns_pend_ctx = NULL;
928 929 930
}

/**
931 932
 * Queus a discovery process for later execution when DNS-SRV lookup
 * has been completed.
933
 */
934 935 936 937 938
static int priv_dns_queue_action(stun_handle_t *sh,
				 stun_action_t action,
				 stun_discovery_f sdf,
				 stun_discovery_magic_t *magic,
				 tag_type_t tag, tag_value_t value, ...)
939
{
940
  ta_list ta;
941

942 943 944
  if (!sh->sh_dns_pend_action) {
    if (!sh->sh_dns_lookup) {
      sh->sh_dns_lookup = stun_dns_lookup((stun_magic_t*)sh, sh->sh_root, priv_lookup_cb, sh->sh_domain);
945
      ta_start(ta, tag, value);
946
      assert(sh->sh_dns_pend_tags == NULL);
947 948 949 950 951
      sh->sh_dns_pend_tags = tl_tlist(sh->sh_home, 
				      ta_tags(ta));
      ta_end(ta);
      sh->sh_dns_pend_cb = sdf;
      sh->sh_dns_pend_ctx = magic;
952 953

    }
954
    sh->sh_dns_pend_action |= action;
955 956

    return 0;
957
  }
958 959
  
  return -1;
960 961 962 963 964 965 966 967 968
}

static int priv_stun_bind_send(stun_handle_t *sh, stun_request_t *req, stun_discovery_t *sd)
{
  int res = stun_send_binding_request(req, sh->sh_pri_addr);
  if (res < 0) {
    stun_free_message(req->sr_msg);
    stun_discovery_destroy(sd);
  }
969
  return res;
970 971
}

972 973 974 975 976
/** 
 * Performs a STUN Binding Discovery (see RFC3489/3489bis) process
 *
 * To integrity protect the discovery process, first call 
 * stun_request_shared_secret() on the handle 'sh'.
Pekka Pessi's avatar
Pekka Pessi committed
977
 *
978 979 980 981
 * If STUNTAG_REGISTER_SOCKET() is omitted, or set to false, the
 * client is responsible for socket i/o. Other stun module will
 * perform the whole discovery process and return the results
 * via callback 'sdf'.
Pekka Pessi's avatar
Pekka Pessi committed
982
 * 
983 984 985
 * @param sh       pointer to valid stun handle
 * @param sdf      callback to signal process progress
 * @param magic    context pointer attached to 'sdf'
Pekka Pessi's avatar
Pekka Pessi committed
986
 *
987
 * @TAGS
988 989
 * @TAG STUNTAG_SOCKET Bind socket for STUN (socket handle).
 * @TAG STUNTAG_REGISTER_SOCKET Register socket for eventloop owned by STUN (boolean)
990

Pekka Pessi's avatar
Pekka Pessi committed
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
 * @return
 * On success, zero is returned.  Upon error, -1 is returned, and @e errno is
 * set appropriately.
 * 
 * @ERRORS
 * @ERROR EFAULT          An invalid address is given as argument
 * @ERROR EPROTONOSUPPORT Not a UDP socket.
 * @ERROR EINVAL          The socket is already bound to an address.
 * @ERROR EACCESS   	  The address is protected, and the user is not 
 *                  	  the super-user.
 * @ERROR ENOTSOCK  	  Argument is a descriptor for a file, not a socket.
 * @ERROR EAGAIN          Operation in progress. Application should call 
 *                        stun_bind() again when there is data available on 
 *                        the socket.
 */
1006 1007 1008 1009 1010
int stun_bind(stun_handle_t *sh,
	      stun_discovery_f sdf,
	      stun_discovery_magic_t *magic,
	      tag_type_t tag, tag_value_t value,
	      ...)
Pekka Pessi's avatar
Pekka Pessi committed
1011
{
1012
  su_socket_t s = -1;
1013
  stun_request_t *req = NULL;
1014
  stun_discovery_t *sd = NULL;
1015
  ta_list ta;
1016
  int index, s_reg = 0;
1017
  
Martti Mela's avatar
Martti Mela committed
1018
  enter;
Pekka Pessi's avatar
Pekka Pessi committed
1019 1020 1021

  if (sh == NULL)
    return errno = EFAULT, -1;
Martti Mela's avatar
Martti Mela committed
1022

1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
  if (!sh->sh_pri_addr[0].su_port) {
    /* no STUN server address, perform a DNS-SRV lookup */
    int err;
    ta_list ta;
    ta_start(ta, tag, value);
    SU_DEBUG_5(("Delaying STUN bind for DNS-SRV query.\n"));
    err = priv_dns_queue_action(sh, stun_action_binding_request, sdf, magic, ta_tags(ta));
    ta_end(ta);
    return err;
  }

1034
  ta_start(ta, tag, value);
Pekka Pessi's avatar
Pekka Pessi committed
1035

1036 1037
  tl_gets(ta_args(ta),
	  STUNTAG_SOCKET_REF(s),
1038
	  STUNTAG_REGISTER_EVENTS_REF(s_reg),
1039 1040
	  TAG_END());

1041 1042
  ta_end(ta);