Merge "res_calendar: Specialized calendars depend on symbols of general calendar."
[asterisk/asterisk.git] / main / stun.c
index 2644307..c103ab8 100644 (file)
  * \note STUN is defined in RFC 3489.
  */
 
-#include "asterisk.h"
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision: 124370 $")
+#include "asterisk.h"
 
 #include "asterisk/_private.h"
 #include "asterisk/stun.h"
@@ -178,7 +180,7 @@ 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));
+                           stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len));
        switch (ntohs(attr->attr)) {
        case STUN_USERNAME:
                state->username = (const char *) (attr->value);
@@ -189,7 +191,7 @@ static int stun_process_attr(struct stun_state *state, struct stun_attr *attr)
        default:
                if (stundebug)
                        ast_verbose("Ignoring STUN attribute %s (%04x), length %d\n",
-                                   stun_attr2str(ntohs(attr->attr)), ntohs(attr->attr), ntohs(attr->len));
+                                   stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len));
        }
        return 0;
 }
@@ -197,12 +199,15 @@ static int stun_process_attr(struct stun_state *state, struct stun_attr *attr)
 /*! \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);
+       int str_length = strlen(s);
+       int attr_length = str_length + ((~(str_length - 1)) & 0x3);
+       int size = sizeof(**attr) + attr_length;
        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));
+               (*attr)->len = htons(attr_length);
+               memcpy((*attr)->value, s, str_length);
+               memset((*attr)->value + str_length, 0, attr_length - str_length);
+               (*attr) = (struct stun_attr *)((*attr)->value + attr_length);
                *len += size;
                *left -= size;
        }
@@ -234,6 +239,21 @@ static int stun_send(int s, struct sockaddr_in *dst, struct stun_header *resp)
                      (struct sockaddr *)dst, sizeof(*dst));
 }
 
+/*!
+ * \internal
+ * \brief Compare the STUN tranaction IDs.
+ *
+ * \param left Transaction ID.
+ * \param right Transaction ID.
+ *
+ * \retval 0 if match.
+ * \retval non-zero if not match.
+ */
+static int stun_id_cmp(stun_trans_id *left, stun_trans_id *right)
+{
+       return memcmp(left, right, sizeof(*left));
+}
+
 /*! \brief helper function to generate a random request id */
 static void stun_req_id(struct stun_header *req)
 {
@@ -242,14 +262,6 @@ static void stun_req_id(struct stun_header *req)
                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;
@@ -270,7 +282,7 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
        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);
+               ast_verbose("STUN Packet, msg %s (%04x), length: %d\n", stun_msg2str(ntohs(hdr->msgtype)), (unsigned)ntohs(hdr->msgtype), x);
        if (x > len) {
                ast_debug(1, "Scrambled STUN packet length (got %d, expecting %d)\n", x, (int)len);
        } else
@@ -291,7 +303,7 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
                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));
+                       ast_debug(1, "Failed to handle attribute %s (%04x)\n", stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr));
                        break;
                }
                /* Clear attribute id: in case previous entry was a string,
@@ -317,6 +329,7 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
                struct stun_header *resp = (struct stun_header *)respdata;
                int resplen = 0;        /* len excluding header */
                int respleft = sizeof(respdata) - sizeof(struct stun_header);
+               char combined[33];
 
                resp->id = hdr->id;
                resp->msgtype = 0;
@@ -327,17 +340,23 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
                        if (stundebug)
                                ast_verbose("STUN Bind Request, username: %s\n",
                                            st.username ? st.username : "<none>");
-                       if (st.username)
+                       if (st.username) {
                                append_attr_string(&attr, STUN_USERNAME, st.username, &resplen, &respleft);
+                               snprintf(combined, sizeof(combined), "%16s%16s", st.username + 16, st.username);
+                       } else {
+                               combined[0] = '\0';
+                       }
+
                        append_attr_address(&attr, STUN_MAPPED_ADDRESS, src, &resplen, &respleft);
                        resp->msglen = htons(resplen);
                        resp->msgtype = htons(STUN_BINDRESP);
                        stun_send(s, src, resp);
+                       ast_stun_request(s, src, combined, NULL);
                        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)));
