/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005-2006 Nokia Corporation. * * Contact: Pekka Pessi * * This library is free software; you can redistribute it and/or * 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 * * See RFC 3489/3489bis for further information. * * @author Martti Mela * @author Tat Chan * @author Pekka Pessi * @author Kai Vehmanen * * @date Created: Thu Jul 24 17:21:00 2003 ppessi */ #include "config.h" #include #include #define SU_ROOT_MAGIC_T struct stun_magic_t #include "sofia-sip/stun.h" #include "stun_internal.h" #include "sofia-sip/stun_tag.h" #include #include #include #include #include #if defined(HAVE_OPENSSL) #include #endif #if !defined(ETIMEDOUT) && defined(_WIN32) #define ETIMEDOUT WSAETIMEDOUT #endif /* Missing socket symbols */ #ifndef SOL_TCP #define SOL_TCP IPPROTO_TCP #endif #if HAVE_FUNC #elif HAVE_FUNCTION #define __func__ __FUNCTION__ #else static char const __func__[] = "stun"; #endif /** STUN log. */ su_log_t stun_log[] = { SU_LOG_INIT("stun", "STUN_DEBUG", SU_DEBUG) }; enum { STUN_SENDTO_TIMEOUT = 1000, STUN_TLS_CONNECT_TIMEOUT = 8000, }; #if 0 /* XXX: not used at the moment (2006/03) */ static int stun_change_map[4][4] = { {0, 1, 2, 3}, /* no change */ {2, 3, 0, 1}, /* change ip */ {1, 0, 3, 2}, /* change port */ {3, 2, 1, 0} /* change ip and port, Ca:Cp */ }; #endif /* 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; #define CHG_IP 0x001 #define CHG_PORT 0x004 #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) struct stun_discovery_s { stun_discovery_t *sd_next, **sd_prev; /**< Linked list */ stun_handle_t *sd_handle; stun_discovery_f sd_callback; stun_discovery_magic_t *sd_magic; tagi_t *sd_tags; /** stored tags for the discovery */ 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 */ su_sockaddr_t sd_bind_addr[1]; /**< local address */ su_socket_t sd_socket2; /**< Alternative socket */ int sd_index; /**< root_register index */ /* Binding discovery */ su_sockaddr_t sd_addr_seen_outside[1]; /**< local address */ /* NAT type related */ stun_nattype_t sd_nattype; /**< Determined NAT type */ unsigned sd_first:1; /**< These are the requests */ unsigned sd_second:1; unsigned sd_third:1; unsigned sd_fourth:1; unsigned :0; /* Life time related */ int sd_lt_cur; int sd_lt; int sd_lt_max; /* Keepalive timeout */ unsigned int sd_timeout; su_timer_t *sd_timer; }; struct stun_request_s { su_timer_t *sr_timer; 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 */ su_socket_t sr_socket; /**< Alternative socket */ su_localinfo_t sr_localinfo; /**< local addrinfo */ su_sockaddr_t sr_local_addr[1]; /**< local address */ su_sockaddr_t sr_destination[1]; stun_state_t sr_state; /**< Progress states */ int sr_retry_count; /**< current retry number */ long sr_timeout; /**< timeout for next sendto() */ int sr_from_y; int sr_request_mask; /**< Mask consisting of chg_ip and chg_port */ stun_discovery_t *sr_discovery; }; struct stun_handle_s { su_home_t sh_home[1]; su_root_t *sh_root; /**< event loop */ int sh_root_index; /**< object index of su_root_register() */ stun_request_t *sh_requests; /**< outgoing requests list */ stun_discovery_t *sh_discoveries; /**< Actions list */ int sh_max_retries; /**< max resend for sendto() */ su_addrinfo_t sh_pri_info; /**< server primary info */ su_sockaddr_t sh_pri_addr[1]; /**< server primary address */ su_addrinfo_t sh_sec_info; /**< server secondary info */ su_sockaddr_t sh_sec_addr[1]; /**< server secondary address */ su_localinfo_t sh_localinfo; /**< local addrinfo */ su_sockaddr_t sh_local_addr[1]; /**< local address */ char *sh_domain; /**< domain address for DNS-SRV lookups */ stun_dns_lookup_t *sh_dns_lookup; stun_action_t sh_dns_pend_action; stun_discovery_f sh_dns_pend_cb; stun_discovery_magic_t *sh_dns_pend_ctx; tagi_t *sh_dns_pend_tags; #if defined(HAVE_OPENSSL) SSL_CTX *sh_ctx; /**< SSL context for TLS */ SSL *sh_ssl; /**< SSL handle for TLS */ #else void *sh_ctx; /**< SSL context for TLS */ void *sh_ssl; /**< SSL handle for TLS */ #endif stun_msg_t sh_tls_request; stun_msg_t sh_tls_response; int sh_nattype; /**< NAT-type, see stun_common.h */ #if 0 stun_event_f sh_callback; /**< callback for calling application */ stun_magic_t *sh_context; /**< application context */ #endif stun_buffer_t sh_username; stun_buffer_t sh_passwd; int sh_use_msgint; /**< try message integrity (TLS) */ int sh_req_msgint; /**< require use of msg-int (TLS) */ }; #define STUN_STATE_STR(x) case x: return #x char const *stun_str_state(stun_state_t state) { switch (state) { STUN_STATE_STR(stun_no_assigned_event); STUN_STATE_STR(stun_dispose_me); 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); STUN_STATE_STR(stun_discovery_init); STUN_STATE_STR(stun_discovery_processing); STUN_STATE_STR(stun_discovery_done); #if 0 STUN_STATE_STR(stun_bind_init); STUN_STATE_STR(stun_bind_processing); STUN_STATE_STR(stun_bind_done); STUN_STATE_STR(stun_bind_error); STUN_STATE_STR(stun_bind_timeout); STUN_STATE_STR(stun_request_not_found); #endif STUN_STATE_STR(stun_tls_connection_timeout); STUN_STATE_STR(stun_tls_connection_failed); STUN_STATE_STR(stun_tls_ssl_connect_failed); STUN_STATE_STR(stun_discovery_timeout); STUN_STATE_STR(stun_request_timeout); case stun_error: default: return "stun_error"; } } /** * Returns the NAT type attached to STUN discovery handle. */ char const *stun_nattype(stun_discovery_t *sd) { 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", }; if (sd) return stun_nattype_str[sd->sd_nattype]; else return stun_nattype_str[stun_nat_unknown]; } su_addrinfo_t const *stun_server_address(stun_handle_t *sh) { return &sh->sh_pri_info; } int stun_lifetime(stun_discovery_t *sd) { return sd ? sd->sd_lt_cur : -1; } #if defined(HAVE_OPENSSL) char const stun_version[] = "sofia-sip-stun using " OPENSSL_VERSION_TEXT; #else char const stun_version[] = "sofia-sip-stun"; #endif 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); static int process_test_lifetime(stun_request_t *req, stun_msg_t *binding_response); static stun_request_t *stun_request_create(stun_discovery_t *sd); static int stun_send_binding_request(stun_request_t *req, su_sockaddr_t *srvr_addr); static int stun_bind_callback(stun_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg); /* timers */ 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); static void stun_test_lifetime_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg); 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); 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, ...); /** * Return su_root_t assigned to stun_handle_t. * * @param self stun_handle_t object * @return su_root_t object, NULL if self not given. */ su_root_t *stun_root(stun_handle_t *self) { return self ? self->sh_root : NULL; } /** * Check if a STUN handle should be created. * * 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 * * @param tag,value,... tag-value list */ int stun_is_requested(tag_type_t tag, tag_value_t value, ...) { ta_list ta; tagi_t const *t, *t2; char const *stun_server; enter; ta_start(ta, tag, value); t = tl_find(ta_args(ta), stuntag_server); 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) stun_server = (char *)t2->t_value; else stun_server = getenv("STUN_SERVER"); ta_end(ta); return stun_server != NULL; } /** * 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) { err = stun_atoaddr(stun->sh_home, AF_INET, &stun->sh_pri_info, server); 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; } #if 0 /** * Creates a STUN handle. * * The created handles can be used for STUN binding discovery, * keepalives, and other STUN usages. * * @param context self pointer for callback 'cb' * @param root eventloop to used by the stun state machine * @param cb callback to signal state machine events * @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_create(stun_magic_t *context, su_root_t *root, stun_event_f cb, 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", "stun_handle_tcreate", 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) { err = stun_atoaddr(stun->sh_home, AF_INET, &stun->sh_pri_info, server); if (err < 0) return NULL; } stun->sh_nattype = stun_nat_unknown; stun->sh_root = root; stun->sh_context = context; stun->sh_callback = cb; /* 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; } #endif /** * Performs shared secret request/response processing. * Result will be trigged in STUN handle callback (state * one of stun_tls_*). **/ 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, ...) { #if defined(HAVE_OPENSSL) 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; stun_discovery_t *sd; /* stun_request_t *req; */ ta_list ta; assert(sh); enter; if (!sh->sh_pri_addr[0].su_port) { /* no STUN server address, perform a DNS-SRV lookup */ ta_list ta; ta_start(ta, tag, value); SU_DEBUG_5(("Delaying STUN shared-secret req. for DNS-SRV query.\n")); err = priv_dns_queue_action(sh, stun_action_tls_query, sdf, magic, ta_tags(ta)); ta_end(ta); return err; } ai = &sh->sh_pri_info; if (sh->sh_use_msgint == 1) { SU_DEBUG_3(("%s: Obtaining shared secret.\n", __func__)); } 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; } 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))); sd = stun_discovery_create(sh, stun_action_tls_query, sdf, magic); sd->sd_socket = s; /* req = stun_request_create(sd); */ events = SU_WAIT_CONNECT | SU_WAIT_ERR; if (su_wait_create(wait, s, events) == -1) STUN_ERROR(errno, su_wait_create); /* su_root_eventmask(sh->sh_root, sh->sh_root_index, s, events); */ if ((sd->sd_index = su_root_register(sh->sh_root, wait, stun_tls_callback, (su_wakeup_arg_t *) sd, 0)) == -1) { STUN_ERROR(errno, su_root_register); return -1; } ta_start(ta, tag, value); sd->sd_tags = tl_adup(sh->sh_home, ta_args(ta)); ta_end(ta); sd->sd_state = stun_tls_connecting; /* Create and start timer for connect() timeout */ SU_DEBUG_3(("%s: creating timeout timer for connect()\n", __func__)); sd->sd_timer = su_timer_create(su_root_task(sh->sh_root), STUN_TLS_CONNECT_TIMEOUT); su_timer_set(sd->sd_timer, stun_tls_connect_timer_cb, (su_wakeup_arg_t *) sd); return 0; #else /* !HAVE_OPENSSL */ return -1; #endif } static stun_request_t *stun_request_create(stun_discovery_t *sd) { stun_handle_t *sh = sd->sd_handle; stun_request_t *req = NULL; enter; req = calloc(sizeof(stun_request_t), 1); if (!req) return NULL; req->sr_handle = sh; req->sr_discovery = sd; /* This is the default */ req->sr_socket = sd->sd_socket; 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; /* req->sr_action = action; */ req->sr_request_mask = 0; req->sr_msg = calloc(sizeof(stun_msg_t), 1); req->sr_state = stun_discovery_init; memcpy(req->sr_local_addr, sd->sd_bind_addr, sizeof(su_sockaddr_t)); /* Insert this request to the request queue */ if (sh->sh_requests) x_insert(sh->sh_requests, req, sr); else sh->sh_requests = req; return req; } void stun_request_destroy(stun_request_t *req) { stun_handle_t *sh; assert(req); enter; sh = req->sr_handle; if (x_is_inserted(req, sr)) x_remove(req, sr); if (!x_is_inserted(req, sr) && !req->sr_next) sh->sh_requests = NULL; req->sr_handle = NULL; req->sr_discovery = NULL; /* memset(req->sr_destination, 0, sizeof(su_sockaddr_t)); */ 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; } free(req); SU_DEBUG_9(("%s: request destroyed.\n", __func__)); return; } /** Destroy a STUN client */ void stun_handle_destroy(stun_handle_t *sh) { stun_discovery_t *sd = NULL, *kill = NULL; stun_request_t *a, *b; enter; if (sh->sh_dns_lookup) stun_dns_lookup_destroy(sh->sh_dns_lookup); if (sh->sh_dns_pend_tags) su_free(sh->sh_home, sh->sh_dns_pend_tags); for (a = sh->sh_requests; a; ) { b = a->sr_next; stun_request_destroy(a); a = b; } /* There can be several discoveries using the same socket. It is still enough to deregister the socket in first of them */ for (sd = sh->sh_discoveries; sd; ) { kill = sd; sd = sd->sd_next; /* Index has same value as sockfd, right? ... or not? */ if (kill->sd_index != -1) su_root_deregister(sh->sh_root, kill->sd_index); if (kill->sd_action == stun_action_tls_query) su_close(kill->sd_socket); stun_discovery_destroy(kill); } su_home_zap(sh->sh_home); } /** Create wait object and register it to the handle callback */ int assign_socket(stun_discovery_t *sd, su_socket_t s, int reg_socket) { stun_handle_t *sh = sd->sd_handle; int events; stun_discovery_t *tmp; /* 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; su_wait_t wait[1] = { SU_WAIT_INIT }; enter; if (s == -1) { SU_DEBUG_3(("%s: invalid socket.\n", __func__)); return errno = EINVAL, -1; } for (tmp = sh->sh_discoveries; tmp; tmp = tmp->sd_next) { 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; } } sd->sd_socket = s; if (reg_socket != 1) return 0; /* set socket asynchronous */ if (su_setblocking(s, 0) < 0) { STUN_ERROR(errno, su_setblocking); su_close(s); return -1; } /* xxx -- check if socket is already assigned to this root */ 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 */ if ((sd->sd_index = su_root_register(sh->sh_root, wait, stun_bind_callback, (su_wakeup_arg_t *) sd, 0)) < 0) { STUN_ERROR(errno, su_root_register); return -1; } SU_DEBUG_7(("%s: socket registered.\n", __func__)); 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; } /** * Helper function needed by Cygwin builds. */ #if defined (__CYGWIN__) static int get_localinfo(su_localinfo_t *clientinfo) { su_localinfo_t hints[1] = {{ LI_CANONNAME | LI_NUMERIC }}, *li, *res = NULL; su_sockaddr_t *sa; int i, error, found = 0; char ipaddr[SU_ADDRSIZE + 2] = { 0 }; hints->li_family = AF_INET; if ((error = su_getlocalinfo(hints, &res)) == 0) { /* 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; clientinfo->li_addrlen = li->li_addrlen; sa = clientinfo->li_addr; memcpy(sa, li->li_addr, sizeof(su_sockaddr_t)); SU_DEBUG_3(("%s: local address found to be %s.\n", __func__, inet_ntop(clientinfo->li_family, SU_ADDR(sa), ipaddr, sizeof(ipaddr)))); 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; } #endif 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; res = stun_dns_lookup_udp_addr(self, &udp_target, &udp_port); if (res == 0 && udp_target) { /* XXX: assumption that same host and port used for UDP/TLS */ 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); /* step: now that server address is known, continue * the pending action */ SU_DEBUG_5(("STUN server address found, running queue actions (%d).\n", sh->sh_dns_pend_action)); 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; 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")); } } 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); } } 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; } /** * Queus a discovery process for later execution when DNS-SRV lookup * has been completed. */ 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, ...) { ta_list ta; 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); ta_start(ta, tag, value); assert(sh->sh_dns_pend_tags == NULL); 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; } sh->sh_dns_pend_action |= action; return 0; } return -1; } 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); } return res; } /** * 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'. * * 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'. * * @param sh pointer to valid stun handle * @param sdf callback to signal process progress * @param magic context pointer attached to 'sdf' * * @TAGS * @TAG STUNTAG_SOCKET Bind socket for STUN (socket handle). * @TAG STUNTAG_REGISTER_SOCKET Register socket for eventloop owned by STUN (boolean) * @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. */ int stun_bind(stun_handle_t *sh, stun_discovery_f sdf, stun_discovery_magic_t *magic, tag_type_t tag, tag_value_t value, ...) { su_socket_t s = -1; stun_request_t *req = NULL; stun_discovery_t *sd = NULL; ta_list ta; int index, s_reg = 0; enter; if (sh == NULL) return errno = EFAULT, -1; 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; } ta_start(ta, tag, value); tl_gets(ta_args(ta), STUNTAG_SOCKET_REF(s), STUNTAG_REGISTER_EVENTS_REF(s_reg), TAG_END()); ta_end(ta); sd = stun_discovery_create(sh, stun_action_binding_request, sdf, magic); if ((index = assign_socket(sd, s, s_reg)) < 0) return -1; req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, 0, 0) < 0) { stun_discovery_destroy(sd); stun_free_message(req->sr_msg); return -1; } /* note: we always report success if bind() succeeds */ return priv_stun_bind_send(sh, req, sd); } /** * Returns the address of the public binding allocated by the NAT. * * In case of multiple on path NATs, the binding allocated by * the outermost NAT is returned. * * This function returns the local address seen from outside. * Note that the address is not valid until the event stun_clien_done is launched. */ int stun_discovery_get_address(stun_discovery_t *sd, void *addr, socklen_t *return_addrlen) { int siz; enter; assert(sd && addr); siz = SU_SOCKADDR_SIZE(sd->sd_addr_seen_outside); /* Check if enough memory provided */ if (siz > *return_addrlen) return errno = EFAULT, -1; else *return_addrlen = siz; memcpy(addr, sd->sd_addr_seen_outside, siz); return 0; } static stun_discovery_t *stun_discovery_create(stun_handle_t *sh, stun_action_t action, stun_discovery_f sdf, stun_discovery_magic_t *magic) { stun_discovery_t *sd = NULL; enter; sd = calloc(1, sizeof(stun_discovery_t)); sd->sd_action = action; sd->sd_handle = sh; sd->sd_callback = sdf; sd->sd_magic = magic; sd->sd_lt_cur = 0; sd->sd_lt = STUN_LIFETIME_EST; sd->sd_lt_max = STUN_LIFETIME_MAX; sd->sd_pri_info.ai_addrlen = 16; sd->sd_pri_info.ai_addr = &sd->sd_pri_addr->su_sa; /* Insert this action to the discovery queue */ if (sh->sh_discoveries) x_insert(sh->sh_discoveries, sd, sd); else sh->sh_discoveries = sd; return sd; } static int stun_discovery_destroy(stun_discovery_t *sd) { stun_handle_t *sh; enter; if (!sd) return errno = EFAULT, -1; sh = sd->sd_handle; if (sd->sd_timer) su_timer_destroy(sd->sd_timer), sd->sd_timer = NULL; /* if we are in the queue*/ if (x_is_inserted(sd, sd)) x_remove(sd, sd); /* if we were the only one */ else if (!sd->sd_next) sh->sh_discoveries = NULL; sd->sd_next = NULL; free(sd); return 0; } /** * Initiates STUN discovery proces to find out NAT * characteristics. * * Note: does not support STUNTAG_DOMAIN() even if specified to * stun_handle_create(). * * @TAGS * @TAG STUNTAG_SOCKET Bind socket for STUN * @TAG STUNTAG_REGISTER_SOCKET Register socket for eventloop owned by STUN * @TAG STUNTAG_SERVER() stun server hostname or dotted IPv4 address * * @return 0 on success, non-zero on error */ int stun_test_nattype(stun_handle_t *sh, stun_discovery_f sdf, stun_discovery_magic_t *magic, tag_type_t tag, tag_value_t value, ...) { int err = 0, index = 0, s_reg = 0; ta_list ta; char const *server = NULL; stun_request_t *req = NULL; stun_discovery_t *sd = NULL; su_sockaddr_t bind_addr; su_socket_t s = -1; socklen_t bind_len; su_sockaddr_t *destination = NULL; enter; if (!sh->sh_pri_addr[0].su_port) { /* no STUN server address, perform a DNS-SRV lookup */ ta_list ta; ta_start(ta, tag, value); SU_DEBUG_5(("Delaying STUN get-nat-type req. for DNS-SRV query.\n")); err = priv_dns_queue_action(sh, stun_action_test_nattype, sdf, magic, ta_tags(ta)); ta_end(ta); return err; } ta_start(ta, tag, value); tl_gets(ta_args(ta), STUNTAG_SOCKET_REF(s), STUNTAG_REGISTER_EVENTS_REF(s_reg), STUNTAG_SERVER_REF(server), TAG_END()); ta_end(ta); bind_len = sizeof bind_addr; if (s < 0) return errno = EFAULT, -1; sd = stun_discovery_create(sh, stun_action_test_nattype, sdf, magic); if ((index = assign_socket(sd, s, s_reg)) < 0) return errno = EFAULT, -1; /* If no server given, use default address from stun_handle_create() */ if (!server) { /* memcpy(&sd->sd_pri_info, &sh->sh_pri_info, sizeof(su_addrinfo_t)); */ memcpy(sd->sd_pri_addr, sh->sh_pri_addr, sizeof(su_sockaddr_t)); } else { err = stun_atoaddr(sh->sh_home, AF_INET, &sd->sd_pri_info, server); memcpy(sd->sd_pri_addr, &sd->sd_pri_info.ai_addr, sizeof(su_sockaddr_t)); } destination = (su_sockaddr_t *) sd->sd_pri_addr; req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, STUNTAG_CHANGE_IP(0), STUNTAG_CHANGE_PORT(0), TAG_END()) < 0) return -1; err = stun_send_binding_request(req, destination); if (err < 0) { stun_free_message(req->sr_msg); return -1; } /* Same Public IP and port, Test III, server ip 0 or 1 should be the same */ req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, STUNTAG_CHANGE_IP(0), STUNTAG_CHANGE_PORT(1), TAG_END()) < 0) return -1; err = stun_send_binding_request(req, destination); if (err < 0) { stun_free_message(req->sr_msg); return -1; } req = NULL; req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, STUNTAG_CHANGE_IP(1), STUNTAG_CHANGE_PORT(1), TAG_END()) < 0) return -1; err = stun_send_binding_request(req, destination); if (err < 0) { stun_free_message(req->sr_msg); } return err; } /******************************************************************** * Internal functions *******************************************************************/ #if defined(HAVE_OPENSSL) static int stun_tls_callback(su_root_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg) { stun_discovery_t *sd = arg; stun_handle_t *self = sd->sd_handle; stun_msg_t *msg_req, *resp; int z, err; SSL_CTX* ctx; SSL *ssl; X509* server_cert; unsigned char buf[512]; stun_attr_t *password, *username; int state; int events = su_wait_events(w, sd->sd_socket), one = 0; unsigned int onelen; enter; SU_DEBUG_7(("%s(%p): events%s%s%s%s\n", __func__, self, events & SU_WAIT_CONNECT ? " CONNECTED" : "", events & SU_WAIT_ERR ? " ERR" : "", events & SU_WAIT_IN ? " IN" : "", events & SU_WAIT_OUT ? " OUT" : "")); getsockopt(sd->sd_socket, SOL_SOCKET, SO_ERROR, (void *)&one, &onelen); if (one != 0) { STUN_ERROR(one, getsockopt); } if (one || events & SU_WAIT_ERR) { su_wait_destroy(w); su_root_deregister(self->sh_root, sd->sd_index); sd->sd_index = -1; /* mark index as deregistered */ su_timer_reset(sd->sd_timer); SU_DEBUG_3(("%s: shared secret not obtained from server. " \ "Proceed without username/password.\n", __func__)); sd->sd_state = stun_tls_connection_failed; if (sd->sd_callback) sd->sd_callback(sd->sd_magic, self, sd, sd->sd_action, sd->sd_state); #if 0 else self->sh_callback(self->sh_context, self, sd, sd->sd_action, sd->sd_state); #endif return 0; } /* Can be NULL, too */ ssl = self->sh_ssl; msg_req = &self->sh_tls_request; resp = &self->sh_tls_response; state = sd->sd_state; switch (state) { case stun_tls_connecting: /* compose shared secret request */ if (stun_make_sharedsecret_req(msg_req) != 0) { STUN_ERROR(errno, stun_make_sharedsecret_req); stun_free_buffer(&msg_req->enc_buf); return -1; } /* openssl initiation */ SSLeay_add_ssl_algorithms(); SSL_load_error_strings(); ctx = SSL_CTX_new(TLSv1_client_method()); self->sh_ctx = ctx; if (ctx == NULL) { STUN_ERROR(errno, SSL_CTX_new); stun_free_buffer(&msg_req->enc_buf); return -1; } if (SSL_CTX_set_cipher_list(ctx, "AES128-SHA") == 0) { STUN_ERROR(errno, SSL_CTX_set_cipher_list); stun_free_buffer(&msg_req->enc_buf); return -1; } /* Start TLS negotiation */ ssl = SSL_new(ctx); self->sh_ssl = ssl; if (SSL_set_fd(ssl, sd->sd_socket) == 0) { STUN_ERROR(err, connect); stun_free_buffer(&msg_req->enc_buf); return -1; } /* No break here! Continue to SSL_connect. If SSL_continue returns * less than 1 because of nonblocking, have a different state * (ssl_connecting) for it */ case stun_tls_ssl_connecting: events = SU_WAIT_ERR | SU_WAIT_IN; su_root_eventmask(self->sh_root, sd->sd_index, sd->sd_socket, events); z = SSL_connect(ssl); err = SSL_get_error(ssl, z); if (z < 1 && (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE)) { sd->sd_state = stun_tls_ssl_connecting; return 0; } else if (z < 1) { su_wait_destroy(w); su_root_deregister(self->sh_root, sd->sd_index); sd->sd_index = -1; /* mark index as deregistered */ stun_free_buffer(&msg_req->enc_buf); sd->sd_state = stun_tls_connection_failed; if (sd->sd_callback) sd->sd_callback(sd->sd_magic, self, sd, sd->sd_action, sd->sd_state); #if 0 else self->sh_callback(self->sh_context, self, sd, sd->sd_action, sd->sd_state); #endif return -1; } /* Inform application about the progress */ sd->sd_state = stun_tls_writing; /* self->sh_callback(self->sh_context, self, self->sh_state); */ events = SU_WAIT_ERR | SU_WAIT_OUT; su_root_eventmask(self->sh_root, sd->sd_index, sd->sd_socket, events); break; case stun_tls_writing: events = SU_WAIT_ERR | SU_WAIT_IN; su_root_eventmask(self->sh_root, sd->sd_index, sd->sd_socket, events); SU_DEBUG_3(("TLS connection using %s\n", SSL_get_cipher(ssl))); server_cert = SSL_get_peer_certificate(ssl); if(server_cert) { SU_DEBUG_3(("\t subject: %s\n", X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0))); SU_DEBUG_3(("\t issuer: %s\n", X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0))); } X509_free(server_cert); z = SSL_write(ssl, msg_req->enc_buf.data, msg_req->enc_buf.size); if (z < 0) { err = SSL_get_error(ssl, z); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) return 0; else { STUN_ERROR(errno, SSL_write); stun_free_buffer(&msg_req->enc_buf); return -1; } } sd->sd_state = stun_tls_reading; break; case stun_tls_reading: events = SU_WAIT_ERR | SU_WAIT_OUT; su_root_eventmask(self->sh_root, sd->sd_index, sd->sd_socket, events); SU_DEBUG_5(("Shared Secret Request sent to server:\n")); debug_print(&msg_req->enc_buf); z = SSL_read(ssl, buf, sizeof(buf)); if (z <= 0) { err = SSL_get_error(ssl, z); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) return 0; else { stun_free_buffer(&msg_req->enc_buf); return -1; } } /* We end up here after there's something to read from the * socket */ resp->enc_buf.size = z; resp->enc_buf.data = malloc(z); memcpy(resp->enc_buf.data, buf, z); SU_DEBUG_5(("Shared Secret Response received from server:\n")); debug_print(&resp->enc_buf); /* closed TLS connection */ SSL_shutdown(ssl); #if 0 sd->sd_state = stun_tls_closing; break; case stun_tls_closing: #endif su_close(sd->sd_socket); SSL_free(self->sh_ssl), ssl = NULL; SSL_CTX_free(self->sh_ctx), ctx = NULL; stun_free_buffer(&msg_req->enc_buf); /* process response */ if (stun_parse_message(resp) < 0) { perror("stun_parse_message"); stun_free_buffer(&resp->enc_buf); return -1; } switch(resp->stun_hdr.msg_type) { case SHARED_SECRET_RESPONSE: username = stun_get_attr(resp->stun_attr, USERNAME); password = stun_get_attr(resp->stun_attr, PASSWORD); if (username != NULL && password != NULL) { /* move result to se */ stun_copy_buffer(&self->sh_username, username->pattr); stun_copy_buffer(&self->sh_passwd, password->pattr); } break; case SHARED_SECRET_ERROR_RESPONSE: if (stun_process_error_response(resp) < 0) { SU_DEBUG_5(("Error in Shared Secret Error Response.\n")); } stun_free_buffer(&resp->enc_buf); return -1; break; default: break; } su_wait_destroy(w); su_root_deregister(self->sh_root, sd->sd_index); sd->sd_index = -1; /* mark index as deregistered */ self->sh_use_msgint = 1; sd->sd_state = stun_tls_done; if (sd->sd_callback) sd->sd_callback(sd->sd_magic, self, sd, sd->sd_action, sd->sd_state); #if 0 else self->sh_callback(self->sh_context, self, sd, sd->sd_action, sd->sd_state); #endif break; default: return -1; } return 0; } #else static int stun_tls_callback(su_root_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg) { return 0; } #endif /* HAVE_OPENSSL */ #if defined(HAVE_OPENSSL) static void stun_tls_connect_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg) { stun_discovery_t *sd = arg; stun_handle_t *sh = sd->sd_handle; enter; su_timer_destroy(t); if (t == sd->sd_timer) { sd->sd_timer = NULL; } SU_DEBUG_7(("%s: timer destroyed.\n", __func__)); if (sd->sd_state != stun_tls_connecting) return; SU_DEBUG_7(("%s: connect() timeout.\n", __func__)); su_root_deregister(sh->sh_root, sd->sd_index); sd->sd_index = -1; /* mark index as deregistered */ sd->sd_state = stun_tls_connection_timeout; sd->sd_callback(sd->sd_magic, sh, sd, sd->sd_action, sd->sd_state); return; } #else static void stun_tls_connect_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg) { } #endif /* HAVE_OPENSSL */ /** Compose a STUN message of the format defined by stun_msg_t * result encoded in enc_buf ready for sending as well. */ int stun_make_sharedsecret_req(stun_msg_t *msg) { int i, len; uint16_t tmp; /* compose header */ msg->stun_hdr.msg_type = SHARED_SECRET_REQUEST; msg->stun_hdr.msg_len = 0; /* actual len computed by stun_send_message */ for (i = 0; i < STUN_TID_BYTES; i++) { msg->stun_hdr.tran_id[i] = (1 + rand() % RAND_MAX_16); } /* no buffer assigned yet */ stun_init_buffer(&msg->enc_buf); msg->enc_buf.data = malloc(20); msg->enc_buf.size = 20; tmp = htons(msg->stun_hdr.msg_type); len = 0; memcpy(msg->enc_buf.data, &tmp, sizeof(tmp)); len+=sizeof(tmp); tmp = htons(msg->stun_hdr.msg_len); memcpy(msg->enc_buf.data+len, &tmp, sizeof(tmp)); len+=sizeof(tmp); memcpy(msg->enc_buf.data+len, msg->stun_hdr.tran_id, STUN_TID_BYTES); len+=STUN_TID_BYTES; return 0; } /* Return action of the request. If no request, return default value */ static inline stun_action_t get_action(stun_request_t *req) { stun_discovery_t *sd = NULL; /* XXX -- if no sr_handle something is leaking... */ if (!req || !req->sr_discovery || !req->sr_handle) return stun_action_no_action; sd = req->sr_discovery; return sd->sd_action; } /* Find request from the request queue, based on TID */ static inline stun_request_t *find_request(stun_handle_t *self, void *id) { void *match; stun_request_t *req = NULL; int len = STUN_TID_BYTES; for (req = self->sh_requests; req; req = req->sr_next) { match = req->sr_msg->stun_hdr.tran_id; if (memcmp(match, id, len) == 0) { return req; } } return NULL; } /** Process socket event */ static int stun_bind_callback(stun_magic_t *m, su_wait_t *w, su_wakeup_arg_t *arg) { stun_discovery_t *sd = arg; stun_handle_t *self = sd->sd_handle; int retval = -1, err = -1, dgram_len; char ipaddr[SU_ADDRSIZE + 2]; stun_msg_t binding_response, *msg; unsigned char dgram[512] = { 0 }; su_sockaddr_t recv; socklen_t recv_len; su_socket_t s = sd->sd_socket; int events = su_wait_events(w, s); enter; SU_DEBUG_7(("%s(%p): events%s%s%s\n", __func__, self, events & SU_WAIT_IN ? " IN" : "", events & SU_WAIT_OUT ? " OUT" : "", events & SU_WAIT_ERR ? " ERR" : "")); if (!(events & SU_WAIT_IN || events & SU_WAIT_OUT)) { /* su_wait_destroy(w); */ /* su_root_deregister(self->sh_root, self->ss_root_index); */ /* self->sh_state = stun_bind_error; */ #if 0 self->sh_callback(self->sh_context, self, NULL, stun_action_no_action, stun_bind_error); #endif return 0; } /* receive response */ recv_len = sizeof(recv); dgram_len = recvfrom(s, dgram, sizeof(dgram), 0, (struct sockaddr *) &recv, &recv_len); err = errno; if ((dgram_len < 0) && (err != EAGAIN)) { /* su_wait_destroy(w); */ /* su_root_deregister(self->sh_root, self->ss_root_index); */ STUN_ERROR(err, recvfrom); /* stun_free_message(binding_request); */ return err; } else if (dgram_len <= 0) { STUN_ERROR(err, recvfrom); /* No data available yet, wait for the event. */ return 0; } /* Message received. */ binding_response.enc_buf.data = (unsigned char *) malloc(dgram_len); binding_response.enc_buf.size = dgram_len; memcpy(binding_response.enc_buf.data, dgram, dgram_len); SU_DEBUG_5(("%s: response from server %s:%u\n", __func__, inet_ntop(recv.su_family, SU_ADDR(&recv), ipaddr, sizeof(ipaddr)), ntohs(recv.su_port))); debug_print(&binding_response.enc_buf); /* Parse here the incoming message. */ if (stun_parse_message(&binding_response) < 0) { stun_free_message(&binding_response); SU_DEBUG_5(("%s: Error parsing response.\n", __func__)); return retval; } /* Based on the decoded payload, find the corresponding request * (based on TID). */ do_action(self, &binding_response); if (binding_response.enc_buf.size) free(binding_response.enc_buf.data); { stun_attr_t **a, *b; msg = &binding_response; for (a = &msg->stun_attr; *a;) { if ((*a)->pattr) free((*a)->pattr); if ((*a)->enc_buf.data) free((*a)->enc_buf.data); b = *a; b = b->next; free(*a); *a = NULL; *a = b; } } return 0; } /** Choose the right state machine */ static int do_action(stun_handle_t *sh, stun_msg_t *msg) { stun_request_t *req = NULL; stun_action_t action = stun_action_no_action; void *id; enter; if (!sh) return errno = EFAULT, -1; id = msg->stun_hdr.tran_id; req = find_request(sh, id); if (!req) { SU_DEBUG_7(("warning: unable to find matching TID for response\n")); return 0; } action = get_action(req); /* Based on the action, use different state machines */ switch (action) { case stun_action_binding_request: action_bind(req, msg); break; case stun_action_test_nattype: action_determine_nattype(req, msg); break; case stun_action_test_lifetime: process_test_lifetime(req, msg); break; case stun_action_keepalive: SU_DEBUG_3(("%s: Response to keepalive received.\n", __func__)); req->sr_state = stun_dispose_me; break; case stun_action_no_action: SU_DEBUG_3(("%s: Unknown response. No matching request found.\n", __func__)); #if 0 req->sr_state = stun_request_not_found; sh->sh_callback(sh->sh_context, sh, NULL, stun_action_no_action, req->sr_state); #endif req->sr_state = stun_dispose_me; break; default: SU_DEBUG_3(("%s: bad action.\n", __func__)); req->sr_state = stun_error; #if 0 sh->sh_callback(sh->sh_context, sh, NULL, stun_action_no_action, req->sr_state); #endif req->sr_state = stun_dispose_me; break; } return 0; } static int process_binding_request(stun_request_t *req, stun_msg_t *binding_response) { int retval = -1, clnt_addr_len; stun_attr_t *mapped_addr, *chg_addr; stun_handle_t *self = req->sr_handle; su_localinfo_t *clnt_info = &req->sr_localinfo; su_sockaddr_t *clnt_addr = clnt_info->li_addr; stun_msg_t *binding_request; stun_discovery_t *sd = req->sr_discovery; enter; binding_request = req->sr_msg; switch (binding_response->stun_hdr.msg_type) { case BINDING_RESPONSE: if (stun_validate_message_integrity(binding_response, &self->sh_passwd) < 0) { stun_free_message(binding_request); stun_free_message(binding_response); return retval; } memset(clnt_addr, 0, sizeof(su_sockaddr_t)); clnt_addr_len = sizeof(su_sockaddr_t); mapped_addr = stun_get_attr(binding_response->stun_attr, MAPPED_ADDRESS); if (mapped_addr != NULL) { memcpy(clnt_addr, mapped_addr->pattr, clnt_addr_len); retval = 0; } /* update alternative server address */ if (sd->sd_sec_addr->su_family == 0) { /* alternative server address not present */ chg_addr = stun_get_attr(binding_response->stun_attr, CHANGED_ADDRESS); if (chg_addr != NULL) memcpy(sd->sd_sec_addr, chg_addr->pattr, sizeof(struct sockaddr_in)); } break; case BINDING_ERROR_RESPONSE: default: if (stun_process_error_response(binding_response) < 0) { SU_DEBUG_3(("%s: Error in Binding Error Response.\n", __func__)); } req->sr_state = stun_discovery_error; break; } return retval; } static void stun_test_lifetime_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg) { stun_request_t *req = arg; stun_discovery_t *sd = req->sr_discovery; su_sockaddr_t *destination; int err; enter; su_timer_destroy(t); destination = (su_sockaddr_t *) sd->sd_pri_addr; err = stun_send_binding_request(req, destination); if (err < 0) { stun_free_message(req->sr_msg); return; } return; } static int process_test_lifetime(stun_request_t *req, stun_msg_t *binding_response) { stun_discovery_t *sd = req->sr_discovery; stun_request_t *new; stun_handle_t *sh = req->sr_handle; su_localinfo_t *li; su_sockaddr_t *sa; su_timer_t *sockfdy_timer = NULL; su_socket_t sockfdy = sd->sd_socket2; int err; stun_action_t action = get_action(req); su_sockaddr_t *destination; /* Even the first message could not be delivered */ if ((req->sr_state == stun_request_timeout) && (req->sr_from_y == -1)) { SU_DEBUG_0(("%s: lifetime determination failed.\n", __func__)); sd->sd_state = stun_discovery_timeout; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; return 0; } if (abs(sd->sd_lt_cur - sd->sd_lt) <= STUN_LIFETIME_CI) { sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; return 0; } /* We come here as a response to a request send from the sockfdy */ if (req->sr_from_y == 1) { req->sr_state = stun_dispose_me, req = NULL; new = stun_request_create(sd); new->sr_from_y = 0; if (stun_make_binding_req(sh, new, new->sr_msg, 0, 0) < 0) return -1; destination = (su_sockaddr_t *) sd->sd_pri_addr; err = stun_send_binding_request(new, destination); if (err < 0) { stun_free_message(new->sr_msg); return -1; } return 0; } else if (req->sr_from_y == 0) { if (req->sr_state != stun_discovery_timeout) { /* mapping with X still valid */ sd->sd_lt_cur = sd->sd_lt; sd->sd_lt = (int) (sd->sd_lt + sd->sd_lt_max) / 2; SU_DEBUG_1(("%s: Response received from socket X, " \ "lifetime at least %d sec, next trial: %d sec\n", __func__, sd->sd_lt_cur, sd->sd_lt)); } else { sd->sd_lt_max = sd->sd_lt; sd->sd_lt = (int) (sd->sd_lt + sd->sd_lt_cur) / 2; SU_DEBUG_1(("%s: No response received from socket X, " \ "lifetime at most %d sec, next trial: %d sec\n", __func__, sd->sd_lt_max, sd->sd_lt)); } } /* Rock, we come from sockfdx */ process_binding_request(req, binding_response); li = &req->sr_localinfo; sa = req->sr_local_addr; stun_free_message(binding_response); /* Destroy me with the bad mofo timer */ req->sr_state = stun_dispose_me, req = NULL; new = stun_request_create(sd); /* Use sockfdy */ new->sr_socket = sockfdy; new->sr_from_y = 1; if (stun_make_binding_req(sh, new, new->sr_msg, 0, 0) < 0) return -1; stun_add_response_address(new->sr_msg, (struct sockaddr_in *) sa); /* Create and start timer */ sockfdy_timer = su_timer_create(su_root_task(sh->sh_root), sd->sd_lt); su_timer_set(sockfdy_timer, stun_test_lifetime_timer_cb, (su_wakeup_arg_t *) new); return 0; } static int action_bind(stun_request_t *req, stun_msg_t *binding_response) { su_localinfo_t *li = NULL; su_sockaddr_t *sa = NULL; stun_discovery_t *sd = req->sr_discovery; stun_handle_t *sh = req->sr_handle; stun_action_t action; enter; action = get_action(req); process_binding_request(req, binding_response); li = &req->sr_localinfo; sa = req->sr_local_addr; memcpy(sd->sd_addr_seen_outside, sa, sizeof(su_sockaddr_t)); sd->sd_state = stun_discovery_done; if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; return 0; } static int action_determine_nattype(stun_request_t *req, stun_msg_t *binding_response) { su_sockaddr_t local; socklen_t locallen; stun_handle_t *sh = req->sr_handle; su_localinfo_t *li = NULL; stun_discovery_t *sd = req->sr_discovery; su_socket_t s = sd->sd_socket; stun_action_t action; int err; enter; action = get_action(req); /* If the NAT type is already detected, ignore this request */ if (!sd || (sd->sd_nattype != stun_nat_unknown)) { req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ return 0; } /* parse first the payload */ if (binding_response) process_binding_request(req, binding_response); /* mapped address */ li = &req->sr_localinfo; if (req->sr_request_mask == 0) { sd->sd_first = 1; memcpy(sd->sd_addr_seen_outside, li->li_addr, sizeof(su_sockaddr_t)); } else if (req->sr_request_mask & (CHG_IP | CHG_PORT)) sd->sd_second = 1; else if (req->sr_request_mask & CHG_PORT) sd->sd_third = 1; memset(&local, 0, sizeof(local)); locallen = sizeof(local); err = getsockname(s, (struct sockaddr *) &local, &locallen); if (err < 0) STUN_ERROR(err, getsockname); if ((req->sr_state == stun_discovery_timeout)) { if (sd->sd_first && sd->sd_second && sd->sd_third && sd->sd_fourth) { sd->sd_nattype = stun_nat_port_res_cone; sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } else if (sd->sd_first && sd->sd_second && sd->sd_fourth) { /* Sudden network problem */ sd->sd_nattype = stun_nat_unknown; sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } else if (sd->sd_first && sd->sd_second) { if (memcmp(li->li_addr, li->li_addr, 8) == 0) { sd->sd_nattype = stun_sym_udp_fw; sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } else { sd->sd_fourth = 1; /* The request will be destroyed by the timer */ req->sr_state = stun_dispose_me; req = NULL; req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, 0, 0) < 0) return -1; err = stun_send_binding_request(req, sd->sd_sec_addr); if (err < 0) { stun_free_message(req->sr_msg); return -1; } return 0; } } else if (sd->sd_first) { sd->sd_nattype = stun_udp_blocked; sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } } else { if (sd->sd_first && sd->sd_second && sd->sd_third && sd->sd_fourth) { if (memcmp(li->li_addr, sd->sd_addr_seen_outside, 8) == 0) { /* Response: Type 6 - Restricted */ sd->sd_nattype = stun_nat_res_cone; } else { sd->sd_nattype = stun_nat_sym; } sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } if (sd->sd_first && sd->sd_second) { if (memcmp(li->li_addr, sd->sd_addr_seen_outside, 8) == 0) sd->sd_nattype = stun_open_internet; else sd->sd_nattype = stun_nat_full_cone; sd->sd_state = stun_discovery_done; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; /* stun_request_destroy(req); */ /* stun_discovery_destroy(sd); */ return 0; } else if (sd->sd_first) { if (memcmp(&local, li->li_addr, 8) == 0) return 0; } } /* The discovery process is still ongoing, but I can be killed */ req->sr_state = stun_dispose_me; return 0; } static void stun_sendto_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg) { stun_request_t *req = arg; stun_handle_t *sh = req->sr_handle; stun_discovery_t *sd = req->sr_discovery; stun_action_t action = get_action(req); long timeout = 0; enter; if (req->sr_state == stun_dispose_me) { stun_request_destroy(req); SU_DEBUG_7(("%s: timer destroyed.\n", __func__)); return; } ++req->sr_retry_count; /* check if max retry count has been exceeded */ if (req->sr_retry_count >= sh->sh_max_retries) { errno = ETIMEDOUT; STUN_ERROR(errno, stun_sendto_timer_cb); stun_free_message(req->sr_msg); free(req->sr_msg), req->sr_msg = NULL; /* Either the server was dead, address wrong or STUN_UDP_BLOCKED */ /* sd->sd_nattype = stun_udp_blocked; */ req->sr_state = stun_request_timeout; /* If the action is binding request, we are done. If action was NAT type determination, process with the state machine. */ switch (action) { case stun_action_binding_request: sd->sd_state = stun_discovery_timeout; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif req->sr_state = stun_dispose_me; break; case stun_action_test_nattype: action_determine_nattype(req, NULL); break; case stun_action_test_lifetime: process_test_lifetime(req, NULL); break; case stun_action_keepalive: sd->sd_state = stun_discovery_timeout; /* Use per discovery specific callback */ if (sd->sd_callback) sd->sd_callback(sd->sd_magic, sh, sd, action, sd->sd_state); #if 0 else sh->sh_callback(sh->sh_context, sh, sd, action, sd->sd_state); #endif stun_keepalive_destroy(sh, sd->sd_socket); break; default: break; return; } /* Destroy me immediately */ req->sr_state = stun_dispose_me; timeout = 0; } else { SU_DEBUG_3(("%s: Timeout no. %d, retransmitting.\n", __func__, req->sr_retry_count)); /* Use pre-defined destination address for re-sends */ if (stun_send_message(req->sr_socket, req->sr_destination, req->sr_msg, &(sh->sh_passwd)) < 0) { stun_free_message(req->sr_msg); free(req->sr_msg), req->sr_msg = NULL; return; } timeout = req->sr_timeout *= 2; } su_timer_set_at(t, stun_sendto_timer_cb, (su_wakeup_arg_t *) req, su_time_add(su_now(), timeout)); return; } /** This function sends a binding request to the address at serv (ip, * port). which could be the original or alternative socket addresses * of the STUN server. Local address is provided in cli, and * resulting mapped address is also saved in cli. * Return 0 if successful, -1 if failed * * @return * On success, zero is returned. Upon error, -1 is returned, and @e errno is * set appropriately. * * @ERRORS * @ERROR EBADF @a sockfd is not a valid deseriptor. * @ERROR EPROTONOSUPPORT @a sockfd is not an 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. * @ERROR ETIMEDOUT Request timed out. * */ static int stun_send_binding_request(stun_request_t *req, su_sockaddr_t *srvr_addr) { su_timer_t *sendto_timer = NULL; int s; stun_handle_t *sh = req->sr_handle; stun_msg_t *msg = req->sr_msg; assert (sh && srvr_addr); enter; s = req->sr_socket; memcpy(req->sr_destination, srvr_addr, sizeof(su_sockaddr_t)); if (stun_send_message(s, srvr_addr, msg, &(sh->sh_passwd)) < 0) { return -1; } /* Create and start timer */ sendto_timer = su_timer_create(su_root_task(sh->sh_root), STUN_SENDTO_TIMEOUT); su_timer_set(sendto_timer, stun_sendto_timer_cb, (su_wakeup_arg_t *) req); req->sr_timer = sendto_timer; req->sr_state = stun_discovery_processing; return 0; } /** Compose a STUN message of the format defined by stun_msg_t */ int stun_make_binding_req(stun_handle_t *sh, stun_request_t *req, stun_msg_t *msg, tag_type_t tag, tag_value_t value, ...) { int i; stun_attr_t *tmp, **p; int bits = 0; int chg_ip = 0, chg_port = 0; ta_list ta; enter; ta_start(ta, tag, value); tl_gets(ta_args(ta), STUNTAG_CHANGE_IP_REF(chg_ip), STUNTAG_CHANGE_PORT_REF(chg_port), TAG_END()); ta_end(ta); if (chg_ip) bits |= CHG_IP; if (chg_port) bits |= CHG_PORT; if (req) req->sr_request_mask = bits; /* compose header */ msg->stun_hdr.msg_type = BINDING_REQUEST; msg->stun_hdr.msg_len = 0; /* actual len computed by stun_send_message */ for (i = 0; i < STUN_TID_BYTES; i++) { msg->stun_hdr.tran_id[i] = (1 + rand() % RAND_MAX_16); } /* optional attributes: * - Response Address * - Change Request X * - Username * - Message-Integrity */ msg->stun_attr = NULL; /* CHANGE_REQUEST */ p = &(msg->stun_attr); if (chg_ip || chg_port) { stun_attr_changerequest_t *attr_cr; tmp = (stun_attr_t *) malloc(sizeof(stun_attr_t)); tmp->attr_type = CHANGE_REQUEST; attr_cr = (stun_attr_changerequest_t *) malloc(sizeof(stun_attr_changerequest_t)); attr_cr->value = (chg_ip ? STUN_CR_CHANGE_IP : 0) | (chg_port ? STUN_CR_CHANGE_PORT : 0); tmp->pattr = attr_cr; tmp->next = NULL; *p = tmp; p = &(tmp->next); } /* USERNAME */ if (sh->sh_use_msgint && sh->sh_username.data && sh->sh_passwd.data) { tmp = (stun_attr_t *) malloc(sizeof(stun_attr_t)); tmp->attr_type = USERNAME; tmp->pattr = &sh->sh_username; tmp->next = NULL; *p = tmp; p = &(tmp->next); /* dummy MESSAGE_INTEGRITY attribute, computed later */ tmp = (stun_attr_t *) malloc(sizeof(stun_attr_t)); tmp->attr_type = MESSAGE_INTEGRITY; tmp->pattr = NULL; tmp->next = NULL; *p = tmp; p = &(tmp->next); } /* no buffer assigned yet */ msg->enc_buf.data = NULL; msg->enc_buf.size = 0; return 0; } int stun_process_response(stun_msg_t *msg) { enter; /* parse msg first */ if (stun_parse_message(msg) < 0) { SU_DEBUG_3(("%s: Error parsing response.\n", __func__)); return -1; } /* check message digest if exists */ switch (msg->stun_hdr.msg_type) { case BINDING_RESPONSE: if (stun_process_binding_response(msg) < 0) return -1; break; case BINDING_ERROR_RESPONSE: if (stun_process_error_response(msg) < 0) return -1; break; default: return -1; } return 0; } /** process binding response */ int stun_process_binding_response(stun_msg_t *msg) { /* currently not needed. */ return 0; } /** process binding error response * Report error and return */ int stun_process_error_response(stun_msg_t *msg) { stun_attr_t *attr; stun_attr_errorcode_t *ec; enter; attr = stun_get_attr(msg->stun_attr, ERROR_CODE); if (attr == NULL) { perror("stun_process_error_response"); return -1; } ec = (stun_attr_errorcode_t *)attr->pattr; SU_DEBUG_5(("%s: Received Binding Error Response:\n", __func__)); SU_DEBUG_5(("%s: Error: %d %s\n", __func__, ec->code, ec->phrase)); return 0; } /** * Sets values for USERNAME and PASSWORD stun fields * for the handle. */ int stun_set_uname_pwd(stun_handle_t *sh, const char *uname, int len_uname, const char *pwd, int len_pwd) { enter; sh->sh_username.data = (unsigned char *) malloc(len_uname); memcpy(sh->sh_username.data, uname, len_uname); sh->sh_username.size = len_uname; sh->sh_passwd.data = (unsigned char *) malloc(len_pwd); memcpy(sh->sh_passwd.data, pwd, len_pwd); sh->sh_passwd.size = len_pwd; sh->sh_use_msgint = 1; /* turn on message integrity ussage */ return 0; } /** * Converts character address format to sockaddr_in */ int stun_atoaddr(su_home_t *home, int ai_family, su_addrinfo_t *info, char const *in) { su_addrinfo_t *res = NULL, *ai, hints[1] = {{ 0 }}; char const *host; char *port = NULL, tmp[SU_ADDRSIZE]; int err; su_sockaddr_t *dstaddr; assert(info && in); enter; dstaddr = (su_sockaddr_t *) info->ai_addr; /* note: works only for IPv4 */ hints->ai_family = ai_family; port = strstr(in, ":"); if (port == NULL) { host = in; } else { assert(port - in < strlen(in) + 1); memcpy(tmp, in, port - in); tmp[port - in] = 0; host = tmp; ++port; } err = su_getaddrinfo(host, NULL, hints, &res); if (err == 0) { for (ai = res; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET) continue; info->ai_flags = ai->ai_flags; info->ai_family = ai->ai_family; info->ai_socktype = ai->ai_socktype; info->ai_protocol = ai->ai_protocol; info->ai_addrlen = ai->ai_addrlen; info->ai_canonname = su_strdup(home, host); memcpy(&dstaddr->su_sa, res->ai_addr, sizeof(struct sockaddr)); break; } if (port) dstaddr->su_port = htons(atoi(port)); else dstaddr->su_port = htons(STUN_DEFAULT_PORT); } else { STUN_ERROR(err, su_getaddrinfo); } if (res) su_freeaddrinfo(res); return err; } /** * Initiates STUN discovery process to find out NAT * binding life-time settings. * * @TAGS * @TAG STUNTAG_SOCKET Bind socket for STUN * @TAG STUNTAG_REGISTER_EVENTS Register socket for eventloop owned by STUN * @TAG STUNTAG_SERVER() stun server hostname or dotted IPv4 address * * @return 0 on success, non-zero on error */ int stun_test_lifetime(stun_handle_t *sh, stun_discovery_f sdf, stun_discovery_magic_t *magic, tag_type_t tag, tag_value_t value, ...) { stun_request_t *req = NULL; stun_discovery_t *sd = NULL; ta_list ta; int s = -1, err, index = 0, s_reg = 0; char ipaddr[SU_ADDRSIZE + 2] = { 0 }; char const *server = NULL; su_socket_t sockfdy; socklen_t y_len; su_sockaddr_t y_addr; su_sockaddr_t *destination; assert(sh); enter; if (!sh->sh_pri_addr[0].su_port) { /* no STUN server address, perform a DNS-SRV lookup */ ta_list ta; ta_start(ta, tag, value); SU_DEBUG_5(("Delaying STUN get-lifetime req. for DNS-SRV query.\n")); err = priv_dns_queue_action(sh, stun_action_test_lifetime, sdf, magic, ta_tags(ta)); ta_end(ta); return err; } ta_start(ta, tag, value); tl_gets(ta_args(ta), STUNTAG_SOCKET_REF(s), STUNTAG_REGISTER_EVENTS_REF(s_reg), STUNTAG_SERVER_REF(server), TAG_END()); sd = stun_discovery_create(sh, stun_action_test_lifetime, sdf, magic); if ((index = assign_socket(sd, s, s_reg)) < 0) return errno = EFAULT, -1; /* If no server given, use default address from stun_handle_create() */ if (!server) { /* memcpy(&sd->sd_pri_info, &sh->sh_pri_info, sizeof(su_addrinfo_t)); */ memcpy(sd->sd_pri_addr, sh->sh_pri_addr, sizeof(su_sockaddr_t)); } else { err = stun_atoaddr(sh->sh_home, AF_INET, &sd->sd_pri_info, server); memcpy(sd->sd_pri_addr, &sd->sd_pri_info.ai_addr, sizeof(su_sockaddr_t)); } destination = (su_sockaddr_t *) sd->sd_pri_addr; req = stun_request_create(sd); /* ci = &req->sr_localinfo; */ /* get local ip address */ /* get_localinfo(ci); */ /* initialize socket y */ sockfdy = socket(AF_INET, SOCK_DGRAM, 0); /* set socket asynchronous */ if (su_setblocking(sockfdy, 0) < 0) { STUN_ERROR(errno, su_setblocking); su_close(sockfdy); return errno = EFAULT, -1; } sd->sd_socket2 = sockfdy; memset(&y_addr, 0, sizeof(y_addr)); memcpy(&y_addr, sd->sd_bind_addr, sizeof(y_addr)); y_addr.su_port = 0; y_len = sizeof(y_addr); if (bind(sockfdy, (struct sockaddr *) &y_addr, y_len) < 0) { return -1; } if (getsockname(sockfdy, (struct sockaddr *) &y_addr, &y_len) < 0) { STUN_ERROR(errno, getsockname); return -1; } SU_DEBUG_3(("%s: socket y bound to %s:%u\n", __func__, inet_ntop(y_addr.su_family, SU_ADDR(&y_addr), ipaddr, sizeof(ipaddr)), (unsigned) ntohs(y_addr.su_port))); req->sr_from_y = -1; SU_DEBUG_1(("%s: determining binding life time, this may take a while.\n", __func__)); if (stun_make_binding_req(sh, req, req->sr_msg, 0, 0) < 0) return -1; err = stun_send_binding_request(req, destination); if (err < 0) { stun_free_message(req->sr_msg); return -1; } ta_end(ta); return 0; } int stun_add_response_address(stun_msg_t *req, struct sockaddr_in *mapped_addr) { stun_attr_sockaddr_t *addr; stun_attr_t *tmp; enter; tmp = malloc(sizeof(stun_attr_t)); tmp->attr_type = RESPONSE_ADDRESS; addr = malloc(sizeof(stun_attr_sockaddr_t)); memcpy(addr, mapped_addr, sizeof(stun_attr_sockaddr_t)); tmp->pattr = addr; if (req->stun_attr == NULL) { tmp->next = NULL; } else { tmp->next = req->stun_attr; } req->stun_attr = tmp; return 0; } /** * Determines if the message is STUN message (-1 if not stun). */ int stun_msg_is_keepalive(uint16_t data) { uint16_t msg_type; /* parse header */ msg_type = ntohs(data); if (msg_type == BINDING_REQUEST || msg_type == BINDING_RESPONSE || msg_type == BINDING_ERROR_RESPONSE) { return 0; } return -1; } /** * Determines length of STUN message (0 if not stun). */ int stun_message_length(void *data, int len, int end_of_message) { unsigned char *p; uint16_t tmp16, msg_type; /* parse header first */ p = data; memcpy(&tmp16, p, 2); msg_type = ntohs(tmp16); if (msg_type == BINDING_REQUEST || msg_type == BINDING_RESPONSE || msg_type == BINDING_ERROR_RESPONSE) { p+=2; memcpy(&tmp16, p, 2); /* return message length */ return ntohs(tmp16); } else return -1; } /** Process incoming message */ int stun_process_message(stun_handle_t *sh, su_socket_t s, su_sockaddr_t *sa, socklen_t salen, void *data, int len) { int retval = -1; stun_msg_t msg; enter; /* Message received. */ msg.enc_buf.data = data; msg.enc_buf.size = len; debug_print(&msg.enc_buf); /* Parse here the incoming message. */ if (stun_parse_message(&msg) < 0) { stun_free_message(&msg); SU_DEBUG_5(("%s: Error parsing response.\n", __func__)); return retval; } if (msg.stun_hdr.msg_type == BINDING_REQUEST) { return stun_process_request(s, &msg, 0, sa, salen); } else if (msg.stun_hdr.msg_type == BINDING_RESPONSE) { /* Based on the decoded payload, find the corresponding request * (based on TID). */ return do_action(sh, &msg); } return -1; } int stun_discovery_release_socket(stun_discovery_t *sd) { stun_handle_t *sh = sd->sd_handle; if (su_root_deregister(sh->sh_root, sd->sd_index) >= 0) { SU_DEBUG_3(("%s: socket deregistered from STUN \n", __func__)); sd->sd_index = -1; /* mark index as deregistered */ return 0; } return -1; } /** * Creates a keepalive dispatcher for bound SIP sockets */ int stun_keepalive(stun_handle_t *sh, su_sockaddr_t *sa, tag_type_t tag, tag_value_t value, ...) { int s = -1; unsigned int timeout = 0; ta_list ta; stun_discovery_t *sd; stun_request_t *req; stun_action_t action = stun_action_keepalive; char ipaddr[SU_ADDRSIZE + 2] = { 0 }; enter; ta_start(ta, tag, value); tl_gets(ta_args(ta), STUNTAG_SOCKET_REF(s), STUNTAG_TIMEOUT_REF(timeout), TAG_END()); if (s < 1 || !sa || timeout == 0) return errno = EFAULT, -1; /* If there already is keepalive associated with the given socket, * destroy it. */ stun_keepalive_destroy(sh, s); /*Ok, here we go */ sd = stun_discovery_create(sh, action, NULL, NULL); /* XXX -- specify last params if necessary */ sd->sd_socket = s; sd->sd_timeout = timeout; memcpy(sd->sd_pri_addr, sa, sizeof(*sa)); req = stun_request_create(sd); SU_DEBUG_3(("%s: Starting to send STUN keepalives to %s:%u\n", __func__, inet_ntop(sa->su_family, SU_ADDR(sa), ipaddr, sizeof(ipaddr)), (unsigned) ntohs(sa->su_port))); if (stun_make_binding_req(sh, req, req->sr_msg, 0, 0) < 0 || stun_send_binding_request(req, sa) < 0) { stun_request_destroy(req); stun_discovery_destroy(sd); return -1; } sd->sd_timer = su_timer_create(su_root_task(sh->sh_root), timeout); su_timer_set(sd->sd_timer, stun_keepalive_timer_cb, (su_wakeup_arg_t *) sd); ta_end(ta); return 0; } /** Send SIP keepalives */ static void stun_keepalive_timer_cb(su_root_magic_t *magic, su_timer_t *t, su_timer_arg_t *arg) { stun_discovery_t *sd = arg; stun_handle_t *sh = sd->sd_handle; int timeout = sd->sd_timeout; su_sockaddr_t *destination = sd->sd_pri_addr; stun_request_t *req; enter; su_timer_destroy(t); if (sd->sd_state == stun_discovery_timeout) return; req = stun_request_create(sd); if (stun_make_binding_req(sh, req, req->sr_msg, 0, 0) < 0 || stun_send_binding_request(req, destination) < 0) { stun_request_destroy(req); stun_discovery_destroy(sd); return; } sd->sd_timer = su_timer_create(su_root_task(sh->sh_root), timeout); su_timer_set(sd->sd_timer, stun_keepalive_timer_cb, (su_wakeup_arg_t *) sd); return; } /** * Destroys the keepalive dispatcher without touching the socket */ int stun_keepalive_destroy(stun_handle_t *sh, su_socket_t s) { stun_discovery_t *sd = NULL; stun_request_t *req; stun_action_t action = stun_action_keepalive; if (sh == NULL) return 1; /* Go through the request queue and destroy keepalive requests * associated with the given socket. */ for (req = sh->sh_requests; req; req = req->sr_next) { if (req->sr_socket == s && req->sr_discovery->sd_action == action) { req->sr_state = stun_dispose_me; if (!sd) sd = req->sr_discovery; } } /* No keepalive found */ if (!sd) return 1; su_timer_destroy(sd->sd_timer), sd->sd_timer = NULL; stun_discovery_destroy(sd); return 0; } int stun_process_request(su_socket_t s, stun_msg_t *req, int sid, su_sockaddr_t *from_addr, int from_len) { stun_msg_t resp; su_sockaddr_t mod_addr[1] = {{ 0 }}, src_addr[1] = {{ 0 }}, chg_addr[1] = {{ 0 }}; stun_attr_t *tmp, m_attr[1], s_attr[1], c_attr[1], **p; su_sockaddr_t to_addr; int c, i; tmp = stun_get_attr(req->stun_attr, RESPONSE_ADDRESS); if (tmp) { memcpy(&to_addr, tmp->pattr, sizeof(to_addr)); } else { memcpy(&to_addr, from_addr, sizeof(to_addr)); } /* compose header */ stun_init_message(&resp); resp.stun_hdr.msg_type = BINDING_RESPONSE; resp.stun_hdr.msg_len = 0; /* actual len computed later */ for (i = 0; i < STUN_TID_BYTES; i++) { resp.stun_hdr.tran_id[i] = req->stun_hdr.tran_id[i]; } p = &(resp.stun_attr); /* MAPPED-ADDRESS */ tmp = m_attr; tmp->attr_type = MAPPED_ADDRESS; memcpy(mod_addr, from_addr, sizeof(*mod_addr)); tmp->pattr = mod_addr; tmp->next = NULL; *p = tmp; p = &(tmp->next); /* SOURCE-ADDRESS depends on CHANGE_REQUEST */ tmp = stun_get_attr(req->stun_attr, CHANGE_REQUEST); if (!tmp) { c = 0; } else { switch (((stun_attr_changerequest_t *) tmp->pattr)->value) { case STUN_CR_CHANGE_IP: c = 1; break; case STUN_CR_CHANGE_PORT: c = 2; break; case STUN_CR_CHANGE_IP | STUN_CR_CHANGE_PORT: /* bitwise or */ c = 3; break; default: return -1; } } tmp = s_attr; tmp->attr_type = SOURCE_ADDRESS; /* memcpy(src_addr, &stun_change_map[c][sid], sizeof(*src_addr)); */ tmp->pattr = src_addr; tmp->next = NULL; *p = tmp; p = &(tmp->next); /* CHANGED-ADDRESS */ /* depends on sid */ tmp = c_attr; tmp->attr_type = CHANGED_ADDRESS; /* memcpy(chg_addr, &stun_change_map[3][sid], sizeof(*chg_addr)); */ tmp->pattr = chg_addr; tmp->next = NULL; *p = tmp; p = &(tmp->next); /* no buffer assigned yet */ resp.enc_buf.data = NULL; resp.enc_buf.size = 0; stun_send_message(s, &to_addr, &resp, NULL); return 0; } /** * Returns socket attached to the discovery object */ su_socket_t stun_discovery_get_socket(stun_discovery_t *sd) { assert(sd); return sd->sd_socket; } /* ------------------------------------------------------------------- * DEPRECATED functions * ------------------------------------------------------------------- */ /** * Deprecated. Unregisters socket from STUN handle event loop */ int stun_handle_release(stun_handle_t *sh, su_socket_t s) { stun_discovery_t *sd; int removed = 0; assert (sh); enter; if (s < 0) return errno = EFAULT, -1; /* There can be several discoveries using the same socket. It is still enough to deregister the socket in first of them */ /* count how many discoveries are using 's' */ for (sd = sh->sh_discoveries; sd; sd = sd->sd_next) { if (sd->sd_socket != s) continue; if (!removed) { su_root_deregister(sh->sh_root, sd->sd_index); SU_DEBUG_3(("%s: socket deregistered from STUN \n", __func__)); ++removed; } sd->sd_index = -1; /* mark index as deregistered */ } if (!removed) { /* Oops, user passed wrong socket */ SU_DEBUG_3(("%s: socket given is not associated with STUN \n", __func__)); return -1; } return 0; } /* Deprecated. Use stun_root(). */ su_root_t *stun_handle_root(stun_handle_t *self) { return stun_root(self); } /* Deprecated. Use stun_request_shared_secret() */ int stun_handle_request_shared_secret(stun_handle_t *sh) { return stun_obtain_shared_secret(sh, NULL, NULL, TAG_NULL()); } /* Deprecated. Use stun_bind() */ int stun_handle_bind(stun_handle_t *sh, tag_type_t tag, tag_value_t value, ...) { int err; ta_list ta; ta_start(ta, tag, value); err = stun_bind(sh, NULL, NULL, ta_tags(ta)); ta_end(ta); return err; } /* Deprecated. Use stun_set_uname_pwd(). */ int stun_handle_set_uname_pwd(stun_handle_t *sh, const char *uname, int len_uname, const char *pwd, int len_pwd) { return stun_set_uname_pwd(sh, uname, len_uname, pwd, len_pwd); } /* Deprecated. Use stun_process_message() */ int stun_handle_process_message(stun_handle_t *sh, su_socket_t s, su_sockaddr_t *sa, socklen_t salen, void *data, int len) { return stun_process_message(sh, s, sa, salen, data, len); }