Small improvement to the STUN support so it can be used by
authorLuigi Rizzo <rizzo@icir.org>
Fri, 13 Jul 2007 16:22:09 +0000 (16:22 +0000)
committerLuigi Rizzo <rizzo@icir.org>
Fri, 13 Jul 2007 16:22:09 +0000 (16:22 +0000)
sockets other than RTP ones.

The main change is a new API function in main/rtp.c (see there
for a description)

    int ast_stun_request(int s, struct sockaddr_in *dst,
        const char *username, struct sockaddr_in *answer)

which can be used to send an STUN request on a socket, and
optionally wait for a reply and store the STUN_MAPPED_ADDRESS
into the 'answer' argument (obviously, the version that
waits for a reply is blocking, but this is no different
from DNS resolutions).

Internally there are minor modifications to let stun_handle_packet()
be somewhat configurable on how to parse the body of responses.

At the moment i am not committing any change to the clients,
but adding STUN client support is extremely simple, e.g. chan_sip.c
could do something like this:

    + add a variable to store the stun server address;

static struct sockaddr_in stunaddr = { 0, };   /*!< stun server address */

    + add code to parse a config file of the form "stunaddr=my.stun.server.org:3478"
      (not shown for brevity);

    + right after binding the main sip socket, talk to the stun server to
      determine the externally visible address

    if (stunaddr.sin_addr.s_addr != 0)
ast_stun_request(sipsock, &stunaddr, NULL, &externip);

      so now 'externip' is set with the externally visible address.

so it is really trivial.

Similarly ast_stun_request could be called when creating the RTP
socket (possibly adding a struct sockaddr_in field in the struct
ast_rtp to store the externalip).

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@75034 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/rtp.h
main/rtp.c

index 492ad91..8d54cbe 100644 (file)
@@ -211,7 +211,22 @@ void ast_rtp_setdtmfcompensate(struct ast_rtp *rtp, int compensate);
 /*! \brief Enable STUN capability */
 void ast_rtp_setstun(struct ast_rtp *rtp, int stun_enable);
 
-/*! \brief Send STUN request (??) */
+/*! \brief Generic STUN request
+ * send a generic stun request to the server specified.
+ * \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.
+ * The interface it may change in the future.
+ */
+int ast_stun_request(int s, struct sockaddr_in *dst,
+       const char *username, struct sockaddr_in *answer);
+
+/*! \brief Send STUN request for an RTP socket
+ * Deprecated, this is just a wrapper for ast_rtp_stun_request()
+ */
 void ast_rtp_stun_request(struct ast_rtp *rtp, struct sockaddr_in *suggestion, const char *username);
 
 /*! \brief The RTP bridge.
index 0bac1ce..9f0c058 100644 (file)
@@ -449,29 +449,8 @@ size_t ast_rtp_alloc_size(void)
        return sizeof(struct ast_rtp);
 }
 
-/*! \brief send a STUN BIND request to the given destination.
- * Optionally, add a username if specified.
- */
-void ast_rtp_stun_request(struct ast_rtp *rtp, struct sockaddr_in *suggestion, const char *username)
-{
-       struct stun_header *req;
-       unsigned char reqdata[1024];
-       int reqlen, reqleft;
-       struct stun_attr *attr;
-       
-       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);
-       stun_send(rtp->s, suggestion, req);
-}
+/*! \brief callback type to be invoked on stun responses. */
+typedef int (stun_cb_f)(struct stun_attr *attr, void *arg);
 
 /*! \brief handle an incoming STUN message.
  *
@@ -479,8 +458,10 @@ void ast_rtp_stun_request(struct ast_rtp *rtp, struct sockaddr_in *suggestion, c
  * 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.
  */
-static int stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data, size_t len)
+static int 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;
@@ -518,6 +499,8 @@ static int stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *dat
                        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;
@@ -571,6 +554,107 @@ static int stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *dat
        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];
+               fd_set rfds;
+               struct timeval to = { 3, 0 };   /* timeout, make it configurable */
+               struct sockaddr_in src;
+               int 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;
+               FD_ZERO(&rfds);
+               FD_SET(s, &rfds);
+               res = ast_select(s + 1, &rfds, NULL, NULL, &to);
+               if (res <= 0)   /* timeout or error */
+                       continue;
+               bzero(&src, 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;
+               }
+               bzero(answer, sizeof(struct sockaddr_in));
+               stun_handle_packet(s, &src, reply_buf, res,
+                       stun_get_mapped, answer);
+               res = 0; /* signal regular exit */
+               break;
+       }
+       return res;
+}
+
+/*! \brief send a STUN BIND request to the given destination.
+ * Optionally, add a username if specified.
+ */
+void ast_rtp_stun_request(struct ast_rtp *rtp, struct sockaddr_in *suggestion, const char *username)
+{
+       ast_stun_request(rtp->s, suggestion, username, NULL);
+}
+
 /*! \brief List of current sessions */
 static AST_RWLIST_HEAD_STATIC(protos, ast_rtp_protocol);
 
@@ -1338,7 +1422,7 @@ struct ast_frame *ast_rtp_read(struct ast_rtp *rtp)
                 * answers to requests, and it returns STUN_ACCEPT
                 * if the request is valid.
                 */
-               if ((stun_handle_packet(rtp->s, &sin, rtp->rawdata + AST_FRIENDLY_OFFSET, res) == STUN_ACCEPT) &&
+               if ((stun_handle_packet(rtp->s, &sin, rtp->rawdata + AST_FRIENDLY_OFFSET, res, NULL, NULL) == STUN_ACCEPT) &&
                        (!rtp->them.sin_port && !rtp->them.sin_addr.s_addr)) {
                        memcpy(&rtp->them, &sin, sizeof(rtp->them));
                }