+                               ast_verbose("Dunno what to do with STUN message %04x (%s)\n", (unsigned)ntohs(hdr->msgtype), stun_msg2str(ntohs(hdr->msgtype)));
                }
        }
        return ret;
@@ -359,78 +378,105 @@ static int stun_get_mapped(struct stun_attr *attr, void *arg)
        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];
+       struct stun_header *rsp;
+       unsigned char req_buf[1024];
+       unsigned char rsp_buf[1024];
        int reqlen, reqleft;
        struct stun_attr *attr;
-       int res = 0;
+       int res = -1;
        int retry;
 
-       req = (struct stun_header *)reqdata;
+       if (answer) {
+               /* Always clear answer in case the request fails. */
+               memset(answer, 0, sizeof(struct sockaddr_in));
+       }
+
+       /* Create STUN bind request */
+       req = (struct stun_header *) req_buf;
        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)
+       reqleft = sizeof(req_buf) - sizeof(struct stun_header);
+       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 */
+
+       for (retry = 0; retry++ < 3;) { /* XXX make retries configurable */
                /* send request, possibly wait for reply */
-               unsigned char reply_buf[1024];
-               fd_set rfds;
-               struct timeval to = { 3, 0 };   /* timeout, make it configurable */
                struct sockaddr_in src;
                socklen_t srclen;
+               struct timeval start;
 
+               /* Send STUN message. */
                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;
+                       ast_debug(1, "stun_send try %d failed: %s\n", retry, strerror(errno));
+                       break;
                }
-               if (answer == NULL)
+               if (!answer) {
+                       /* Successful send since we don't care about any response. */
+                       res = 0;
                        break;
-               FD_ZERO(&rfds);
-               FD_SET(s, &rfds);
-               res = ast_select(s + 1, &rfds, NULL, NULL, &to);
-               if (res <= 0)   /* timeout or error */
-                       continue;
+               }
+
+               start = ast_tvnow();
+try_again:
+               /* Wait for response. */
+               {
+                       struct pollfd pfds = { .fd = s, .events = POLLIN };
+                       int ms;
+
+                       ms = ast_remaining_ms(start, 3000);
+                       if (ms <= 0) {
+                               /* No response, timeout */
+                               res = 1;
+                               continue;
+                       }
+                       res = ast_poll(&pfds, 1, ms);
+                       if (res < 0) {
+                               /* Error */
+                               continue;
+                       }
+                       if (!res) {
+                               /* No response, timeout */
+                               res = 1;
+                               continue;
+                       }
+               }
+
+               /* Read STUN response. */
                memset(&src, 0, sizeof(src));
                srclen = sizeof(src);
-               /* XXX pass -1 in the size, because stun_handle_packet might
+               /* XXX pass sizeof(rsp_buf) - 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);
+               res = recvfrom(s, rsp_buf, sizeof(rsp_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;
+                       ast_debug(1, "recvfrom try %d failed: %s\n", retry, strerror(errno));
+                       break;
                }
-               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 */
+
+               /* Process the STUN response. */
+               rsp = (struct stun_header *) rsp_buf;
+               if (ast_stun_handle_packet(s, &src, rsp_buf, res, stun_get_mapped, answer)
+                       || (rsp->msgtype != htons(STUN_BINDRESP)
+                               && rsp->msgtype != htons(STUN_BINDERR))
+                       || stun_id_cmp(&req->id, &rsp->id)) {
+                       /* Bad STUN packet, not right type, or transaction ID did not match. */
+                       memset(answer, 0, sizeof(struct sockaddr_in));
+
+                       /* Was not a resonse to our request. */
+                       goto try_again;
+               }
+               /* Success.  answer contains the external address if available. */
+               res = 0;
                break;
        }
        return res;
@@ -468,8 +514,14 @@ static struct ast_cli_entry cli_stun[] = {
        AST_CLI_DEFINE(handle_cli_stun_set_debug, "Enable/Disable STUN debugging"),
 };
 
+static void stun_shutdown(void)
+{
+       ast_cli_unregister_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry));
+}
+
 /*! \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));
+       ast_register_cleanup(stun_shutdown);
 }