Commit 94a2d5af authored by Kai Vehmanen's avatar Kai Vehmanen

Fixed bugs STUN fingerprint creation and verification. Major update to the STUN internal APIs.

darcs-hash:20070830144220-77cd4-cc8ab8b16405964ab42ed1d6560ae5a2e770890b.gz
parent d43e9ce2
......@@ -51,67 +51,8 @@
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#ifdef HAVE_POLL
# include <sys/poll.h>
#endif
#include <fcntl.h>
/** Blocking mode STUN binding discovery */
int stun_bind_run (int fd,
const struct sockaddr *restrict srv, socklen_t srvlen,
struct sockaddr *restrict addr, socklen_t *addrlen)
{
#ifdef HAVE_POLL
stun_bind_t *ctx;
uint8_t buf[STUN_MAXMSG];
size_t len = 0;
ssize_t val;
val = stun_bind_start (&ctx, fd, srv, srvlen);
if (val)
return val;
do
{
struct pollfd ufd[1];
unsigned delay = stun_bind_timeout (ctx);
memset (ufd, 0, sizeof (ufd));
ufd[0].fd = fd = stun_bind_fd (ctx),
ufd[0].events = POLLIN;
if (poll (ufd, sizeof (ufd) / sizeof (ufd[0]), delay) <= 0)
{
val = stun_bind_elapse (ctx);
continue;
}
val = recv (fd, buf + len, sizeof (buf) - len, MSG_DONTWAIT);
if (val == -1)
{
#ifdef MSG_ERRQUEUE
/* FIXME: do proper ICMP error handling? */
struct msghdr hdr;
memset (&hdr, 0, sizeof (hdr));
recvmsg (fd, &hdr, MSG_ERRQUEUE);
#endif
val = EAGAIN;
continue;
}
len += val;
val = stun_bind_process (ctx, buf, len, addr, addrlen);
}
while (val == EAGAIN);
return val;
#else
(void)fd; (void)srv; (void)srvlen; (void)addr; (void)addrlen;
return ENOSYS;
#endif
}
/** Non-blocking mode STUN binding discovery */
#include "trans.h"
......@@ -164,13 +105,6 @@ stun_bind_alloc (stun_bind_t **restrict context, int fd,
}
void stun_bind_cancel (stun_bind_t *context)
{
stun_trans_deinit (&context->trans);
free (context);
}
static int
stun_bind_launch (stun_bind_t *ctx)
{
......@@ -205,6 +139,13 @@ int stun_bind_start (stun_bind_t **restrict context, int fd,
}
void stun_bind_cancel (stun_bind_t *context)
{
stun_trans_deinit (&context->trans);
free (context);
}
/** Timer and retransmission handling */
unsigned stun_bind_timeout (const stun_bind_t *context)
......@@ -225,13 +166,6 @@ int stun_bind_elapse (stun_bind_t *context)
/** Incoming packets handling */
int stun_bind_fd (const stun_bind_t *context)
{
assert (context != NULL);
return stun_trans_fd (&context->trans);
}
int stun_bind_process (stun_bind_t *restrict ctx,
const void *restrict buf, size_t len,
struct sockaddr *restrict addr, socklen_t *addrlen)
......@@ -273,6 +207,37 @@ int stun_bind_process (stun_bind_t *restrict ctx,
}
/** Blocking mode STUN binding discovery */
int stun_bind_run (int fd,
const struct sockaddr *restrict srv, socklen_t srvlen,
struct sockaddr *restrict addr, socklen_t *addrlen)
{
stun_bind_t *ctx;
uint8_t buf[STUN_MAXMSG];
ssize_t val;
val = stun_bind_start (&ctx, fd, srv, srvlen);
if (val)
return val;
do
{
val = stun_trans_recv (&ctx->trans, buf, sizeof (buf));
if (val == -1)
{
val = errno;
continue;
}
val = stun_bind_process (ctx, buf, val, addr, addrlen);
}
while (val == EAGAIN);
return val;
}
/** ICE keep-alives (Binding discovery indication!) */
int
......@@ -286,6 +251,7 @@ stun_bind_keepalive (int fd, const struct sockaddr *restrict srv,
stun_init_indication (buf, STUN_BINDING);
val = stun_finish (buf, &len);
assert (val == 0);
(void)val;
/* NOTE: hopefully, this is only needed for non-stream sockets */
if (stun_sendto (fd, buf, len, srv, srvlen) == -1)
......@@ -347,7 +313,7 @@ stun_conncheck_start (stun_bind_t **restrict context, int fd,
ctx->trans.msg.length = sizeof (ctx->trans.msg.buf);
val = stun_finish_short (ctx->trans.msg.buf, &ctx->trans.msg.length,
username, password, NULL, 0);
username, password, NULL);
if (val)
goto error;
......@@ -357,3 +323,5 @@ error:
stun_bind_cancel (*context);
return val;
}
......@@ -120,14 +120,6 @@ unsigned stun_bind_timeout (const stun_bind_t *context);
*/
int stun_bind_elapse (stun_bind_t *context);
/**
* This is meant to integrate with I/O polling loops and event frameworks.
* @return file descriptor used by the STUN Binding discovery context.
* Always succeeds.
*/
int stun_bind_fd (const stun_bind_t *context);
/**
* Gives data to be processed within the context of a STUN Binding discovery
* or ICE connectivity check.
......@@ -168,33 +160,6 @@ int stun_bind_keepalive (int fd, const struct sockaddr *restrict srv,
socklen_t srvlen);
/**
* Tries to parse a STUN Binding request and format a Binding response
* accordingly.
*
* @param buf [OUT] output buffer to write a Binding response to. May refer
* to the same buffer space as the request message
* @param plen [IN/OUT] output buffer size on entry, response length on exit
* @param msg pointer to the first byte of a valid STUN message (check message
* validity with stun_validate() first)
* @param src socket address the message was received from
* @param srclen byte length of the socket address
* @param muxed If true, non-multiplexed (old RFC3489-style) messages will be
* considered as invalid.
*
* @return 0 if successful (@a rbuf contains a <b>non-error</b> response),
* EINVAL: malformatted request message or socket address,
* EAFNOSUPPORT: unsupported socket address family,
* EPROTO: unsupported request message type or parameter,
* ENOBUFS: insufficient response buffer space.
*
* In case of error, the value at @a plen is set to the size of an error
* response, or 0 if no error response should be sent.
*/
int
stun_bind_reply (uint8_t *buf, size_t *plen, const uint8_t *msg,
const struct sockaddr *restrict src, socklen_t srclen,
bool muxed);
# ifndef STUN_VALIDATE_DECLARATION
# define STUN_VALIDATE_DECLARATION 2
......
#! /bin/sh
cat << EOF
/* This file is automatically generated! Do not modify! */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "stun-msg.h"
#include <assert.h>
bool stun_is_unknown (uint16_t type)
{
assert (!stun_optional (type));
switch (type)
{
EOF
sed -n -e 's/\s*\(STUN_[A-Z_]*\)=0x[0-7][0-9A-F]\{3\},/\t\tcase \1:/p'
cat << EOF
return false;
default:
return true;
}
}
EOF
......@@ -41,22 +41,31 @@
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <netinet/in.h> /* htons() */
#include "stun-msg.h"
static uint32_t crc32 (const void *buf, size_t size);
static inline uint32_t crc32 (const struct iovec *iov, size_t size);
/**
* Computes the FINGERPRINT of a STUN message.
* @return fingerprint value in <b>host</b> byte order.
*/
uint32_t stun_fingerprint (const uint8_t *msg)
uint32_t stun_fingerprint (const uint8_t *msg, size_t len)
{
/* Don't hash last 8-bytes (= the FINGERPRINT attribute) */
size_t len = 12u + stun_length (msg); // 20 - 8 = 12
return crc32 (msg, len) ^ 0x5354554e;
struct iovec iov[3];
uint16_t fakelen = htons (len - 20u);
assert (len >= 28u);
iov[0].iov_base = (void *)msg;
iov[0].iov_len = 2;
iov[1].iov_base = &fakelen;
iov[1].iov_len = 2;
iov[2].iov_base = (void *)(msg + 4);
/* first 4 bytes done, last 8 bytes not summed */
iov[2].iov_len = len - 12u;
return crc32 (iov, sizeof (iov) / sizeof (iov[0])) ^ 0x5354554e;
}
/*-
......@@ -64,7 +73,7 @@ uint32_t stun_fingerprint (const uint8_t *msg)
* code or tables extracted from it, as desired without restriction.
*
* Extracted from FreeBSD CVS (src/sys/libkern/crc32.c)
* and adapted by Rémi Denis-Courmont, 17 April 2007.
* and adapted by Rémi Denis-Courmont, 2007.
*/
/*
......@@ -153,13 +162,20 @@ static const uint32_t crc32_tab[] = {
};
static
uint32_t crc32 (const void *buf, size_t size)
static inline
uint32_t crc32 (const struct iovec *iov, size_t n)
{
const uint8_t *p = buf;
size_t i;
uint32_t crc = 0xffffffff;
while (size--)
crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
for (i = 0; i < n; i++)
{
const uint8_t *p = iov[i].iov_base;
size_t size = iov[i].iov_len;
while (size--)
crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
}
return crc ^ 0xffffffff;
}
......@@ -37,30 +37,53 @@
# include <config.h>
#endif
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include "stun-msg.h"
#include <string.h>
#include <assert.h>
void stun_sha1 (const uint8_t *msg, uint8_t *sha, const void *key, size_t keylen)
void stun_sha1 (const uint8_t *msg, size_t len, uint8_t *sha,
const void *restrict key, size_t keylen)
{
size_t mlen = stun_length (msg);
assert (mlen >= 32);
HMAC_CTX ctx;
uint16_t fakelen = htons (len - 20u);
/*
* + 20 bytes for STUN header
* - 24 bytes for MESSAGE-INTEGRITY attribute
* - 8 bytes for FINGERPRINT attribute
*/
mlen -= 12;
assert (len >= 44u);
HMAC_CTX_init (&ctx);
HMAC_Init_ex (&ctx, key, keylen, EVP_sha1 (), NULL);
HMAC_Update (&ctx, msg, 2);
HMAC_Update (&ctx, (const uint8_t *)&fakelen, 2);
/* first 4 bytes done, last 24 bytes not summed */
HMAC_Update (&ctx, msg + 4, len - 28u);
HMAC_Final (&ctx, sha, NULL);
HMAC_CTX_cleanup (&ctx);
}
void stun_hash_creds (const char *realm, const char *login, const char *pw,
unsigned char md5[16])
{
EVP_MD_CTX ctx;
assert (realm && login && pw && md5);
HMAC (EVP_sha1 (), key, keylen, msg, mlen, sha, NULL);
EVP_MD_CTX_init (&ctx);
EVP_DigestInit_ex (&ctx, EVP_md5 (), NULL);
EVP_DigestUpdate (&ctx, realm, strlen (realm));
EVP_DigestUpdate (&ctx, ":", 1);
EVP_DigestUpdate (&ctx, login, strlen (login));
EVP_DigestUpdate (&ctx, ":", 1);
EVP_DigestUpdate (&ctx, pw, strlen (pw));
EVP_DigestFinal (&ctx, md5, NULL);
}
......
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2007 Nokia Corporation. All rights reserved.
* Contact: Rémi Denis-Courmont
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Rémi Denis-Courmont, Nokia
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <pthread.h>
#include <openssl/rand.h>
#include <stdint.h>
#define UNIQUE_SIZE 20u
#define NONCE_SIZE 24u
static uint8_t unique_id[UNIQUE_SIZE];
static void generate_unique_id (void)
{
RAND_pseudo_bytes (unique_id, sizeof (unique_id));
}
static void
stun_generate_nonce (uint8_t *nonce, time_t now,
const struct sockaddr_storage *restrict addr)
{
static pthread_once_t once = PTHREAD_ONCE_INIT;
HMAC_CTX ctx;
uint32_t stamp = now;
pthread_once (&once, generate_unique_id);
/*
* Nonce are generated from the current time and the client address and
* port number, keyed with a pseudo-random secret.
*/
HMAC_CTX_init (&ctx);
HMAC_Init_ex (&ctx, unique_id, sizeof (unique_id), EVP_sha1 (), NULL);
HMAC_Update (&ctx, &stamp, 4);
HMAC_Update (&ctx, &ss->family, sizeof (ss->family));
switch (addr->ss_family)
{
case AF_INET:
{
const struct sockaddr_in *ip4 = (const struct sockaddr_in *)addr;
HMAC_Update (&ctx, &ip4->sin_addr, 4);
HMAC_Update (&ctx, &ip4->sin_port, 2);
break;
}
case AF_INET6:
{
const struct sockaddr_in6*ip6 = (const struct sockaddr_in6*)addr;
HMAC_Update (&ctx, &ip6->sin6_addr, 16);
HMAC_Update (&ctx, &ip6->sin6_port, 2);
if (IN6_IS_ADDR_LINK_LOCAL (&ip6->sin6_addr))
HMAC_Update (&ctx, &ip6->sin6_scope_id
sizeof (ip6->sin6_scope_id));
break;
}
}
HMAC_Final (&ctx, nonce, NULL);
HMAC_CTX_cleanup (&ctx);
memcpy (nonce + 20, &stamp, 4);
}
static int
stun_append_nonce (uint8_t *buf, size_t buflen,
const struct sockaddr_storage *restrict addr)
{
uint8_t nonce[NONCE_SIZE];
stun_generate_nonce (nonce, time (NULL), addr);
return stun_append_bytes (buf, buflen, STUN_NONCE, nonce, sizeof (nonce));
}
static int
stun_verify_nonce (const uint8_t *buf, unsigned valid_time,
const struct sockaddr_storage *restrict addr)
{
const uint8_t *
stun_generate_nonce (nonce, time (NULL), addr);
return stun_append_bytes (buf, buflen, STUN_NONCE, nonce, sizeof (nonce));
}
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2007 Nokia Corporation. All rights reserved.
* Contact: Rémi Denis-Courmont
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Rémi Denis-Courmont, Nokia
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "bind.h"
#include "stun-msg.h"
#include <errno.h>
/** ICE connectivity checks **/
#include "stun-ice.h"
static int
stun_bind_error (uint8_t *buf, size_t *plen, const uint8_t *req,
stun_error_t code, const char *pass)
{
size_t len = *plen;
int val;
*plen = 0;
DBG ("STUN Error Reply (buffer size: %u)...\n", (unsigned)len);
val = stun_init_error (buf, len, req, code);
if (val)
return val;
val = stun_finish_short (buf, &len, NULL, pass, NULL);
if (val)
return val;
*plen = len;
DBG (" Error response (%u) of %u bytes\n", (unsigned)code,
(unsigned)*plen);
return 0;
}
int
stun_conncheck_reply (uint8_t *buf, size_t *restrict plen, const uint8_t *msg,
const struct sockaddr *restrict src, socklen_t srclen,
const char *pass, bool *restrict control, uint64_t tie)
{
size_t len = *plen;
uint64_t q;
int val, ret = 0;
#define err( code ) \
stun_bind_error (buf, &len, msg, code, pass); \
*plen = len
*plen = 0;
DBG ("STUN Reply (buffer size = %u)...\n", (unsigned)len);
if (stun_get_class (msg) != STUN_REQUEST)
{
DBG (" Unhandled non-request (class %u) message.\n",
stun_get_class (msg));
return EINVAL;
}
if (!stun_demux (msg))
{
DBG (" Incorrectly multiplexed STUN message ignored.\n");
return EINVAL;
}
if (stun_has_unknown (msg))
{
DBG (" Unknown mandatory attributes in message.\n");
val = stun_init_error_unknown (buf, len, msg);
if (!val)
val = stun_finish_short (buf, &len, NULL, pass, NULL);
if (val)
goto failure;
*plen = len;
return EPROTO;
}
/* Short term credentials checking */
val = 0;
if (!stun_present (msg, STUN_MESSAGE_INTEGRITY) // FIXME: wrong!
|| !stun_present (msg, STUN_USERNAME))
{
DBG (" Missing USERNAME or MESSAGE-INTEGRITY.\n");
val = STUN_BAD_REQUEST;
}
else
/* FIXME: verify USERNAME, return STUN_UNAUTHORIZED if wrong */
if (stun_verify_password (msg, pass))
{
DBG (" Integrity check failed.\n");
val = STUN_UNAUTHORIZED;
}
if (val)
{
stun_bind_error (buf, &len, msg, val, NULL);
*plen = len;
return EPERM;
}
if (stun_get_method (msg) != STUN_BINDING)
{
DBG (" Bad request (method %u) message.\n",
stun_get_method (msg));
err (STUN_BAD_REQUEST);
return EPROTO;
}
/* Role conflict handling */
assert (control != NULL);
if (!stun_find64 (msg, *control ? STUN_ICE_CONTROLLING
: STUN_ICE_CONTROLLED, &q))
{
DBG ("STUN Role Conflict detected:\n");
if (tie < q)
{
DBG (" switching role from \"controll%s\" to \"controll%s\"\n",
*control ? "ing" : "ed", *control ? "ed" : "ing");
*control = !*control;
ret = EACCES;
}
else
{
DBG (" staying \"controll%s\" (sending error)\n",
*control ? "ing" : "ed");
*plen = len;
err (STUN_ROLE_CONFLICT);
return 0;
}
}
#ifndef NDEBUG
else
if (stun_find64 (msg, *control ? STUN_ICE_CONTROLLED
: STUN_ICE_CONTROLLING, &q))
DBG ("STUN Role not specified by peer!\n");
#endif
stun_init_response (buf, len, msg);
val = stun_append_xor_addr (buf, len, STUN_XOR_MAPPED_ADDRESS,
src, srclen);
if (val)
{
DBG (" Mapped address problem: %s\n", strerror (val));
goto failure;
}
val = stun_finish_short (buf, &len, NULL, pass, NULL);
if (val)
goto failure;
*plen = len;
DBG (" All done (response size: %u)\n", (unsigned)len);
return ret;
failure:
assert (*plen == 0);
DBG (" Fatal error formatting Response: %s\n", strerror (val));
return val;
}
#undef err
/** FIXME: this needs to be merged with stun_conncheck_reply, REALLY! */
char *stun_conncheck_username (const uint8_t *restrict msg,
char *restrict buf, size_t buflen)
{
size_t i;
if ((buflen == 0)
|| stun_find_string (msg, STUN_USERNAME, buf, (buflen - 1) / 6))
return NULL;
for (i = 0; buf[i]; i++)
{
char c = buf[i];
/* ref ICE sect 7.1.1.4. (ID-16) */
if (((c >= '/') && (c <= '9')) || ((c >= 'A') && (c <= 'Z'))
|| ((c >= 'a') && (c <= 'z')) || (c == '+') || (c == ':'))
continue;
return NULL;
}
return buf;
}
uint32_t stun_conncheck_priority (const uint8_t *msg)
{
uint32_t value;
if (stun_find32 (msg, STUN_PRIORITY, &value))
return 0;
return value;
}
bool stun_conncheck_use_candidate (const uint8_t *msg)
{
return !stun_find_flag (msg, STUN_USE_CANDIDATE);
}
......@@ -87,10 +87,15 @@ int stun_conncheck_start (stun_bind_t **restrict context, int fd,
* @param control [IN/OUT] whether we are controlling ICE or not
* @param tie tie breaker value for ICE role determination
*
* @return same as stun_bind_reply() with one additionnal error code:
* EACCES: ICE role conflict occured, please recheck the flag at @a control
* @return 0 if successful (@a rbuf contains a <b>non-error</b> response),
* EINVAL: malformatted request message or socket address,
* EAFNOSUPPORT: unsupported socket address family,
* EPROTO: unsupported request message type or parameter,
* ENOBUFS: insufficient response buffer space.
* EACCES: ICE role conflict occurred, please recheck the flag at @a control
*
* @note @a buf and @a msg <b>must not</b> collide.
* In case of error, the value at @a plen is set to the size of an error
* response, or 0 if no error response should be sent.
*/
int
stun_conncheck_reply (uint8_t *buf, size_t *restrict plen, const uint8_t *msg,
......
This diff is collapsed.
......@@ -59,7 +59,15 @@
# define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif
static
/** Default port for STUN binding discovery */
#define IPPORT_STUN 3478
#include "stun-msg.h"
#include "stund.h"
/**
* Creates a listening socket
*/
int listen_socket (int fam, int type, int proto, uint16_t port)
{
int yes = 1;
......@@ -72,8 +80,6 @@ int listen_socket (int fam, int type, int proto, uint16_t port)
if (fd < 3)
goto error;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int));
struct sockaddr_storage addr;
memset (&addr, 0, sizeof (addr));
addr.ss_family = fam;
......@@ -89,8 +95,7 @@ int listen_socket (int fam, int type, int proto, uint16_t port)
case AF_INET6:
#ifdef IPV6_V6ONLY
if (setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &yes, sizeof (yes)))
goto error;
setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &yes, sizeof (yes));
#endif
((struct sockaddr_in6 *)&addr)->sin6_port = port;
break;
......@@ -139,59 +144,107 @@ error:
}
static int err_dequeue (int fd)
/** Dequeue error from a socket if applicable */
static int recv_err (int fd)
{
#ifdef MSG_ERRQUEUE
struct msghdr hdr;
memset (&hdr, 0, sizeof (hdr));
return recvmsg (fd, &hdr, MSG_ERRQUEUE) == 0;
return recvmsg (fd, &hdr, MSG_ERRQUEUE) >= 0;
#endif
}
#include "bind.h"
/** Receives a message or dequeues an error from a socket */
ssize_t recv_safe (int fd, struct msghdr *msg)
{
ssize_t len = recvmsg (fd, msg, 0);
if (len == -1)
recv_err (fd);
else
if (msg->msg_flags & MSG_TRUNC)
{
errno = EMSGSIZE;
return -1;
}
return len;
}
/** Sends a message through a socket */
ssize_t send_safe (int fd, const struct msghdr *msg)
{
ssize_t len;
do
len = sendmsg (fd, msg, 0);
while ((len == -1) && (recv_err (fd) == 0));
return len;
}
static int dgram_process (int sock)
{
struct sockaddr_storage addr;
uint8_t buf[1500];
uint8_t buf[STUN_MAXMSG];
char ctlbuf[CMSG_SPACE (sizeof (struct in6_pktinfo))];
struct iovec iov = { buf, sizeof (buf) };
struct msghdr mh;
memset (&mh, 0, sizeof (mh));
mh.msg_name = (struct sockaddr *)&addr;
mh.msg_namelen = sizeof (addr);
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
mh.msg_control = ctlbuf;
mh.msg_controllen = sizeof (ctlbuf);
ssize_t len = recvmsg (sock, &mh, 0);
if (len < 0)
struct msghdr mh =
{
err_dequeue (sock);
.msg_name = (struct sockaddr *)&addr,
.msg_namelen = sizeof (addr),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = ctlbuf,
.msg_controllen = sizeof (ctlbuf)
};
size_t len = recv_safe (sock, &mh);
if (len == (size_t)-1)
return -1;
}
if (mh.msg_flags & MSG_TRUNC)
/* Mal-formatted packets */
if ((stun_validate (buf, len) <= 0)
|| (stun_get_class (buf) != STUN_REQUEST))
return -1;
if (stun_validate (buf, len) <= 0)
return -1;
/* Unknown attributes */
if (stun_has_unknown (buf))
{
stun_init_error_unknown (buf, sizeof (buf), buf);
goto finish;
}
len = stun_bind_reply (buf, &iov.iov_len, buf,
mh.msg_name, mh.msg_namelen, false);
if (iov.iov_len == 0)
return -1;
switch (stun_get_method (buf))
{
case STUN_BINDING:
stun_init_response (buf, sizeof (buf), buf);
if (stun_has_cookie (buf))
stun_append_xor_addr (buf, sizeof (buf),
STUN_XOR_MAPPED_ADDRESS,
mh.msg_name, mh.msg_namelen);
else
stun_append_addr (buf, sizeof (buf), STUN_MAPPED_ADDRESS,
mh.msg_name, mh.msg_namelen);
break;
do
len = sendmsg (sock, &mh, 0);
while ((len == -1) && err_dequeue (sock));
case STUN_ALLOCATE:
case STUN_CONNECT:
case STUN_SET_ACTIVE_DST:
goto send;
if (len < (ssize_t)iov.iov_len)
return -1;
return 0;
default:
stun_init_error (buf, sizeof (buf), buf, STUN_BAD_REQUEST);
}
finish:
stun_finish (buf, &iov.iov_len);
send:
len = send_safe (sock, &mh);
return (len < iov.iov_len) ? -1 : 0;
}
......
/*
* This file is part of the Nice GLib ICE library.
*
* (C) 2007 Nokia Corporation. All rights reserved.
* Contact: Rémi Denis-Courmont
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Nice GLib ICE library.
*
* The Initial Developers of the Original Code are Collabora Ltd and Nokia
* Corporation. All Rights Reserved.
*
* Contributors:
* Rémi Denis-Courmont, Nokia
*
* Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
* case the provisions of LGPL are applicable instead of those above. If you
* wish to allow use of your version of this file only under the terms of the
* LGPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replace
* them with the notice and other provisions required by the LGPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the LGPL.
*/
#ifndef NICE_STUN_STUND_H
# define NICE_STUN_STUND_H 1
int listen_socket (int fam, int type, int proto, uint16_t port);
ssize_t send_safe (int fd, const struct msghdr *msg);
ssize_t recv_safe (int fd, struct msghdr *msg);
#endif
......@@ -111,15 +111,7 @@ ssize_t stun_validate (const uint8_t *msg, size_t len)
}
/**
* Looks for an attribute in a *valid* STUN message.
* @param msg message buffer
* @param type STUN attribute type (host byte order)
* @param palen [OUT] pointer to store the byte length of the attribute
* @return a pointer to the start of the attribute payload if found,
* otherwise NULL.
*/
static const void *
const void *
stun_find (const uint8_t *restrict msg, stun_attr_type_t type,
uint16_t *restrict palen)
{
......@@ -148,6 +140,19 @@ stun_find (const uint8_t *restrict msg, stun_attr_type_t type,
return msg;
}
/* Look for and ignore misordered attributes */
switch (atype)
{
case STUN_MESSAGE_INTEGRITY:
/* Only fingerprint may come after M-I */
if (type == STUN_FINGERPRINT)
break;
case STUN_FINGERPRINT:
/* Nothing may come after FPR */
return NULL;
}
alen = stun_align (alen);
length -= alen;
msg += alen;
......@@ -157,13 +162,6 @@ stun_find (const uint8_t *restrict msg, stun_attr_type_t type,
}
bool stun_present (const uint8_t *msg, stun_attr_type_t type)
{
uint16_t dummy;
return stun_find (msg, type, &dummy) != NULL;
}
int stun_find_flag (const uint8_t *msg, stun_attr_type_t type)
{
const void *ptr;
......@@ -220,21 +218,90 @@ int stun_find64 (const uint8_t *msg, stun_attr_type_t type, uint64_t *pval)
}
ssize_t stun_find_string (const uint8_t *restrict msg, stun_attr_type_t type,
char *buf, size_t buflen)
int stun_find_string (const uint8_t *restrict msg, stun_attr_type_t type,
char *buf, size_t maxcp)
{
const char *ptr;
const unsigned char *ptr;
uint16_t len;
ptr = stun_find (msg, type, &len);
if (ptr == NULL)
return -1;
return ENOENT;
/* UTF-8 validation */
while (len > 0)
{
unsigned i;
unsigned char c = *ptr++;
if (maxcp == 0)
return ENOBUFS;
maxcp--;
if (c == 0)
return EINVAL; /* unexpected nul byte */
else
if (c < 0x80)
i = 0; /* 0x01-0x7F: ASCII code point */
else
if (c < 0xC2)
/* 0x80-0xBF: continuation byte */
/* 0xC0-0xC1: overlongs */
return EINVAL;
else
if (c < 0xE0)
i = 1; /* 0xC2-0xDF: two bytes code point */
else
if (c < 0xF0)
{
if (*ptr < 0xA0)
return EINVAL; /* overlong */
i = 2; /* 0xE0-0xEF: three bytes code point */
}
else
if (c < 0xF8)
{
if (*ptr < 0x90)
return EINVAL; /* overlong */
i = 3; /* 0xF0-0xF7: four bytes code point */
}
else
if (c < 0xFC)
{
if (*ptr < 0x88)
return EINVAL; /* overlong */
i = 4; /* 0xF8-0xFB: five bytes code point */
}
else
if (c < 0xFE)
{
if (*ptr < 0x84)
return EINVAL; /* overlong */
i = 5;
}
else
return EINVAL; /* 0xFE-0xFF: Byte-Order-Mark */
*buf++ = c;
len--;
if (i > len)
return EINVAL; /* too short */
while (i > 0)
{
c = *ptr++;
len--;
if ((c & 0xC0) != 0x80)
return EINVAL;
memcpy (buf, ptr, (len < buflen) ? len : buflen);
if (len < buflen)
buf[len] = '\0';
*buf++ = c;
}
}
return len;
*buf = '\0';
return 0;
}
......@@ -386,49 +453,43 @@ int stun_strcmp (const uint8_t *restrict msg, stun_attr_type_t type,
}
#endif
static inline bool check_cookie (const uint8_t *msg)
bool stun_has_cookie (const uint8_t *msg)
{
uint32_t cookie = htonl (STUN_COOKIE);
return memcmp (msg + 4, &cookie, 4) == 0;
}
static const uint8_t *stun_end (const uint8_t *msg)
{
return msg + 20 + stun_length (msg);
return memcmp (msg + 4, &cookie, sizeof (cookie)) == 0;
}
bool stun_demux (const uint8_t *msg)
{
const void *fpr;
const uint8_t *fpr;
uint32_t crc32;
uint16_t fprlen;
assert (stun_valid (msg));
/* Checks cookie */
if (!check_cookie (msg))
if (!stun_has_cookie (msg))
{
DBG ("STUN demux error: no cookie!\n");
return 0;
}
/* Looks for FINGERPRINT */
fpr = stun_end (msg) - 4;
if ((fpr != stun_find (msg, STUN_FINGERPRINT, &fprlen)) || (fprlen != 4))
fpr = stun_find (msg, STUN_FINGERPRINT, &fprlen);
if ((fpr == NULL) || (fprlen != 4))
{
DBG ("STUN demux error: no FINGERPRINT attribute!\n");
return 0;
}
/* Checks FINGERPRINT */
crc32 = htonl (stun_fingerprint (msg));
crc32 = htonl (stun_fingerprint (msg, fpr + 4 - msg));
if (memcmp (fpr, &crc32, 4))
{
DBG ("STUN demux error: bad fingerprint (expected: 0x%08x)!\n",
stun_fingerprint (msg));
DBG ("STUN demux error: bad fingerprint: 0x%08x, expected: 0x%08x!\n",
(fpr[0] << 24) | (fpr[1] << 16) | (fpr[2] << 8) | fpr[3],
stun_fingerprint (msg, fpr + 4 - msg));
return 0;
}
......@@ -455,35 +516,26 @@ stun_verify_key (const uint8_t *msg, const void *key, size_t keylen)
assert (msg != NULL);
assert ((keylen == 0) || (key != NULL));
hash = stun_end (msg) - 20;
if (stun_demux (msg))
hash -= 8; // room for FINGERPRINT at the end
if ((stun_find (msg, STUN_MESSAGE_INTEGRITY, &hlen) != hash)
|| (hlen != 20))
hash = stun_find (msg, STUN_MESSAGE_INTEGRITY, &hlen);
if ((hash == NULL) || (hlen != 20))
{
DBG ("STUN auth error: no MESSAGE-INTEGRITY attribute!\n");
return ENOENT;
}
stun_sha1 (msg, sha, key, keylen);
stun_sha1 (msg, hash + 20 - msg, sha, key, keylen);
DBG (" Message HMAC-SHA1 fingerprint:"
"\n key : ");
DBG_bytes (key, keylen);
DBG ("\n expected: ");
DBG_bytes (sha, sizeof (sha));
DBG ("\n received: ");
DBG_bytes (hash, sizeof (sha));
DBG ("\n");
if (memcmp (sha, hash, sizeof (sha)))
{
#ifndef NDEBUG
unsigned i;
DBG ("STUN auth error: SHA1 fingerprint mismatch!"
"\n key : 0x");
for (i = 0; i < keylen; i++)
DBG ("%02x", ((uint8_t *)key)[i]);
DBG ("\n expected: 0x");
for (i = 0; i < 20; i++)
DBG ("%02x", sha[i]);
DBG ("\n received: 0x");
for (i = 0; i < 20; i++)
DBG ("%02x", hash[i]);
DBG ("\n");
#endif
DBG ("STUN auth error: SHA1 fingerprint mismatch!\n");
return EPERM;
}
......@@ -532,7 +584,7 @@ int stun_find_errno (const uint8_t *restrict msg, int *restrict code)
* @param method STUN method number (host byte order)
* @param id STUN transaction id
* @param key HMAC key, or NULL if there is no authentication
* @param keylen HMAC key byte length, 0 is no authentication
* @param keylen HMAC key byte length, must be 0 if @a key is NULL
* @param error [OUT] set to -1 if the response is not an error,
* to the error code ([0..799]) if it is an error response.
*
......@@ -548,7 +600,7 @@ stun_match_answer (const uint8_t *msg, stun_method_t method,
assert (error != NULL);
if ((stun_get_method (msg) != method) /* wrong request type */
|| !check_cookie (msg) /* response to old-style request */
|| !stun_has_cookie (msg) /* response to old-style request */
|| memcmp (msg + 8, id, 12)) /* wrong transaction ID */
return false;
......@@ -563,13 +615,25 @@ stun_match_answer (const uint8_t *msg, stun_method_t method,
break;
case STUN_ERROR:
if (stun_find_errno (msg, error))
if (stun_find_errno (msg, error) != 0)
return false; // missing ERROR-CODE: ignore message
break;
}
if ((key != NULL) && stun_verify_key (msg, key, keylen))
return false;
/* If a shared secret exists, verify the message hash.
* If there is no shared secret, verify there is no hash at all. */
if (key != NULL)
{
/* FIXME: 401 errors do not have MESSAGE-INTEGRITY, so that we
* currently ignore them. */
if (stun_verify_key (msg, key, keylen) != 0)
return false;
}
else
{
if (stun_present (msg, STUN_MESSAGE_INTEGRITY))
return false;
}
return true;
}
......@@ -589,55 +653,6 @@ bool stun_match_messages (const uint8_t *restrict resp,
}
bool stun_is_unknown (uint16_t type)
{
switch (type)
{
/* Mandatory */
case STUN_MAPPED_ADDRESS:
case STUN_OLD_RESPONSE_ADDRESS:
case STUN_OLD_CHANGE_REQUEST:
case STUN_OLD_SOURCE_ADDRESS:
case STUN_OLD_CHANGED_ADDRESS:
case STUN_USERNAME:
case STUN_OLD_PASSWORD:
case STUN_MESSAGE_INTEGRITY:
case STUN_ERROR_CODE:
case STUN_UNKNOWN_ATTRIBUTES:
case STUN_OLD_REFLECTED_FROM:
case STUN_LIFETIME:
case STUN_BANDWIDTH:
case STUN_REMOTE_ADDRESS:
case STUN_DATA:
case STUN_REALM:
case STUN_NONCE:
case STUN_RELAY_ADDRESS:
case STUN_REQUESTED_ADDRESS_TYPE:
case STUN_REQUESTED_PORT_PROPS:
case STUN_REQUESTED_TRANSPORT:
case STUN_XOR_MAPPED_ADDRESS:
case STUN_TIMER_VAL:
case STUN_REQUESTED_IP:
case STUN_CONNECT_STAT:
case STUN_PRIORITY:
case STUN_USE_CANDIDATE:
/* Optional */
case STUN_SERVER:
case STUN_ALTERNATE_SERVER:
case STUN_REFRESH_INTERVAL:
case STUN_FINGERPRINT:
case STUN_ICE_CONTROLLED:
case STUN_ICE_CONTROLLING:
return false;
}
return true;
}
unsigned
stun_find_unknown (const uint8_t *restrict msg, uint16_t *restrict list,
unsigned max)
......@@ -657,8 +672,7 @@ stun_find_unknown (const uint8_t *restrict msg, uint16_t *restrict list,
assert (len >= (4 + alen));
len -= 4 + alen;
if (!stun_optional (atype)
&& stun_is_unknown (atype))
if (!stun_optional (atype) && stun_is_unknown (atype))
{
DBG ("STUN unknown: attribute 0x%04x(%u bytes)\n",
(unsigned)atype, (unsigned)alen);
......
......@@ -78,18 +78,21 @@ void stun_set_type (uint8_t *h, stun_class_t c, stun_method_t m)
* Initializes a STUN message buffer, with no attributes.
* @param c STUN message class (host byte order)
* @param m STUN message method (host byte order)
* @param id 12-bytes transaction ID
* @param id 16-bytes transaction ID
*/
static void stun_init (uint8_t *msg, stun_class_t c, stun_method_t m,
const stun_transid_t id)
{
static const uint8_t init[8] = { 0, 0, 0, 0, 0x21, 0x12, 0xA4, 0x42 };
memcpy (msg, init, sizeof (init));
memset (msg, 0, 4);
stun_set_type (msg, c, m);
msg += 8;
if (msg != id)
{
uint32_t cookie = htonl (STUN_COOKIE);
memcpy (msg - 4, &cookie, sizeof (cookie));
memcpy (msg, id, 12);
}
}
......@@ -122,7 +125,7 @@ void stun_init_indication (uint8_t *req, stun_method_t m)
* enough room in the STUN message buffer. Return value is always on a
* 32-bits boundary.
*/
static void *
void *
stun_append (uint8_t *msg, size_t msize, stun_attr_type_t type, size_t length)
{
uint8_t *a;
......@@ -141,7 +144,9 @@ stun_append (uint8_t *msg, size_t msize, stun_attr_type_t type, size_t length)
a = msg + 20u + mlen;
a = stun_setw (a, type);
a = stun_setw (a, length);
/* NOTE: If cookie is not present, we need to force the attribute length
* to a multiple of 4 for compatibility with old RFC3489 */
a = stun_setw (a, stun_has_cookie (msg) ? length : stun_align (length));
mlen += 4 + length;
/* Add padding if needed */
......@@ -162,7 +167,7 @@ stun_append (uint8_t *msg, size_t msize, stun_attr_type_t type, size_t length)
* @param len attribute payload length
* @return 0 on success, ENOBUFS on error.
*/
static int
int
stun_append_bytes (uint8_t *restrict msg, size_t msize, stun_attr_type_t type,
const void *data, size_t len)
{
......@@ -224,6 +229,8 @@ void stun_init_response (uint8_t *ans, size_t msize, const uint8_t *req)
assert (msize >= 20u);
stun_init (ans, STUN_RESPONSE, stun_get_method (req), stun_id (req));
/* For RFC3489 compatibility, we cannot assume the cookie */
memcpy (ans + 4, req + 4, 4);
(void)stun_append_server (ans, msize);
}
......@@ -246,9 +253,7 @@ static const char *stun_strerror (stun_error_t code)
{ STUN_UNKNOWN_ATTRIBUTE, "Unknown attribute" },
/*
{ STUN_STALE_CREDENTIALS, "Authentication expired" },
*/
{ STUN_INTEGRITY_CHECK_FAILURE, "Incorrect username/password" },
/*
{ STUN_MISSING_USERNAME, "Username required" },
{ STUN_USE_TLS, "Secure connection required" },
{ STUN_MISSING_REALM, "Authentication domain required" },
......@@ -267,12 +272,11 @@ static const char *stun_strerror (stun_error_t code)
{ STUN_ROLE_CONFLICT, "Role conflict" },
{ STUN_SERVER_ERROR, "Temporary server error" },
{ STUN_SERVER_CAPACITY, "Temporary server congestion" },
{ 0, "" }
};
const char *str = "Unknown error";
unsigned i;
size_t i;
for (i = 0; tab[i].phrase[0]; i++)
for (i = 0; i < (sizeof (tab) / sizeof (tab[0])); i++)
{
if (tab[i].code == code)
{
......@@ -315,12 +319,15 @@ stun_append_error (uint8_t *restrict msg, size_t msize, stun_error_t code)
int stun_init_error (uint8_t *ans, size_t msize, const uint8_t *req,
stun_error_t err)
stun_error_t err)
{
assert (stun_valid (req));
assert (msize >= 20u);
assert (stun_get_class (req) == STUN_REQUEST);
stun_init (ans, STUN_ERROR, stun_get_method (req), stun_id (req));
/* For RFC3489 compatibility, we cannot assume the cookie */
memcpy (ans + 4, req + 4, 4);
(void)stun_append_server (ans, msize);
return stun_append_error (ans, msize, err);
}
......@@ -330,14 +337,11 @@ int stun_init_error_unknown (uint8_t *ans, size_t msize, const uint8_t *req)
{
unsigned counter, i;
#ifdef HAVE_C_VARARRAYS
uint16_t ids[stun_length (req) / 4];
uint16_t ids[1 + (stun_length (req) / 4)];
#else
uint16_t ids[256];
#endif
assert (stun_valid (req));
assert (stun_get_class (req) == STUN_REQUEST);
counter = stun_find_unknown (req, ids, sizeof (ids) / sizeof (ids[0]));
assert (counter > 0);
......@@ -346,6 +350,12 @@ int stun_init_error_unknown (uint8_t *ans, size_t msize, const uint8_t *req)
for (i = 0; i < counter; i++)
ids[i] = htons (ids[i]);
/* NOTE: Old RFC3489 compatibility:
* When counter is odd, duplicate one value for 32-bits padding. */
if (!stun_has_cookie (req) && (counter & 1))
ids[counter++] = ids[0];
return stun_append_bytes (ans, msize, STUN_UNKNOWN_ATTRIBUTES, ids,
counter * 2);
}
......@@ -411,13 +421,11 @@ int stun_append_xor_addr (uint8_t *restrict msg, size_t msize,
socklen_t addrlen)
{
int val;
union
{
struct sockaddr addr;
char fill[addrlen];
} xor;
/* Must be big enough to hold any supported address: */
struct sockaddr_storage xor;
assert (sizeof (xor) >= addrlen);
if (addrlen > sizeof (xor))
addrlen = sizeof (xor);
memcpy (&xor, addr, addrlen);
val = stun_xor_address (msg, (struct sockaddr *)&xor, addrlen);
......@@ -429,14 +437,13 @@ int stun_append_xor_addr (uint8_t *restrict msg, size_t msize,
}
static size_t
size_t
stun_finish_long (uint8_t *msg, size_t *restrict plen,
const char *realm, const char *username,
const void *key, size_t keylen,
const void *nonce, size_t noncelen)
const char *realm, const char *username, const char *nonce,
const void *restrict key, size_t keylen)
{
size_t len = *plen;
uint8_t *sha = NULL, *crc;
uint8_t *ptr;
int val = ENOBUFS;
uint32_t fpr;
......@@ -456,58 +463,55 @@ stun_finish_long (uint8_t *msg, size_t *restrict plen,
if (nonce != NULL)
{
val = stun_append_bytes (msg, len, STUN_NONCE, nonce, noncelen);
val = stun_append_string (msg, len, STUN_NONCE, nonce);
if (val)
return val;
}
if (key != NULL)
{
sha = stun_append (msg, len, STUN_MESSAGE_INTEGRITY, 20);
if (sha == NULL)
ptr = stun_append (msg, len, STUN_MESSAGE_INTEGRITY, 20);
if (ptr == NULL)
return ENOBUFS;
}
crc = stun_append (msg, len, STUN_FINGERPRINT, 4);
if (crc == NULL)
return ENOBUFS;
stun_sha1 (msg, ptr + 20 - msg, ptr, key, keylen);
if (sha != NULL)
{
stun_sha1 (msg, sha, key, keylen);
#ifndef NDEBUG
DBG (" Message HMAC-SHA1 fingerprint:"
"\n key : 0x");
for (unsigned i = 0; i < keylen; i++)
DBG ("%02x", ((uint8_t *)key)[i]);
DBG ("\n sent : 0x");
for (unsigned i = 0; i < 20; i++)
DBG ("%02x", sha[i]);
"\n key : ");
DBG_bytes (key, keylen);
DBG ("\n sent : ");
DBG_bytes (ptr, 20);
DBG ("\n");
#endif
}
fpr = htonl (stun_fingerprint (msg));
memcpy (crc, &fpr, sizeof (fpr));
/*
* NOTE: we always add a FINGERPRINT, even when it's not needed.
* This is OK, as it is an optional attribute. It also makes my
* software engineer's life easier.
*/
ptr = stun_append (msg, len, STUN_FINGERPRINT, 4);
if (ptr == NULL)
return ENOBUFS;
*plen = ptr + 4 -msg;
fpr = htonl (stun_fingerprint (msg, *plen));
memcpy (ptr, &fpr, sizeof (fpr));
*plen = 20u + stun_length (msg);
return 0;
}
size_t stun_finish_short (uint8_t *msg, size_t *restrict plen,
const char *username, const char *password,
const void *nonce, size_t noncelen)
const char *username, const char *restrict password,
const char *nonce)
{
size_t passlen = (password != NULL) ? strlen (password) : 0;
return stun_finish_long (msg, plen, NULL, username, password, passlen,
nonce, noncelen);
return stun_finish_long (msg, plen, NULL, username, nonce,
password, password ? strlen (password) : 0);
}
size_t stun_finish (uint8_t *msg, size_t *restrict plen)
{
return stun_finish_short (msg, plen, NULL, NULL, NULL, 0);
return stun_finish_short (msg, plen, NULL, NULL, NULL);
}
......@@ -10,8 +10,11 @@ AM_CPPFLAGS = -I$(top_srcdir)
AM_CFLAGS = -std=gnu99
LDADD = ../libstun.la
check_PROGRAMS = test-crc32 test-parse test-format \
test-bind test-bindserv test-conncheck
check_PROGRAMS = \
test-parse \
test-format \
test-bind \
test-conncheck
dist_check_SCRIPTS = check-bind.sh
......
......@@ -172,18 +172,20 @@ static void bad_responses (void)
uint8_t buf[1000];
/* Allocate a local UDP port */
int servfd = listen_dgram ();
int servfd = listen_dgram (), fd;
assert (servfd != -1);
val = getsockname (servfd, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
val = stun_bind_start (&ctx, -1, (struct sockaddr *)&addr, addrlen);
fd = socket (addr.ss_family, SOCK_DGRAM, 0);
assert (fd != -1);
val = stun_bind_start (&ctx, fd, (struct sockaddr *)&addr, addrlen);
assert (val == 0);
/* Send to/receive from our client instance only */
val = getsockname (stun_bind_fd (ctx),
(struct sockaddr *)&addr, &addrlen);
val = getsockname (fd, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
val = connect (servfd, (struct sockaddr *)&addr, addrlen);
......@@ -219,8 +221,8 @@ static void bad_responses (void)
(struct sockaddr *)&addr, &addrlen);
assert (val == EAGAIN);
stun_bind_cancel (ctx);
close (fd);
close (servfd);
}
......
......@@ -72,19 +72,6 @@ int main (void)
ip4.sin_port = htons (12345);
ip4.sin_addr.s_addr = htonl (0x7f000001);
/* Good message test */
stun_init_request (buf, STUN_BINDING);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
len = sizeof (buf);
val = stun_bind_reply (buf, &len, buf,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == 0);
assert (len > 0);
assert (stun_present (buf, STUN_XOR_MAPPED_ADDRESS));
/* Same with too small response buffer */
stun_init_request (buf, STUN_BINDING);
len = sizeof (buf);
......@@ -96,46 +83,6 @@ int main (void)
assert (val == ENOBUFS);
assert (len == 0);
/* Incorrect message class */
stun_init_request (buf, STUN_BINDING);
stun_init_response (buf, sizeof (buf), buf);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
len = sizeof (buf);
val = stun_bind_reply (buf, &len, buf,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == EINVAL);
assert (len == 0);
/* Incorrect message method */
stun_init_request (buf, 0x666);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
len = sizeof (buf);
val = stun_bind_reply (buf, &len, buf,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == EPROTO);
assert (len > 0);
/* Unknown attribute */
stun_init_request (buf, STUN_BINDING);
val = stun_append_string (buf, sizeof (buf), 0x666,
"The evil unknown attribute!");
assert (val == 0);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
len = sizeof (buf);
val = stun_bind_reply (buf, &len, buf,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == EPROTO);
assert (len > 0);
/* Same with too small response buffer */
stun_init_request (buf, STUN_BINDING);
stun_append_string (buf, sizeof (buf), 0x666, "Unknown attribute!");
......@@ -148,31 +95,5 @@ int main (void)
assert (val == ENOBUFS);
assert (len == 0);
/* Non-multiplexed message */
len = sizeof (buf);
val = stun_bind_reply (buf, &len, req,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == 0);
assert (len > 0);
assert (stun_present (buf, STUN_MAPPED_ADDRESS));
len = sizeof (buf);
val = stun_bind_reply (buf, &len, req,
(struct sockaddr *)&ip4, sizeof (ip4), true);
assert (val == EINVAL);
assert (len == 0);
/* Invalid socket address */
stun_init_request (buf, STUN_BINDING);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
ip4.sin_family = AF_UNSPEC;
len = sizeof (buf);
val = stun_bind_reply (buf, &len, buf,
(struct sockaddr *)&ip4, sizeof (ip4), false);
assert (val == EAFNOSUPPORT);
return 0;
}
......@@ -61,7 +61,9 @@ int main (void)
ssize_t val;
size_t len;
static const char name[] = "admin", pass[] = "secret";
char nbuf[6];
char nbuf[STUN_MAX_STR];
int code;
uint16_t alen;
bool control = false;
memset (&ip4, 0, sizeof (ip4));
......@@ -72,6 +74,46 @@ int main (void)
ip4.sin_port = htons (12345);
ip4.sin_addr.s_addr = htonl (0x7f000001);
/* Incorrect message class */
stun_init_request (req, STUN_BINDING);
stun_init_response (req, sizeof (req), req);
len = sizeof (req);
val = stun_finish (req, &len);
assert (val == 0);
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EINVAL);
assert (len == 0);
/* Incorrect message method */
stun_init_request (req, 0x666);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL);
assert (val == 0);
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EPROTO);
assert (len > 0);
/* Unknown attribute */
stun_init_request (req, STUN_BINDING);
val = stun_append_string (req, sizeof (req), 0x666,
"The evil unknown attribute!");
assert (val == 0);
len = sizeof (req);
val = stun_finish (req, &len);
assert (val == 0);
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EPROTO);
assert (len > 0);
/* Unauthenticated message */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
......@@ -83,12 +125,13 @@ int main (void)
sizeof (ip4), pass, &control, tie);
assert (val == EPERM);
assert (len > 0);
// FIXME: check error code
assert (stun_match_messages (resp, req, NULL, 0, &code)
&& (code == STUN_BAD_REQUEST));
/* No username */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
val = stun_finish_short (req, &len, NULL, pass, NULL, 0);
val = stun_finish_short (req, &len, NULL, pass, NULL);
assert (val == 0);
len = sizeof (resp);
......@@ -96,32 +139,30 @@ int main (void)
sizeof (ip4), pass, &control, tie);
assert (val == EPERM);
assert (len > 0);
assert (stun_get_class (resp) == STUN_ERROR);
assert (!stun_present (resp, STUN_MESSAGE_INTEGRITY));
assert (stun_match_messages (resp, req, NULL, 0, &code)
&& (code == STUN_BAD_REQUEST));
assert (stun_conncheck_username (req, NULL, 0) == NULL);
assert (stun_conncheck_username (req, nbuf, sizeof (nbuf)) == NULL);
assert (stun_conncheck_priority (req) == 0);
assert (stun_conncheck_use_candidate (req) == false);
/* Bad username */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
val = stun_finish_short (req, &len, "ab\xff", pass, NULL, 0);
val = stun_finish_short (req, &len, "ab\xff", pass, NULL);
assert (val == 0);
assert (stun_conncheck_username (req, nbuf, sizeof (nbuf)) == NULL);
/* FIXME: use conncheck_reply */
/* Bad fingerprint */
/* Bad integrity */
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), "bad", &control, tie);
assert (val == EPERM);
assert (len > 0);
assert (stun_get_class (resp) == STUN_ERROR);
assert (!stun_present (resp, STUN_MESSAGE_INTEGRITY));
assert (stun_match_messages (resp, req, NULL, 0, &code)
&& (code == STUN_UNAUTHORIZED));
/* Good message */
stun_init_request (req, STUN_BINDING);
......@@ -130,7 +171,7 @@ int main (void)
val = stun_append_flag (req, sizeof (req), STUN_USE_CANDIDATE);
assert (val == 0);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL, 0);
val = stun_finish_short (req, &len, name, pass, NULL);
assert (val == 0);
len = sizeof (resp);
......@@ -138,9 +179,9 @@ int main (void)
sizeof (ip4), pass, &control, tie);
assert (val == 0);
assert (len > 0);
assert (stun_get_class (resp) == STUN_RESPONSE);
assert (stun_present (resp, STUN_MESSAGE_INTEGRITY));
assert (stun_match_messages (resp, req, (uint8_t *)pass,
strlen (pass), &code)
&& (code == -1));
assert (stun_conncheck_priority (req) == 0x12345678);
assert (stun_conncheck_use_candidate (req) == true);
......@@ -152,13 +193,34 @@ int main (void)
assert (stun_conncheck_username (req, nbuf, sizeof (nbuf)) == nbuf);
assert (strcmp (nbuf, name) == 0);
/* Invalid socket address */
ip4.sin_family = AF_UNSPEC;
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EAFNOSUPPORT);
ip4.sin_family = AF_INET;
/* Bad CRC32 */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL);
assert (val == 0);
((uint8_t *)stun_find (req, STUN_FINGERPRINT, &alen))[0] ^= 1;
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EINVAL);
assert (len == 0);
/* Lost role conflict */
stun_init_request (req, STUN_BINDING);
val = stun_append64 (req, sizeof (req), STUN_ICE_CONTROLLING, tie + 1);
assert (val == 0);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL, 0);
val = stun_finish_short (req, &len, name, pass, NULL);
assert (val == 0);
len = sizeof (resp);
......@@ -168,15 +230,16 @@ int main (void)
assert (val == EACCES);
assert (len > 0);
assert (control == false);
assert (stun_get_class (resp) == STUN_RESPONSE);
assert (stun_present (resp, STUN_MESSAGE_INTEGRITY));
assert (stun_match_messages (resp, req, (uint8_t *)pass,
strlen (pass), &code)
&& (code == -1));
/* Won role conflict */
stun_init_request (req, STUN_BINDING);
val = stun_append64 (req, sizeof (req), STUN_ICE_CONTROLLED, tie - 1);
assert (val == 0);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL, 0);
val = stun_finish_short (req, &len, name, pass, NULL);
assert (val == 0);
len = sizeof (resp);
......@@ -186,8 +249,9 @@ int main (void)
assert (val == 0);
assert (len > 0);
assert (control == false);
assert (stun_get_class (resp) == STUN_ERROR);
assert (stun_present (resp, STUN_MESSAGE_INTEGRITY));
assert (stun_match_messages (resp, req, (uint8_t *)pass,
strlen (pass), &code)
&& (code == STUN_ROLE_CONFLICT));
return 0;
}
......@@ -89,7 +89,7 @@ finish_check (uint8_t *msg)
len = sizeof (mshort);
if (stun_verify_password (mshort, "toto") != ENOENT)
fatal ("Missing HMAC test failed");
if (stun_finish_short (mshort, &len, "ABCDE", "admin", "ABC", 3))
if (stun_finish_short (mshort, &len, "ABCDE", "admin", "ABC"))
fatal ("Cannot finish message with short-term creds");
dynamic_check (mshort, len);
if (stun_verify_password (mshort, "admin") != 0)
......@@ -155,6 +155,7 @@ int main (void)
fatal ("Response formatting test failed");
/* Error formatting test */
stun_init_request (msg, STUN_BINDING);
if (stun_init_error (msg, sizeof (msg), msg, 400))
fatal ("Error initialization test failed");
finish_check (msg);
......@@ -162,6 +163,7 @@ int main (void)
fatal ("Error formatting test failed");
/* Unknown error formatting test */
stun_init_request (msg, STUN_BINDING);
if (stun_init_error (msg, sizeof (msg), msg, 666))
fatal ("Unknown error initialization test failed");
finish_check (msg);
......@@ -198,13 +200,13 @@ int main (void)
if (stun_finish (msg, &len) != ENOBUFS)
fatal ("Fingerprint overflow test failed");
len = sizeof (msg);
if (stun_finish_short (msg, &len, NULL, "secret", NULL, 0) != ENOBUFS)
if (stun_finish_short (msg, &len, NULL, "secret", NULL) != ENOBUFS)
fatal ("Message integrity overflow test failed");
len = sizeof (msg);
if (stun_finish_short (msg, &len, "login", "secret", NULL, 0) != ENOBUFS)
if (stun_finish_short (msg, &len, "login", "secret", NULL) != ENOBUFS)
fatal ("Username overflow test failed");
len = sizeof (msg);
if (stun_finish_short (msg, &len, NULL, "secret", "foobar", 6) != ENOBUFS)
if (stun_finish_short (msg, &len, NULL, "secret", "foobar") != ENOBUFS)
fatal ("Nonce overflow test failed");
/* Address attributes tests */
......
......@@ -220,11 +220,11 @@ static void test_attribute (void)
/* MESSAGE-INTEGRITY attribute */
"\x00\x08\x00\x14"
"\x20\x10\xee\x8d"
"\x61\xc9\x3e\x46"
"\xbe\x41\xad\x5c"
"\xad\x38\xaa\x4c"
"\xe8\xf1\xaf\x07"
"\x42\x95\x4b\x54"
"\x73\x3c\x73\xef"
"\xa9\x75\xad\x6f"
"\xbe\xd5\x6b\x13"
"\x9d\x53\x5f\x57"
;
union
......@@ -235,7 +235,7 @@ static void test_attribute (void)
socklen_t addrlen;
uint32_t dword;
uint64_t qword;
char str[5];
char str[STUN_MAX_STR];
printf ("Attribute test message length: %u\n", sizeof (acme));
......@@ -268,11 +268,9 @@ static void test_attribute (void)
if (stun_find64 (acme, 0xff04, &qword) !=0)
fatal ("Quad-word test failed");
if (stun_find_string (acme, 0xff00, str, sizeof (str)) != -1)
if (stun_find_string (acme, 0xff00, str, STUN_MAX_CP) != ENOENT)
fatal ("Absent string test failed");
if ((stun_find_string (acme, 0xff02, str, 1) != 4) || (str[0] != 'A'))
fatal ("String buffer underflow test failed");
if ((stun_find_string (acme, 0xff02, str, sizeof (str)) != 4)
if ((stun_find_string (acme, 0xff02, str, STUN_MAX_CP) != 0)
|| strcmp (str, "ABCD"))
fatal ("String test failed");
......@@ -304,9 +302,63 @@ static void test_attribute (void)
}
static void test_vectors (void)
{
static const char username[] = "evtj:h6vY";
static const char password[] = "VOkJxbRl1RmTxUk/WvJxBt";
/* Request message */
static const unsigned char req[] =
"\x00\x01\x00\x44"
"\x21\x12\xa4\x42"
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
"\x00\x24\x00\x04"
"\x6e\x00\x01\xff"
"\x80\x29\x00\x08"
"\x93\x2f\xf9\xb1\x51\x26\x3b\x36"
"\x00\x06\x00\x09"
"\x65\x76\x74\x6a\x3a\x68\x36\x76\x59\x20\x20\x20"
"\x00\x08\x00\x14"
"\x62\x4e\xeb\xdc\x3c\xc9\x2d\xd8\x4b\x74\xbf\x85"
"\xd1\xc0\xf5\xde\x36\x87\xbd\x33"
"\x80\x28\x00\x04"
"\xad\x8a\x85\xff";
/* Response message */
static const unsigned char resp[] =
"\x01\x01\x00\x3c"
"\x21\x12\xa4\x42"
"\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae"
"\x80\x22\x00\x0b"
"\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20"
"\x00\x20\x00\x08"
"\x00\x01\xa1\x47\x5e\x12\xa4\x43"
"\x00\x08\x00\x14"
"\xab\x4e\x53\x29\x61\x00\x08\x4c\x89\xf2\x7c\x69"
"\x30\x33\x5c\xa3\x58\x14\xea\x90"
"\x80\x28\x00\x04"
"\xae\x25\x8d\xf2";
puts ("Checking test vectors...");
if (stun_demux (req) != true)
fatal ("Request test vector checksum failed");
if (stun_verify_password (req, password) != 0)
fatal ("Request test vector authentication failed");
if (stun_demux (resp) != true)
fatal ("Response test vector checksum failed");
if (stun_verify_password (resp, password) != 0)
fatal ("Response test vector authentication failed");
puts ("Done.");
}
int main (void)
{
test_message ();
test_attribute ();
test_vectors ();
return 0;
}
......@@ -127,7 +127,9 @@ int stun_timer_refresh (stun_timer_t *timer)
unsigned delay = stun_timer_remainder (timer);
if (delay == 0)
{
#if STUN_END_TIMEOUT > STUN_RELIABLE_TIMEOUT
#if STUN_RELIABLE_TIMEOUT < STUN_END_TIMEOUT
/* Reliable timeout MUST be bigger (or equal) to end timeout, so that
* retransmissions never happen with reliable transports. */
# error Inconsistent STUN timeout values!
#endif
if (timer->delay >= STUN_END_TIMEOUT)
......
......@@ -46,12 +46,16 @@
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#ifdef HAVE_POLL
# include <poll.h>
#endif
#include "stun-msg.h"
#include "trans.h"
#define TRANS_OWN_FD 0x1 /* descriptor belongs to us */
#define TRANS_RELIABLE 0x2 /* reliable transport */
#define TRANS_FGPRINT 0x4 /* whether to use FINGERPRINT */
int stun_trans_init (stun_trans_t *restrict tr, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen)
......@@ -85,17 +89,15 @@ int stun_trans_init (stun_trans_t *restrict tr, int fd,
}
int stun_trans_create (stun_trans_t *restrict tr, int type, int proto,
const struct sockaddr *restrict srv, socklen_t srvlen)
/**
* Creates and connects a socket. This is useful when a socket is to be used
* for multiple consecutive transactions (e.g. TURN).
*/
static int stun_socket (int family, int type, int proto)
{
int fd, val;
if (srvlen < sizeof (struct sockaddr))
return EINVAL;
fd = socket (srv->sa_family, type, proto);
int fd = socket (family, type, proto);
if (fd == -1)
return errno;
return -1;
#ifdef FD_CLOEXEC
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
......@@ -109,7 +111,7 @@ int stun_trans_create (stun_trans_t *restrict tr, int type, int proto,
{
/* Linux specifics for ICMP errors on non-connected sockets */
int yes = 1;
switch (srv->sa_family)
switch (family)
{
case AF_INET:
setsockopt (fd, SOL_IP, IP_RECVERR, &yes, sizeof (yes));
......@@ -121,25 +123,47 @@ int stun_trans_create (stun_trans_t *restrict tr, int type, int proto,
}
#endif
return fd;
}
int stun_trans_create (stun_trans_t *restrict tr, int type, int proto,
const struct sockaddr *restrict srv, socklen_t srvlen)
{
int val, fd;
if (srvlen < sizeof(*srv))
return EINVAL;
fd = stun_socket (srv->sa_family, type, proto);
if (fd == -1)
return errno;
if (connect (fd, srv, srvlen) && (errno != EINPROGRESS))
{
val = errno;
else
val = stun_trans_init (tr, fd, NULL, 0);
goto error;
}
val = stun_trans_init (tr, fd, NULL, 0);
if (val)
{
close (fd);
return val;
}
goto error;
tr->flags |= TRANS_OWN_FD;
return 0;
error:
close (fd);
return val;
}
void stun_trans_deinit (stun_trans_t *tr)
{
int saved = errno;
assert (tr->sock.fd != -1);
if (tr->flags & TRANS_OWN_FD)
close (tr->sock.fd);
free (tr->key.value);
......@@ -165,19 +189,23 @@ int stun_trans_start (stun_trans_t *tr)
{
int val;
assert (tr->msg.offset == 0);
val = stun_trans_send (tr);
if (val)
return val;
tr->msg.offset = 0;
if (tr->flags & TRANS_RELIABLE)
/*
* FIXME: wait for three-way handshake, somewhere
*/
stun_timer_start_reliable (&tr->timer);
else
stun_timer_start (&tr->timer);
DBG ("STUN transaction @%p started (timeout: %ums)\n", tr,
stun_trans_timeout (tr));
val = stun_trans_send (tr);
if (val)
return val;
return 0;
}
......@@ -216,6 +244,54 @@ ssize_t stun_sendto (int fd, const uint8_t *buf, size_t len,
return val;
}
ssize_t stun_recvfrom (int fd, uint8_t *buf, size_t maxlen,
struct sockaddr *restrict dst,
socklen_t *restrict dstlen)
{
static const int flags = MSG_DONTWAIT | MSG_NOSIGNAL;
ssize_t val;
if (dstlen != NULL)
val = recvfrom (fd, buf, maxlen, flags, dst, dstlen);
else
val = recv (fd, buf, maxlen, flags);
if ((val == -1) && stun_err_dequeue (fd))
errno = EAGAIN;
return val;
}
unsigned stun_trans_timeout (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->sock.fd != -1);
return stun_timer_remainder (&tr->timer);
}
int stun_trans_fd (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->sock.fd != -1);
return tr->sock.fd;
}
bool stun_trans_reading (const stun_trans_t *tr)
{
return true;
}
bool stun_trans_writing (const stun_trans_t *tr)
{
return false;
}
/**
* Try to send STUN request/indication
*/
......@@ -234,14 +310,15 @@ stun_trans_send (stun_trans_t *tr)
/* Message sent succesfully! */
tr->msg.offset += val;
assert (tr->msg.offset <= tr->msg.length);
if (tr->msg.offset == tr->msg.length)
tr->msg.offset = 0; // FIXME: temporary hack, may break TCP
return 0;
}
int stun_trans_tick (stun_trans_t *tr)
{
assert (tr->sock.fd != -1);
switch (stun_timer_refresh (&tr->timer))
{
case -1:
......@@ -249,6 +326,10 @@ int stun_trans_tick (stun_trans_t *tr)
return ETIMEDOUT; // fatal error!
case 0:
/* Retransmit can only happen with non reliable transport */
assert ((tr->flags & TRANS_RELIABLE) == 0);
tr->msg.offset = 0;
stun_trans_send (tr);
DBG ("STUN transaction @%p retransmitted (timeout: %ums).\n", tr,
stun_trans_timeout (tr));
......@@ -257,11 +338,73 @@ int stun_trans_tick (stun_trans_t *tr)
}
/**
* Waits for a response or timeout to occur.
*
* @return ETIMEDOUT if the transaction has timed out, or 0 if an incoming
* message needs to be processed.
*/
static int stun_trans_wait (stun_trans_t *tr)
{
#ifdef HAVE_POLL
int val = 0;
do
{
struct pollfd ufd;
unsigned delay = stun_trans_timeout (tr);
memset (&ufd, 0, sizeof (ufd));
ufd.fd = stun_trans_fd (tr);
if (stun_trans_writing (tr))
ufd.events |= POLLOUT;
if (stun_trans_reading (tr))
ufd.events |= POLLIN;
if (poll (&ufd, 1, delay) <= 0)
{
val = stun_trans_tick (tr);
continue;
}
val = 0;
}
while (val == EAGAIN);
return val;
#else
(void)tr;
return ENOSYS;
#endif
}
int stun_trans_recv (stun_trans_t *tr, uint8_t *buf, size_t buflen)
{
for (;;)
{
ssize_t val = stun_trans_wait (tr);
if (val)
{
errno = val /* = ETIMEDOUT */;
return -1;
}
val = stun_recv (tr->sock.fd, buf, buflen);
if (val >= 0)
return val;
}
}
int stun_trans_preprocess (stun_trans_t *restrict tr, int *pcode,
const void *restrict buf, size_t len)
{
assert (pcode != NULL);
/* FIXME: possible infinite loop */
if (stun_validate (buf, len) <= 0)
return EAGAIN;
......@@ -277,6 +420,49 @@ int stun_trans_preprocess (stun_trans_t *restrict tr, int *pcode,
if (*pcode >= 0)
{
DBG (" STUN error message received (code: %d)\n", *pcode);
/* ALTERNATE-SERVER mechanism */
if ((tr->key.value != NULL) && ((*pcode / 100) == 3))
{
struct sockaddr_storage srv;
socklen_t slen = sizeof (srv);
if (stun_find_addr (buf, STUN_ALTERNATE_SERVER,
(struct sockaddr *)&srv, &slen))
{
DBG (" Unexpectedly missing ALTERNATE-SERVER attribute\n");
return ECONNREFUSED;
}
if (tr->sock.dstlen == 0)
{
if (connect (tr->sock.fd, (struct sockaddr *)&srv, slen))
{
/* This error case includes address family mismatch */
DBG (" Error switching to alternate server: %s\n",
strerror (errno));
return ECONNREFUSED;
}
}
else
{
if ((tr->sock.dst.ss_family != srv.ss_family)
|| (slen > sizeof (tr->sock.dst)))
{
DBG (" Unsupported alternate server\n");
return ECONNREFUSED;
}
memcpy (&tr->sock.dst, &srv, tr->sock.dstlen = slen);
}
DBG (" Restarting with alternate server\n");
if (stun_trans_start (tr) == 0)
return EAGAIN;
DBG (" Restart failed!\n");
}
return ECONNREFUSED;
}
......@@ -285,19 +471,3 @@ int stun_trans_preprocess (stun_trans_t *restrict tr, int *pcode,
return 0;
}
unsigned stun_trans_timeout (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->sock.fd != -1);
return stun_timer_remainder (&tr->timer);
}
int stun_trans_fd (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->sock.fd != -1);
return tr->sock.fd;
}
......@@ -92,19 +92,20 @@ int stun_trans_init (stun_trans_t *restrict tr, int fd,
*
* @param tr pointer to an unused STUN transaction struct
* @param sotype socket type (as in socket() second parameter)
* @param proto socket protocol (as is socket() third parameter)
* @param proto socket protocol (as in socket() third parameter)
* @param srv STUN server socket address (ignored if @a srvlen is 0)
* @param srvlen STUN server socket address length (or 0 @a fd is connected)
*/
int stun_trans_create (stun_trans_t *restrict tr, int sotype, int proto,
const struct sockaddr *restrict srv, socklen_t srvlen);
/**
* Releases resources allocated by stun_trans_init() or stun_trans_create(),
* and cancel the transaction if still pending.
*/
void stun_trans_deinit (stun_trans_t *restrict tr);
int stun_trans_start (stun_trans_t *restrict tr);
int stun_trans_tick (stun_trans_t *tr);
int stun_trans_preprocess (stun_trans_t *restrict tr, int *code,
const void *restrict buf, size_t len);
/**
* This is meant to integrate with I/O pooling loops and event frameworks.
......@@ -117,14 +118,67 @@ unsigned stun_trans_timeout (const stun_trans_t *tr);
/**
* This is meant to integrate with I/O polling loops and event frameworks.
*
* @return file descriptor used by the STUN Binding discovery context.
* @return file descriptor the transaction is waiting for.
* Always succeeds.
*/
int stun_trans_fd (const stun_trans_t *tr);
/**
* This is meant to integrate with I/O polling loops and event frameworks.
*
* @return whether the transaction waits for input (from the network).
*/
bool stun_trans_reading (const stun_trans_t *tr);
/**
* This is meant to integrate with I/O polling loops and event frameworks.
*
* @return whether the transaction waits for output (to the network).
*/
bool stun_trans_writing (const stun_trans_t *tr);
/**
* Refreshes a STUN request transaction state according to current time,
* retransmits request if needed. This function should be called when
* stun_trans_timeout() reaches zero
*
* @return ETIMEDOUT if the transaction has timed out, or EAGAIN if it is
* still pending.
*/
int stun_trans_tick (stun_trans_t *tr);
int stun_trans_recv (stun_trans_t *tr, uint8_t *buf, size_t buflen);
int stun_trans_preprocess (stun_trans_t *restrict tr, int *code,
const void *restrict buf, size_t len);
/**
* Safe wrapper around sendto()
* - returns EPIPE, but never yields SIGPIPE.
* - non blocking regardless of file descriptor blocking-ness.
* - drops incoming ICMP errors. FIXME: this is actually quite bad;
* we should process ICMP errors, not ignore them.
*
* This can be used to send non-requests message, i.e. whenever the
* transaction is not used.
*/
ssize_t stun_sendto (int fd, const uint8_t *buf, size_t len,
const struct sockaddr *dst, socklen_t dstlen);
ssize_t stun_recvfrom (int fd, uint8_t *buf, size_t maxlen,
struct sockaddr *restrict src,
socklen_t *restrict srclen);
static inline ssize_t stun_send (int fd, const uint8_t *buf, size_t len)
{
return stun_sendto (fd, buf, len, NULL, 0);
}
static inline ssize_t stun_recv (int fd, uint8_t *buf, size_t maxlen)
{
return stun_recvfrom (fd, buf, maxlen, NULL, NULL);
}
# ifdef __cplusplus
}
# endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment