/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2008, Digium, Inc. * * Mark Spencer * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * * \brief STUN Support * * \author Mark Spencer * * \note STUN is defined in RFC 3489. */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/_private.h" #include "asterisk/stun.h" #include "asterisk/cli.h" #include "asterisk/utils.h" #include "asterisk/channel.h" static int stundebug; /*!< Are we debugging stun? */ /*! * \brief STUN support code * * This code provides some support for doing STUN transactions. * Eventually it should be moved elsewhere as other protocols * than RTP can benefit from it - e.g. SIP. * STUN is described in RFC3489 and it is based on the exchange * of UDP packets between a client and one or more servers to * determine the externally visible address (and port) of the client * once it has gone through the NAT boxes that connect it to the * outside. * The simplest request packet is just the header defined in * struct stun_header, and from the response we may just look at * one attribute, STUN_MAPPED_ADDRESS, that we find in the response. * By doing more transactions with different server addresses we * may determine more about the behaviour of the NAT boxes, of * course - the details are in the RFC. * * All STUN packets start with a simple header made of a type, * length (excluding the header) and a 16-byte random transaction id. * Following the header we may have zero or more attributes, each * structured as a type, length and a value (whose format depends * on the type, but often contains addresses). * Of course all fields are in network format. */ typedef struct { unsigned int id[4]; } __attribute__((packed)) stun_trans_id; struct stun_header { unsigned short msgtype; unsigned short msglen; stun_trans_id id; unsigned char ies[0]; } __attribute__((packed)); struct stun_attr { unsigned short attr; unsigned short len; unsigned char value[0]; } __attribute__((packed)); /* * The format normally used for addresses carried by STUN messages. */ struct stun_addr { unsigned char unused; unsigned char family; unsigned short port; unsigned int addr; } __attribute__((packed)); /*! \brief STUN message types * 'BIND' refers to transactions used to determine the externally * visible addresses. 'SEC' refers to transactions used to establish * a session key for subsequent requests. * 'SEC' functionality is not supported here. */ #define STUN_BINDREQ 0x0001 #define STUN_BINDRESP 0x0101 #define STUN_BINDERR 0x0111 #define STUN_SECREQ 0x0002 #define STUN_SECRESP 0x0102 #define STUN_SECERR 0x0112 /*! \brief Basic attribute types in stun messages. * Messages can also contain custom attributes (codes above 0x7fff) */ #define STUN_MAPPED_ADDRESS 0x0001 #define STUN_RESPONSE_ADDRESS 0x0002 #define STUN_CHANGE_REQUEST 0x0003 #define STUN_SOURCE_ADDRESS 0x0004 #define STUN_CHANGED_ADDRESS 0x0005 #define STUN_USERNAME 0x0006 #define STUN_PASSWORD 0x0007 #define STUN_MESSAGE_INTEGRITY 0x0008 #define STUN_ERROR_CODE 0x0009 #define STUN_UNKNOWN_ATTRIBUTES 0x000a #define STUN_REFLECTED_FROM 0x000b /*! \brief helper function to print message names */ static const char *stun_msg2str(int msg) { switch (msg) { case STUN_BINDREQ: return "Binding Request"; case STUN_BINDRESP: return "Binding Response"; case STUN_BINDERR: return "Binding Error Response"; case STUN_SECREQ: return "Shared Secret Request"; case STUN_SECRESP: return "Shared Secret Response"; case STUN_SECERR: return "Shared Secret Error Response"; } return "Non-RFC3489 Message"; } /*! \brief helper function to print attribute names */ static const char *stun_attr2str(int msg) { switch (msg) { case STUN_MAPPED_ADDRESS: return "Mapped Address"; case STUN_RESPONSE_ADDRESS: return "Response Address"; case STUN_CHANGE_REQUEST: return "Change Request"; case STUN_SOURCE_ADDRESS: return "Source Address"; case STUN_CHANGED_ADDRESS: return "Changed Address"; case STUN_USERNAME: return "Username"; case STUN_PASSWORD: return "Password"; case STUN_MESSAGE_INTEGRITY: return "Message Integrity"; case STUN_ERROR_CODE: return "Error Code"; case STUN_UNKNOWN_ATTRIBUTES: return "Unknown Attributes"; case STUN_REFLECTED_FROM: return "Reflected From"; } return "Non-RFC3489 Attribute"; } /*! \brief here we store credentials extracted from a message */ struct stun_state { const char *username; const char *password; }; static int stun_process_attr(struct stun_state *state, struct stun_attr *attr) { if (stundebug) ast_verbose("Found STUN Attribute %s (%04x), length %d\n", stun_attr2str(ntohs(attr->attr)), ntohs(attr->attr), ntohs(attr->len)); switch (ntohs(attr->attr)) { case STUN_USERNAME: state->username = (const char *) (attr->value); break; case STUN_PASSWORD: state->password = (const char *) (attr->value); break; default: if (stundebug) ast_verbose("Ignoring STUN attribute %s (%04x), length %d\n", stun_attr2str(ntohs(attr->attr)), ntohs(attr->attr), ntohs(attr->len)); } return 0; } /*! \brief append a string to an STUN message */ static void append_attr_string(struct stun_attr **attr, int attrval, const char *s, int *len, int *left) { int size = sizeof(**attr) + strlen(s); if (*left > size) { (*attr)->attr = htons(attrval); (*attr)->len = htons(strlen(s)); memcpy((*attr)->value, s, strlen(s)); (*attr) = (struct stun_attr *)((*attr)->value + strlen(s)); *len += size; *left -= size; } } /*! \brief append an address to an STUN message */ static void append_attr_address(struct stun_attr **attr, int attrval, struct sockaddr_in *sin, int *len, int *left) { int size = sizeof(**attr) + 8; struct stun_addr *addr; if (*left > size) { (*attr)->attr = htons(attrval); (*attr)->len = htons(8); addr = (struct stun_addr *)((*attr)->value); addr->unused = 0; addr->family = 0x01; addr->port = sin->sin_port; addr->addr = sin->sin_addr.s_addr; (*attr) = (struct stun_attr *)((*attr)->value + 8); *len += size; *left -= size; } } /*! \brief wrapper to send an STUN message */ static int stun_send(int s, struct sockaddr_in *dst, struct stun_header *resp) { return sendto(s, resp, ntohs(resp->msglen) + sizeof(*resp), 0, (struct sockaddr *)dst, sizeof(*dst)); } /*! \brief helper function to generate a random request id */ static void stun_req_id(struct stun_header *req) { int x; for (x = 0; x < 4; x++) req->id.id[x] = ast_random(); } /*! \brief handle an incoming STUN message. * * Do some basic sanity checks on packet size and content, * try to extract a bit of information, and possibly reply. * At the moment this only processes BIND requests, and returns * the externally visible address of the request. * If a callback is specified, invoke it with the attribute. */ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data, size_t len, stun_cb_f *stun_cb, void *arg) { struct stun_header *hdr = (struct stun_header *)data; struct stun_attr *attr; struct stun_state st; int ret = AST_STUN_IGNORE; int x; /* On entry, 'len' is the length of the udp payload. After the * initial checks it becomes the size of unprocessed options, * while 'data' is advanced accordingly. */ if (len < sizeof(struct stun_header)) { ast_debug(1, "Runt STUN packet (only %d, wanting at least %d)\n", (int) len, (int) sizeof(struct stun_header)); return -1; } len -= sizeof(struct stun_header); data += sizeof(struct stun_header); x = ntohs(hdr->msglen); /* len as advertised in the message */ if (stundebug) ast_verbose("STUN Packet, msg %s (%04x), length: %d\n", stun_msg2str(ntohs(hdr->msgtype)), ntohs(hdr->msgtype), x); if (x > len) { ast_debug(1, "Scrambled STUN packet length (got %d, expecting %d)\n", x, (int)len); } else len = x; memset(&st, 0, sizeof(st)); while (len) { if (len < sizeof(struct stun_attr)) { ast_debug(1, "Runt Attribute (got %d, expecting %d)\n", (int)len, (int) sizeof(struct stun_attr)); break; } attr = (struct stun_attr *)data; /* compute total attribute length */ x = ntohs(attr->len) + sizeof(struct stun_attr); if (x > len) { ast_debug(1, "Inconsistent Attribute (length %d exceeds remaining msg len %d)\n", x, (int)len); break; } if (stun_cb) stun_cb(attr, arg); if (stun_process_attr(&st, attr)) { ast_debug(1, "Failed to handle attribute %s (%04x)\n", stun_attr2str(ntohs(attr->attr)), ntohs(attr->attr)); break; } /* Clear attribute id: in case previous entry was a string, * this will act as the terminator for the string. */ attr->attr = 0; data += x; len -= x; } /* Null terminate any string. * XXX NOTE, we write past the size of the buffer passed by the * caller, so this is potentially dangerous. The only thing that * saves us is that usually we read the incoming message in a * much larger buffer in the struct ast_rtp */ *data = '\0'; /* Now prepare to generate a reply, which at the moment is done * only for properly formed (len == 0) STUN_BINDREQ messages. */ if (len == 0) { unsigned char respdata[1024]; struct stun_header *resp = (struct stun_header *)respdata; int resplen = 0; /* len excluding header */ int respleft = sizeof(respdata) - sizeof(struct stun_header); resp->id = hdr->id; resp->msgtype = 0; resp->msglen = 0; attr = (struct stun_attr *)resp->ies; switch (ntohs(hdr->msgtype)) { case STUN_BINDREQ: if (stundebug) ast_verbose("STUN Bind Request, username: %s\n", st.username ? st.username : ""); if (st.username) append_attr_string(&attr, STUN_USERNAME, st.username, &resplen, &respleft); append_attr_address(&attr, STUN_MAPPED_ADDRESS, src, &resplen, &respleft); resp->msglen = htons(resplen); resp->msgtype = htons(STUN_BINDRESP); stun_send(s, src, resp); ret = AST_STUN_ACCEPT; break; default: if (stundebug) ast_verbose("Dunno what to do with STUN message %04x (%s)\n", ntohs(hdr->msgtype), stun_msg2str(ntohs(hdr->msgtype))); } } return ret; } /*! \brief Extract the STUN_MAPPED_ADDRESS from the stun response. * This is used as a callback for stun_handle_response * when called from ast_stun_request. */ static int stun_get_mapped(struct stun_attr *attr, void *arg) { struct stun_addr *addr = (struct stun_addr *)(attr + 1); struct sockaddr_in *sa = (struct sockaddr_in *)arg; if (ntohs(attr->attr) != STUN_MAPPED_ADDRESS || ntohs(attr->len) != 8) return 1; /* not us. */ sa->sin_port = addr->port; sa->sin_addr.s_addr = addr->addr; return 0; } /*! \brief Generic STUN request * Send a generic stun request to the server specified, * possibly waiting for a reply and filling the 'reply' field with * the externally visible address. Note that in this case the request * will be blocking. * (Note, the interface may change slightly in the future). * * \param s the socket used to send the request * \param dst the address of the STUN server * \param username if non null, add the username in the request * \param answer if non null, the function waits for a response and * puts here the externally visible address. * \return 0 on success, other values on error. */ int ast_stun_request(int s, struct sockaddr_in *dst, const char *username, struct sockaddr_in *answer) { struct stun_header *req; unsigned char reqdata[1024]; int reqlen, reqleft; struct stun_attr *attr; int res = 0; int retry; req = (struct stun_header *)reqdata; stun_req_id(req); reqlen = 0; reqleft = sizeof(reqdata) - sizeof(struct stun_header); req->msgtype = 0; req->msglen = 0; attr = (struct stun_attr *)req->ies; if (username) append_attr_string(&attr, STUN_USERNAME, username, &reqlen, &reqleft); req->msglen = htons(reqlen); req->msgtype = htons(STUN_BINDREQ); for (retry = 0; retry < 3; retry++) { /* XXX make retries configurable */ /* send request, possibly wait for reply */ unsigned char reply_buf[1024]; struct pollfd pfds = { .fd = s, .events = POLLIN }; struct sockaddr_in src; socklen_t srclen; res = stun_send(s, dst, req); if (res < 0) { ast_log(LOG_WARNING, "ast_stun_request send #%d failed error %d, retry\n", retry, res); continue; } if (answer == NULL) break; res = ast_poll(&pfds, 1, 3000); if (res <= 0) /* timeout or error */ continue; memset(&src, 0, sizeof(src)); srclen = sizeof(src); /* XXX pass -1 in the size, because stun_handle_packet might * write past the end of the buffer. */ res = recvfrom(s, reply_buf, sizeof(reply_buf) - 1, 0, (struct sockaddr *)&src, &srclen); if (res < 0) { ast_log(LOG_WARNING, "ast_stun_request recvfrom #%d failed error %d, retry\n", retry, res); continue; } memset(answer, 0, sizeof(struct sockaddr_in)); ast_stun_handle_packet(s, &src, reply_buf, res, stun_get_mapped, answer); res = 0; /* signal regular exit */ break; } return res; } static char *handle_cli_stun_set_debug(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "stun set debug {on|off}"; e->usage = "Usage: stun set debug {on|off}\n" " Enable/Disable STUN (Simple Traversal of UDP through NATs)\n" " debugging\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != e->args) return CLI_SHOWUSAGE; if (!strncasecmp(a->argv[e->args-1], "on", 2)) stundebug = 1; else if (!strncasecmp(a->argv[e->args-1], "off", 3)) stundebug = 0; else return CLI_SHOWUSAGE; ast_cli(a->fd, "STUN Debugging %s\n", stundebug ? "Enabled" : "Disabled"); return CLI_SUCCESS; } static struct ast_cli_entry cli_stun[] = { AST_CLI_DEFINE(handle_cli_stun_set_debug, "Enable/Disable STUN debugging"), }; /*! \brief Initialize the STUN system in Asterisk */ void ast_stun_init(void) { ast_cli_register_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry)); }