Commit 91d4e0a1 authored by Kai Vehmanen's avatar Kai Vehmanen

Major update to the new STUN API: support for binding discoveries, fingerprint...

Major update to the new STUN API: support for binding discoveries, fingerprint support, message-integrity support, asynchronous public API without glib dependency. Test cases for most of the functionality. Unit tests moved to a separate folder.

darcs-hash:20070521152838-77cd4-17a0eed0493917f9125713ebc5d333bd74af5a4f.gz
parent 2aee6d44
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
# Licensed under MPL 1.1/LGPL 2.1. See file COPYING. # Licensed under MPL 1.1/LGPL 2.1. See file COPYING.
SUBDIRS = . tests SUBDIRS = . tests
DIST_SUBDIRS = tests
include $(top_srcdir)/common.mk include $(top_srcdir)/common.mk
AM_CFLAGS = $(ERROR_CFLAGS) $(GLIB_CFLAGS) AM_CFLAGS = $(ERROR_CFLAGS) $(GLIB_CFLAGS) $(OPENSSL_CFLAGS)
AM_CPPFLAGS = -I$(top_srcdir)
LIBS = $(GLIB_LIBS) LIBS = $(GLIB_LIBS)
...@@ -18,14 +20,16 @@ noinst_LTLIBRARIES = libstun.la ...@@ -18,14 +20,16 @@ noinst_LTLIBRARIES = libstun.la
libstun_la_SOURCES = stun.h stun.c \ libstun_la_SOURCES = stun.h stun.c \
stun-msg.h stunsend.c stunrecv.c crc32.c hmac.c \ stun-msg.h stunsend.c stunrecv.c crc32.c hmac.c \
bind.h bind.c timer.h timer.c trans.h trans.c \
libstun_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) bind.h conncheck.h bind.c bindserv.c
libstun_la_LIBADD = $(LIBS) $(OPENSSL_LIBS) $(LIBRT) libstun_la_LIBADD = $(LIBS) $(OPENSSL_LIBS) $(LIBRT)
noinst_PROGRAMS = stun-client stund noinst_PROGRAMS = stun-client
bin_PROGRAMS = stund stunbdc
stun_client_LDADD = libstun.la stun_client_LDADD = libstun.la
stund_LDADD = libstun.la -lpthread stund_LDADD = libstun.la -lpthread
stunbdc_LDADD = libstun.la
check_PROGRAMS = \ check_PROGRAMS = \
test-attribute-pack \ test-attribute-pack \
......
...@@ -53,28 +53,8 @@ ...@@ -53,28 +53,8 @@
#include <sys/poll.h> #include <sys/poll.h>
#include <fcntl.h> #include <fcntl.h>
/** /** Blocking mode STUN binding discovery */
* Initial STUN timeout (milliseconds). The spec says it should be 100ms,
* but that's way too short for most types of wireless Internet access.
*/
#define STUN_INIT_TIMEOUT 600
#define STUN_END_TIMEOUT 4800
/**
* Performs STUN Binding discovery in blocking mode.
*
* @param fd socket to use for binding discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address byte length
* @param addr pointer to a socket address structure to hold the discovered
* binding (remember this can be either IPv4 or IPv6 regardless of the socket
* family) [OUT]
* @param addrlen pointer to the byte length of addr [IN], set to the byte
* length of the binding socket address on return.
*
* @return 0 on success, a standard error value in case of error.
* In case of error, addr and addrlen are undefined.
*/
int stun_bind_run (int fd, int stun_bind_run (int fd,
const struct sockaddr *restrict srv, socklen_t srvlen, const struct sockaddr *restrict srv, socklen_t srvlen,
struct sockaddr *restrict addr, socklen_t *addrlen) struct sockaddr *restrict addr, socklen_t *addrlen)
...@@ -102,291 +82,251 @@ int stun_bind_run (int fd, ...@@ -102,291 +82,251 @@ int stun_bind_run (int fd,
return val; return val;
} }
/** Non-blocking mode STUN binding discovery */
#include "stun-msg.h" #include "stun-msg.h"
#include "trans.h"
struct stun_bind_s struct stun_bind_s
{ {
struct sockaddr_storage srv; stun_trans_t trans;
socklen_t srvlen; size_t keylen;
uint8_t key[0];
struct timespec deadline; };
unsigned delay;
int fd;
bool ownfd;
stun_transid_t transid; /** Initialization/deinitization */
};
/**
* Initializes a STUN Binding discovery context. Does not send anything.
* This allows customization of the STUN Binding Request.
*
* @param context pointer to an opaque pointer that will be passed to
* stun_bind_resume() afterward
* @param fd socket to use for discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address length
*
* @return 0 on success, a standard error value otherwise.
*/
static int static int
stun_bind_req (stun_bind_t *ctx) stun_bind_alloc (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen)
{ {
/* FIXME: support for TCP */ stun_bind_t *ctx = malloc (sizeof (*ctx));
stun_msg_t msg; if (ctx == NULL)
stun_init (&msg, STUN_REQUEST, STUN_BINDING, ctx->transid); return ENOMEM;
memset (ctx, 0, sizeof (*ctx));
size_t len = stun_finish (&msg); *context = ctx;
if (!len)
return errno; int val = stun_trans_init (&ctx->trans, fd, srv, srvlen);
if (val)
ssize_t val = sendto (ctx->fd, &msg, len, 0, {
(struct sockaddr *)&ctx->srv, ctx->srvlen); free (ctx);
if (val == -1) return val;
return errno; }
if (val < (ssize_t)len)
return EMSGSIZE; ctx->keylen = (size_t)(-1);
stun_init_request (ctx->trans.msg, STUN_BINDING);
return 0; return 0;
} }
static void stun_gettime (struct timespec *restrict now) void stun_bind_cancel (stun_bind_t *context)
{ {
#if (_POSIX_MONOTONIC_CLOCK - 0) >= 0 stun_trans_deinit (&context->trans);
if (clock_gettime (CLOCK_MONOTONIC, now)) free (context);
#endif
{ // fallback to wall clock
struct timeval tv;
gettimeofday (&tv, NULL);
now->tv_sec = tv.tv_sec;
now->tv_nsec = tv.tv_usec * 1000;
}
} }
/** static int
* Sets deadline = now + delay stun_bind_launch (stun_bind_t *ctx)
*/
static void
stun_setto (struct timespec *restrict deadline, unsigned delay)
{ {
div_t d = div (delay, 1000); int val = stun_trans_start (&ctx->trans);
stun_gettime (deadline); if (val)
stun_bind_cancel (ctx);
// add delay to current time return val;
deadline->tv_sec += d.quot;
deadline->tv_nsec += d.rem * 1000000;
DBG ("New STUN timeout is %ums\n", delay);
} }
/** int stun_bind_start (stun_bind_t **restrict context, int fd,
* @return Remaining delay = deadline - now, or 0 if behind schedule. const struct sockaddr *restrict srv,
*/ socklen_t srvlen)
static unsigned
stun_getto (const struct timespec *restrict deadline)
{ {
unsigned delay; stun_bind_t *ctx;
struct timespec now;
stun_gettime (&now); int val = stun_bind_alloc (context, fd, srv, srvlen);
if (now.tv_sec > deadline->tv_sec) if (val)
return 0; return val;
delay = deadline->tv_sec - now.tv_sec; ctx = *context;
if ((delay == 0) && (now.tv_nsec >= deadline->tv_nsec))
return 0;
delay *= 1000; ctx->trans.msglen = sizeof (ctx->trans.msg);
delay += ((signed)(deadline->tv_nsec - now.tv_nsec)) / 1000000; val = stun_finish (ctx->trans.msg, &ctx->trans.msglen);
DBG ("Current STUN timeout is %ums\n", delay); if (val)
return delay; {
stun_bind_cancel (ctx);
return val;
}
return stun_bind_launch (ctx);
} }
/** /** Timer and retransmission handling */
* Aborts a running STUN Binding dicovery.
*/ unsigned stun_bind_timeout (const stun_bind_t *context)
void stun_bind_cancel (stun_bind_t *restrict context)
{ {
int val = errno; assert (context != NULL);
return stun_trans_timeout (&context->trans);
}
if (context->ownfd)
close (context->fd);
#ifndef NDEBUG
context->fd = -1;
#endif
free (context);
errno = val; int stun_bind_elapse (stun_bind_t *context)
{
int val = stun_trans_tick (&context->trans);
if (val != EAGAIN)
stun_bind_cancel (context);
return val;
} }
/** Incoming packets handling */
/** int stun_bind_fd (const stun_bind_t *context)
* Starts STUN Binding discovery in non-blocking mode.
*
* @param context pointer to an opaque pointer that will be passed to
* stun_bind_resume() afterward
* @param fd socket to use for discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address length
*
* @return 0 on success, a standard error value otherwise.
*/
int stun_bind_start (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv,
socklen_t srvlen)
{ {
stun_bind_t *ctx = malloc (sizeof (*ctx)); assert (context != NULL);
if (ctx == NULL) return stun_trans_fd (&context->trans);
return errno; }
memset (ctx, 0, sizeof (*ctx));
*context = ctx;
if (srvlen > sizeof (ctx->srv))
{
stun_bind_cancel (ctx);
return ENOBUFS;
}
memcpy (&ctx->srv, srv, ctx->srvlen = srvlen);
if (fd == -1) int stun_bind_process (stun_bind_t *restrict ctx,
{ const void *restrict buf, size_t len,
if (srvlen < sizeof (struct sockaddr)) struct sockaddr *restrict addr, socklen_t *addrlen)
{
bool error;
int val = stun_validate (buf, len);
if (val <= 0)
return EAGAIN;
assert (ctx != NULL);
DBG ("Received %u-bytes STUN message\n", (unsigned)val);
if (!stun_match_messages (buf, ctx->trans.msg,
(ctx->keylen != (size_t)(-1)) ? ctx->key : NULL,
(ctx->keylen != (size_t)(-1)) ? ctx->keylen : 0,
&error))
return EAGAIN;
if (error)
{ {
stun_bind_cancel (ctx); stun_bind_cancel (ctx);
return EINVAL; return ECONNREFUSED; // FIXME: better error value
} }
fd = socket (ctx->srv.ss_family, SOCK_DGRAM, 0); if (stun_has_unknown (buf))
if (fd == -1)
{ {
stun_bind_cancel (ctx); stun_bind_cancel (ctx);
return errno; return EPROTO;
}
#ifdef FD_CLOEXEC
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
#endif
#ifdef O_NONBLOCK
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
#endif
ctx->ownfd = true;
} }
ctx->fd = fd;
stun_setto (&ctx->deadline, ctx->delay = STUN_INIT_TIMEOUT);
stun_make_transid (ctx->transid);
int val = stun_bind_req (ctx); val = stun_find_xor_addr (buf, STUN_XOR_MAPPED_ADDRESS, addr, addrlen);
if (val)
{
DBG (" No XOR-MAPPED-ADDRESS: %s\n", strerror (val));
val = stun_find_addr (buf, STUN_MAPPED_ADDRESS, addr, addrlen);
if (val) if (val)
{ {
DBG (" No MAPPED-ADDRESS: %s\n", strerror (val));
stun_bind_cancel (ctx); stun_bind_cancel (ctx);
return val; return val;
} }
}
DBG (" Mapped address found!\n");
stun_bind_cancel (ctx);
return 0; return 0;
} }
/**
* Continues STUN Binding discovery in non-blocking mode.
*
* @param addr pointer to a socket address structure to hold the discovered
* binding (remember this can be either IPv4 or IPv6 regardless of the socket
* family) [OUT]
* @param addrlen pointer to the byte length of addr [IN], set to the byte
* length of the binding socket address on return.
*
* @return EAGAIN is returned if the discovery has not completed yet.
0 is returned on successful completion, another standard error value
* otherwise. If the return value is not EAGAIN, <context> is freed and must
* not be re-used.
*
* FIXME: document file descriptor closure semantics.
*/
int stun_bind_resume (stun_bind_t *restrict context, int stun_bind_resume (stun_bind_t *restrict context,
struct sockaddr *restrict addr, socklen_t *addrlen) struct sockaddr *restrict addr, socklen_t *addrlen)
{ {
stun_msg_t buf; stun_msg_t buf;
ssize_t len; ssize_t len;
bool error;
assert (context != NULL); assert (context != NULL);
assert (context->fd != -1);
// FIXME: should we only accept packet from server IP:port ? len = recv (context->trans.fd, &buf, sizeof (buf), MSG_DONTWAIT);
// FIXME: write a function to wrap this? if (len >= 0)
len = recv (context->fd, &buf, sizeof (buf), MSG_DONTWAIT); return stun_bind_process (context, &buf, len, addr, addrlen);
if (len < 0)
goto skip;
len = stun_validate (&buf, len); return stun_bind_elapse (context);
if (len <= 0) }
goto skip;
DBG ("Received %u-bytes STUN message\n", (unsigned)len);
if (!stun_match_answer (&buf, STUN_BINDING, context->transid, &error)) /** Connectivity checks */
goto skip; #include "conncheck.h"
if (error) int
{ stun_conncheck_start (stun_bind_t **restrict context, int fd,
stun_bind_cancel (context); const struct sockaddr *restrict srv, socklen_t srvlen,
return ECONNREFUSED; // FIXME: better error value const char *username, const char *password,
} bool cand_use, bool controlling, uint32_t priority,
uint64_t tie)
{
int val;
stun_bind_t *ctx;
if (stun_has_unknown (&buf)) assert (username != NULL);
{ assert (password != NULL);
stun_bind_cancel (context);
return EPROTO;
}
len = stun_find_xor_addr (&buf, STUN_XOR_MAPPED_ADDRESS, addr, addrlen); val = stun_bind_alloc (context, fd, srv, srvlen);
if (len) if (val)
{ return val;
DBG (" No XOR-MAPPED-ADDRESS: %s\n", strerror (len));
len = stun_find_addr (&buf, STUN_MAPPED_ADDRESS, addr, addrlen); val = strlen (password);
if (len) ctx = realloc (*context, sizeof (*ctx) + val + 1);
if (ctx == NULL)
{ {
DBG (" No MAPPED-ADDRESS: %s\n", strerror (len)); val = ENOMEM;
stun_bind_cancel (context); goto error;
return len;
}
} }
DBG (" Mapped address found!\n"); *context = ctx;
stun_bind_cancel (context); memcpy (ctx->key, password, val);
return 0; ctx->keylen = val;
skip: if (cand_use)
// FIXME: we call gettimeofday() twice here (minor problem)
if (!stun_getto (&context->deadline))
{
if (context->delay >= STUN_END_TIMEOUT)
{ {
DBG ("Received no valid responses. STUN transaction failed.\n"); val = stun_append_flag (ctx->trans.msg, sizeof (ctx->trans.msg),
stun_bind_cancel (context); STUN_USE_CANDIDATE);
return ETIMEDOUT; // fatal error! if (val)
goto error;
} }
context->delay *= 2; val = stun_append32 (ctx->trans.msg, sizeof (ctx->trans.msg),
DBG ("Retrying with longer timeout... %ums\n", context->delay); STUN_PRIORITY, priority);
stun_bind_req (context); if (val)
stun_setto (&context->deadline, context->delay); goto error;
}
return EAGAIN;
}
val = stun_append64 (ctx->trans.msg, sizeof (ctx->trans.msg),
controlling ? STUN_ICE_CONTROLLING
: STUN_ICE_CONTROLLED, tie);
if (val)
goto error;
/** ctx->trans.msglen = sizeof (ctx->trans.msg);
* @return recommended maximum delay (in milliseconds) to wait for a val = stun_finish_short (ctx->trans.msg, &ctx->trans.msglen,
* response. username, password, NULL, 0);
* This is meant to integrate with I/O pooling loops and event frameworks. if (val)
*/ goto error;
unsigned stun_bind_timeout (const stun_bind_t *restrict context)
{
assert (context != NULL);
assert (context->fd != -1);
return stun_getto (&context->deadline);
}
return stun_bind_launch (ctx);
/** error:
* @return file descriptor used by the STUN Binding discovery context. stun_bind_cancel (*context);
* Always succeeds. return val;
* This is meant to integrate with I/O polling loops and event frameworks.
*/
int stun_bind_fd (const stun_bind_t *restrict context)
{
assert (context != NULL);
return context->fd;
} }
...@@ -36,23 +36,178 @@ ...@@ -36,23 +36,178 @@
#ifndef STUN_BIND_H #ifndef STUN_BIND_H
# define STUN_BIND_H 1 # define STUN_BIND_H 1
/**
* @file bind.h
* @brief STUN binding discovery
*/
# ifndef IPPORT_STUN
/** Default port for STUN binding discovery */
# define IPPORT_STUN 3478
# endif
typedef struct stun_bind_s stun_bind_t; typedef struct stun_bind_s stun_bind_t;
# include <stdbool.h>
# include <stdint.h>
# ifdef __cplusplus # ifdef __cplusplus
extern "C" { extern "C" {
# endif # endif
/**
* Performs STUN Binding discovery in blocking mode.
*
* @param fd socket to use for binding discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address byte length
* @param addr [OUT] pointer to a socket address structure to hold
* discovered binding (Remember that it can be an IPv6 even if the socket
* local family is IPv4, so you should use a sockaddr_storage buffer)
* @param addrlen [IN/OUT] pointer to the byte length of addr, set to the byte
* length of the binding socket address on return.
*
* @return 0 on success, a standard error value in case of error.
* In case of error, addr and addrlen are undefined.
*/
int stun_bind_run (int fd, int stun_bind_run (int fd,
const struct sockaddr *restrict srv, socklen_t srvlen, const struct sockaddr *restrict srv, socklen_t srvlen,
struct sockaddr *restrict addr, socklen_t *addrlen); struct sockaddr *restrict addr, socklen_t *addrlen);
/**
* Starts STUN Binding discovery in non-blocking mode.
*
* @param context pointer to an opaque pointer that will be passed to
* stun_bind_resume() afterward
* @param fd socket to use for discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address length
*
* @return 0 on success, a standard error value otherwise.
*/
int stun_bind_start (stun_bind_t **restrict context, int fd, int stun_bind_start (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv, const struct sockaddr *restrict srv, socklen_t srvlen);
socklen_t srvlen);
/**
* Aborts a running STUN Binding discovery.
* @param context binding discovery (or conncheck) context pointer
* to be released.
*/
void stun_bind_cancel (stun_bind_t *context);
/**
* This is meant to integrate with I/O pooling loops and event frameworks.
*
* @param context binding discovery (or conncheck) context pointer
* @return recommended maximum delay (in milliseconds) to wait for a
* response.
*/
unsigned stun_bind_timeout (const stun_bind_t *context);
/**
* Handles retransmission timeout, and sends request retransmit if needed.
* This should be called whenever event polling indicates that
* stun_bind_timeout() has elapsed. It is however safe to call this earlier
* (in which case retransmission will not occur) or later (in which case
* late retransmission will be done).
*
* @param context binding discovery (or conncheck) context pointer
*
* @return ETIMEDOUT if the transaction has timed out, or EAGAIN if it is
* still pending.
*
* If anything except EAGAIN (but including zero) is returned, the context
* is free'd and must no longer be used.
*/
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.
*
* @param context context (from stun_bind_start() or stun_conncheck_start())
* @param buf pointer to received data to be processed
* @param len byte length of data at @a buf
* @param addr socket address pointer to receive mapped address in case of
* successful processing
* @param addrlen [IN/OUT] pointer to the size of the socket address buffer
* at @a addr upon entry, set to the useful size on success
*
* @return 0 on success, an error code otherwise:
* - EAGAIN: ignored invalid message (non-fatal error)
* - ECONNREFUSED: error message from server
* - EPROTO: unsupported message from server
* - ENOENT: no mapped address in message from server
* - EAFNOSUPPORT: unsupported mapped address family from server
* - EINVAL: invalid mapped address from server
*
* If anything except EAGAIN (but including zero) is returned, the context
* is free'd and must no longer be used.
*/
int stun_bind_process (stun_bind_t *restrict context,
const void *restrict buf, size_t len,
struct sockaddr *restrict addr, socklen_t *addrlen);
/**
* Continues STUN Binding discovery in non-blocking mode:
* Tries to dequeue a data from the network and processes it,
* updates the transaction timer if needed.
*
* @param context binding discovery context (from stun_bind_start())
* @param addr pointer to a socket address structure to hold the discovered
* binding (remember this can be either IPv4 or IPv6 regardless of the socket
* family) [OUT]
* @param addrlen pointer to the byte length of @a addr [IN], set to the byte
* length of the binding socket address on return.
*
* @return EAGAIN is returned if the discovery has not completed yet.
0 is returned on successful completion, another standard error value
* otherwise. If the return value is not EAGAIN, @a context is freed and must
* not be re-used.
*/
int stun_bind_resume (stun_bind_t *restrict context, int stun_bind_resume (stun_bind_t *restrict context,
struct sockaddr *restrict addr, socklen_t *addrlen); struct sockaddr *restrict addr, socklen_t *addrlen);
void stun_bind_cancel (stun_bind_t *restrict context);
int stun_bind_fd (const stun_bind_t *restrict context);
unsigned stun_bind_timeout (const stun_bind_t *restrict context); /**
* 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
ssize_t stun_validate (const uint8_t *msg, size_t len);
# endif
# ifdef __cplusplus # ifdef __cplusplus
} }
......
/*
* 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>
static int
stun_bind_error (uint8_t *buf, size_t *plen, const uint8_t *req,
stun_error_t code, const char *pass)
{
int val = stun_init_error (buf, *plen, req, code);
if (val)
return val;
val = stun_finish_short (buf, plen, NULL, pass, NULL, 0);
if (val)
return val;
DBG (" Error response (%u) of %u bytes\n", (unsigned)code,
(unsigned)*plen);
return 0;
}
static int
stun_binding_reply (uint8_t *buf, size_t *restrict plen, const uint8_t *msg,
const struct sockaddr *restrict src, socklen_t srclen,
bool muxed, const char *restrict pass)
{
assert (plen != NULL);
size_t len = *plen;
int val;
*plen = 0;
#define err( code ) \
stun_bind_error (buf, &len, msg, code, pass); \
*plen = len
if (stun_get_class (msg) != STUN_REQUEST)
{
DBG (" Unhandled non-request (class %u) message.\n",
(unsigned)stun_get_class (msg));
return EINVAL;
}
if (muxed)
{
if (!stun_demux (msg))
{
DBG (" Incorrectly multiplexed STUN message ignored.\n");
return EINVAL;
}
}
else
muxed = stun_demux (msg);
DBG (" %s-style STUN message.\n", muxed ? "New" : "Old");
if (pass != NULL)
{
if (!stun_has_integrity (msg))
{
DBG (" Message Authentication Code missing.\n");
err (STUN_UNAUTHORIZED);
return EPERM;
}
if (!stun_present (msg, STUN_USERNAME))
{
DBG (" Username missing.\n");
err (STUN_MISSING_USERNAME);
return EPERM;
}
/* NOTE: should check in that order:
* - missing realm
* - missing nonce
* - stale nonce
* - unknown user / stale credentials
*/
if (stun_verify_password (msg, pass))
{
DBG (" Integrity check failed.\n");
err (STUN_INTEGRITY_CHECK_FAILURE);
return EPERM;
}
}
if (stun_get_method (msg) != STUN_BINDING)
{
DBG (" Bad request (method %u) message.\n",
(unsigned)stun_get_method (msg));
err (STUN_BAD_REQUEST);
return EPROTO;
}
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, 0);
if (val)
goto failure;
*plen = len;
return EPROTO;
}
stun_init_response (buf, msg);
val = muxed
? stun_append_xor_addr (buf, len, STUN_XOR_MAPPED_ADDRESS, src, srclen)
: stun_append_addr (buf, len, STUN_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, 0);
if (val)
goto failure;
*plen = len;
return 0;
failure:
err (STUN_GLOBAL_FAILURE);
return val;
}
#undef err
int
stun_bind_reply (uint8_t *buf, size_t *restrict plen, const uint8_t *msg,
const struct sockaddr *restrict src, socklen_t srclen,
bool muxed)
{
return stun_binding_reply (buf, plen, msg, src, srclen, muxed, NULL);
}
/** Connectivity checks **/
#include "conncheck.h"
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)
{
uint64_t q;
int val = stun_binding_reply (buf, plen, msg, src, srclen, true, pass);
if (val)
return val;
/* Role conflict handling */
// FIXME: breaks if buf == msg
assert (val == 0);
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;
val = EACCES;
}
else
{
DBG (" staying \"controll%s\" (sending error)\n",
*control ? "ing" : "ed");
stun_bind_error (buf, plen, msg, STUN_ROLE_CONFLICT, pass);
}
}
return val;
}
char *stun_conncheck_username (const uint8_t *restrict msg,
char *restrict buf, size_t buflen)
{
ssize_t len = stun_find_string (msg, STUN_USERNAME, buf, buflen);
if ((len == -1) || ((size_t)len >= buflen))
return NULL;
for (size_t i = 0; i < (size_t)len; i++)
{
char c = buf[i];
if (((c >= '/') && (c <= '9')) || ((c >= 'A') && (c <= 'Z'))
|| ((c >= 'a') && (c <= 'z')) || (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);
}
/*
* 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 STUN_CONNCHECK_H
# define STUN_CONNCHECK_H 1
# include "stun/bind.h"
# ifdef __cplusplus
extern "C" {
# endif
/**
* @file conncheck.h
* @brief STUN/ICE connectivity checks
*/
/**
* Starts a connectivity check using STUN Binding discovery.
*
* @param context pointer to an opaque pointer that will be passed to
* stun_bind_resume() afterward
* @param fd socket to use for discovery, or -1 to create one
* @param srv STUN server socket address
* @param srvlen STUN server socket address length
* @param username nul-terminated username for authentication
* (need not be kept valid after return)
* @param password nul-terminated shared secret (ICE password)
* (need not be kept valid after return)
* @param cand_use whether to include a USE-CANDIDATE flag
* @param priority host-byte order PRIORITY value
* @param controlling whether we are in controlling (true) or
* controlled (false) state
* @param tie control tie breaker value (host-byte order)
*
* @return 0 on success, a standard error value otherwise.
*/
int stun_conncheck_start (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen,
const char *username, const char *password,
bool cand_use, bool controlling, uint32_t priority,
uint64_t tie);
/**
* Tries to parse a STUN connectivity check (Binding request) and format a
* 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 the binding request
* @param src socket address the message was received from
* @param srclen byte length of the socket address
* @param password HMAC secret password
* @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
*
* @note @a buf and @a msg <b>must not</b> collide.
*/
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);
/**
* Extracts the username from a STUN message.
* @param msg pointer to the first byte of the binding request
* @param buf where to store the username as a nul-terminated string
* @param buflen byte length of @a buf buffer
*
* @return @a buf on success, NULL on error
*/
char *stun_conncheck_username (const uint8_t *restrict msg,
char *restrict buf, size_t buflen);
/**
* Extracts the priority from a STUN message.
* @param msg valid STUN message.
* @return host byte order priority, or 0 if not specified.
*/
uint32_t stun_conncheck_priority (const uint8_t *msg);
/**
* Extracts the "use candidate" flag from a STUN message.
* @param msg valid STUN message.
* @return true if the flag is set, false if not.
*/
bool stun_conncheck_use_candidate (const uint8_t *msg);
# ifdef __cplusplus
}
# endif
#endif
...@@ -53,15 +53,13 @@ static uint32_t crc32 (const void *buf, size_t size); ...@@ -53,15 +53,13 @@ static uint32_t crc32 (const void *buf, size_t size);
* Computes the FINGERPRINT of a STUN message. * Computes the FINGERPRINT of a STUN message.
* @return fingerprint value in <b>host</b> byte order. * @return fingerprint value in <b>host</b> byte order.
*/ */
uint32_t stun_fingerprint (const void *msg) uint32_t stun_fingerprint (const uint8_t *msg)
{ {
const uint8_t *ptr = msg;
assert (msg != NULL); assert (msg != NULL);
/* Don't hash last 8-bytes (= the FINGERPRINT attribute) */ /* Don't hash last 8-bytes (= the FINGERPRINT attribute) */
assert (stun_length (ptr) >= 8); assert (stun_length (msg) >= 8);
size_t len = 20 + stun_length (ptr) - 8; size_t len = 20u + stun_length (msg) - 8;
return crc32 (msg, len) ^ 0x5354554e; return crc32 (msg, len) ^ 0x5354554e;
} }
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
#include <assert.h> #include <assert.h>
void stun_sha1 (const void *msg, uint8_t *sha, const void *key, size_t keylen) void stun_sha1 (const uint8_t *msg, uint8_t *sha, const void *key, size_t keylen)
{ {
size_t mlen = stun_length (msg); size_t mlen = stun_length (msg);
assert (mlen >= 32); assert (mlen >= 32);
......
...@@ -56,10 +56,10 @@ static inline void DBG (const char *fmt, ...) ...@@ -56,10 +56,10 @@ static inline void DBG (const char *fmt, ...)
# include <sys/types.h> # include <sys/types.h>
# include <stdbool.h> # include <stdbool.h>
# define STUN_MAXMSG 65552 /* bytes */
# define STUN_COOKIE 0x2112A442 # define STUN_COOKIE 0x2112A442
# ifndef IPPORT_STUN # define STUN_COOKIE_BYTES 0x21, 0x12, 0xA4, 0x42
# define IPPORT_STUN 3478
# endif
typedef struct stun_hdr_s typedef struct stun_hdr_s
{ {
...@@ -70,11 +70,7 @@ typedef struct stun_hdr_s ...@@ -70,11 +70,7 @@ typedef struct stun_hdr_s
} stun_hdr_t; } stun_hdr_t;
typedef struct stun_s typedef uint8_t stun_msg_t[STUN_MAXMSG];
{
stun_hdr_t hdr;
uint8_t buf[65532];
} stun_msg_t;
/* Message classes */ /* Message classes */
typedef enum typedef enum
...@@ -113,11 +109,17 @@ typedef enum ...@@ -113,11 +109,17 @@ typedef enum
STUN_XOR_MAPPED_ADDRESS=0x0020, STUN_XOR_MAPPED_ADDRESS=0x0020,
STUN_PRIORITY=0x0024, /* ICE-15 */
STUN_USE_CANDIDATE=0x0025, /* ICE-15 */
/* Optional attributes */ /* Optional attributes */
STUN_FINGERPRINT=0x8021, // FIXME: rfc3489bis-06 has wrong value
STUN_SERVER=0x8022, STUN_SERVER=0x8022,
STUN_ALTERNATE_SERVER=0x8023, STUN_ALTERNATE_SERVER=0x8023,
STUN_REFRESH_INTERVAL=0x8024 STUN_REFRESH_INTERVAL=0x8024,
STUN_FINGERPRINT=0x8028,
STUN_ICE_CONTROLLED=0x8029, /* ICE-15 */
STUN_ICE_CONTROLLING=0x802A /* ICE-15 */
} stun_attr_type_t; } stun_attr_type_t;
...@@ -128,44 +130,25 @@ static inline int stun_optional (stun_attr_type_t t) ...@@ -128,44 +130,25 @@ static inline int stun_optional (stun_attr_type_t t)
typedef uint8_t stun_transid_t[12]; typedef uint8_t stun_transid_t[12];
typedef struct stun_attr_hdr_s
{
uint16_t attr_type;
uint16_t attr_len;
uint8_t attr_value[0];
} stun_attr_hdr_t;
/* MESSAGE-INTEGRITY */
typedef struct stun_attr_integrity_s
{
stun_attr_hdr_t int_hdr;
uint8_t int_hmac[20];
} stun_attr_integrity_t;
/* ERROR-CODE */
typedef struct stun_attr_error_s
{
stun_attr_hdr_t err_hdr;
unsigned err_zero:21;
unsigned err_class:3;
uint8_t err_number;
} stun_attr_error_t;
/* Error codes */ /* Error codes */
# define STUN_TRY_ALTERNATE 300 typedef enum
# define STUN_BAD_REQUEST 400 {
# define STUN_UNAUTHORIZED 401 STUN_TRY_ALTERNATE=300,
# define STUN_UNKNOWN_ATTRIBUTE 420 STUN_BAD_REQUEST=400,
# define STUN_STALE_CREDENTIALS 430 STUN_UNAUTHORIZED=401,
# define STUN_INTEGRITY_CHECK_FAILURE 431 STUN_UNKNOWN_ATTRIBUTE=420,
# define STUN_MISSING_USERNAME 432 STUN_STALE_CREDENTIALS=430,
# define STUN_USE_TLS 433 STUN_INTEGRITY_CHECK_FAILURE=431,
# define STUN_MISSING_REALM 434 STUN_MISSING_USERNAME=432,
# define STUN_MISSING_NONCE 435 STUN_USE_TLS=433,
# define STUN_UNKNOWN_USERNAME 436 STUN_MISSING_REALM=434,
# define STUN_STALE_NONCE 438 STUN_MISSING_NONCE=435,
# define STUN_SERVER_ERROR 500 STUN_UNKNOWN_USERNAME=436,
# define STUN_GLOBAL_FAILURE 600 STUN_STALE_NONCE=438,
STUN_ROLE_CONFLICT=487,
STUN_SERVER_ERROR=500,
STUN_GLOBAL_FAILURE=600
} stun_error_t;
/** /**
...@@ -173,16 +156,16 @@ typedef struct stun_attr_error_s ...@@ -173,16 +156,16 @@ typedef struct stun_attr_error_s
*/ */
static inline size_t stun_padding (size_t l) static inline size_t stun_padding (size_t l)
{ {
static const size_t pads[4] = { 0, 3, 2, 1 }; return (4 - (l & 3)) & 3;
return pads[l & 3];
} }
/** /**
* Rounds up an integer to the next multiple of 4. * Rounds up an integer to the next multiple of 4.
*/ */
static inline size_t stun_align (size_t l) static inline size_t stun_align (size_t l)
{ {
return l + stun_padding (l); return (l + 3) & ~3;
} }
...@@ -190,22 +173,21 @@ static inline size_t stun_align (size_t l) ...@@ -190,22 +173,21 @@ static inline size_t stun_align (size_t l)
* Reads a word from a non-aligned buffer. * Reads a word from a non-aligned buffer.
* @return host byte order word value. * @return host byte order word value.
*/ */
static inline uint16_t stun_getw (const void *ptr) static inline uint16_t stun_getw (const uint8_t *ptr)
{ {
return (((const uint8_t *)ptr)[0] << 8) return ((ptr)[0] << 8) | ptr[1];
| ((const uint8_t *)ptr)[1];
} }
static inline uint16_t stun_length (const void *ptr) static inline uint16_t stun_length (const uint8_t *ptr)
{ {
return stun_getw (((const uint8_t *)ptr) + 2); return stun_getw (ptr + 2);
} }
/** /**
* @return STUN message class in host byte order (value from 0 to 3) * @return STUN message class in host byte order (value from 0 to 3)
*/ */
static inline uint16_t stun_get_class (const void *msg) static inline uint16_t stun_get_class (const uint8_t *msg)
{ {
uint16_t t = stun_getw (msg); uint16_t t = stun_getw (msg);
return ((t & 0x0100) >> 7) | ((t & 0x0010) >> 4); return ((t & 0x0100) >> 7) | ((t & 0x0010) >> 4);
...@@ -214,56 +196,217 @@ static inline uint16_t stun_get_class (const void *msg) ...@@ -214,56 +196,217 @@ static inline uint16_t stun_get_class (const void *msg)
/** /**
* @return STUN message method (value from 0 to 0xfff) * @return STUN message method (value from 0 to 0xfff)
*/ */
static inline uint16_t stun_get_method (const void *msg) static inline uint16_t stun_get_method (const uint8_t *msg)
{ {
uint16_t t = stun_getw (msg); uint16_t t = stun_getw (msg);
return ((t & 0x3e00) >> 2) | ((t & 0x00e0) >> 1) | (t & 0x000f); return ((t & 0x3e00) >> 2) | ((t & 0x00e0) >> 1) | (t & 0x000f);
} }
/**
* @return STUN message transaction ID
*/
static inline const uint8_t *stun_id (const uint8_t *msg)
{
//assert (stun_valid (req));
return msg + 8;
}
# ifdef __cplusplus # ifdef __cplusplus
extern "C" { extern "C" {
# endif # endif
uint32_t stun_fingerprint (const void *msg); uint32_t stun_fingerprint (const uint8_t *msg);
void stun_sha1 (const void *msg, uint8_t *sha, void stun_sha1 (const uint8_t *msg, uint8_t *sha,
const void *key, size_t keylen); const void *key, size_t keylen);
int stun_xor_address (const void *msg, int stun_xor_address (const uint8_t *msg,
struct sockaddr *addr, socklen_t addrlen); struct sockaddr *addr, socklen_t addrlen);
/* Message processing functions */ /**
ssize_t stun_validate (const void *msg, size_t len); * @section stunrecv
bool stun_demux (const void *msg); * @brief STUN message processing functions
bool stun_match_answer (const void *msg, stun_method_t method, */
const stun_transid_t id, bool *restrict error);
int stun_verify_key (const void *msg, const void *key, size_t keylen); # ifndef STUN_VALIDATE_DECLARATION
int stun_verify_password (const void *msg, const char *pw); # define STUN_VALIDATE_DECLARATION 1
/**
* Verifies that a packet is a valid STUN message.
*
* @return actual byte length of the message if valid (>0),
* 0 if it the packet is incomplete or -1 in case of other error.
*/
ssize_t stun_validate (const uint8_t *msg, size_t len);
#endif
/**
* Checks whether a packet on a mutiplexed STUN/non-STUN channel looks like a
* STUN message. It is assumed that stun_validate succeeded first (i.e.
* returned a stricly positive value).
*
* @return true if STUN message with cookie and fingerprint, 0 otherwise.
*/
bool stun_demux (const uint8_t *msg);
bool stun_match_messages (const uint8_t *restrict resp,
const uint8_t *restrict req,
const uint8_t *key, size_t keylen,
bool *restrict error);
int stun_verify_key (const uint8_t *msg, const void *key, size_t keylen);
int stun_verify_password (const uint8_t *msg, const char *pw);
/**
* Checks if an attribute is present within a STUN message.
*
* @param msg valid STUN message
* @param type STUN attribute type (host byte order)
*
* @return whether there is a MESSAGE-INTEGRITY attribute
*/
bool stun_present (const uint8_t *msg, stun_attr_type_t type);
/*int stun_find32 (const void *msg, stun_attr_type_t type, uint32_t *pval);*/ /**
int stun_find_addr (const void *restrict msg, stun_attr_type_t type, * Looks for a flag attribute within a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @return 0 if flag is present, ENOENT if it is not, EINVAL if flag payload
* size is not zero.
*/
int stun_find_flag (const uint8_t *msg, stun_attr_type_t type);
/**
* Extracts a 32-bits attribute from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param pval [OUT] where to store the host byte ordered value
* @return 0 on success, ENOENT if attribute not found,
* EINVAL if attribute payload was not 32-bits.
*/
int stun_find32 (const uint8_t *msg, stun_attr_type_t type, uint32_t *pval);
/**
* Extracts a 64-bits attribute from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param pval [OUT] where to store the host byte ordered value
* @return 0 on success, ENOENT if attribute not found,
* EINVAL if attribute payload was not 64-bits.
*/
int stun_find64 (const uint8_t *msg, stun_attr_type_t type, uint64_t *pval);
/**
* Extracts a string from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param buf buffer to store the extracted string
* @param buflen byte length of @a buf
*
* @return number of characters (not including terminating nul) that would
* have been written to @a buf if @a buflen were big enough (if the return
* value is strictly smaller than @a buflen then the call was successful);
* -1 if the specified attribute could not be found.
*
* @note A nul-byte is appended at the end (unless the buffer is not big
* enough). However this function does not check for nul characters within
* the extracted string; the caller is responsible for ensuring that the
* extracted string does not contain any "illegal" bytes sequence (nul bytes
* or otherwise, depending on the context).
*/
ssize_t stun_find_string (const uint8_t *restrict msg, stun_attr_type_t type,
char *buf, size_t buflen);
/**
* Extracts a network address attribute from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param addr [OUT] where to store the socket address
* @param addrlen [IN/OUT] pointer to the size of the socket address
* buffer upon entry, set to the length of the extracted socket
* address upon return,
* @return 0 on success, ENOENT if attribute not found,
* EINVAL if attribute payload size was wrong or addrlen too small,
* EAFNOSUPPORT if address family is unknown.
*/
int stun_find_addr (const uint8_t *restrict msg, stun_attr_type_t type,
struct sockaddr *restrict addr, struct sockaddr *restrict addr,
socklen_t *restrict addrlen); socklen_t *restrict addrlen);
int stun_find_xor_addr (const void *restrict msg, stun_attr_type_t type,
/**
* Extracts an obfuscated network address attribute from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param addr [OUT] where to store the socket address
* @param addrlen [IN/OUT] pointer to the size of the socket address
* buffer upon entry, set to the length of the extracted socket
* address upon return,
* @return 0 on success, ENOENT if attribute not found,
* EINVAL if attribute payload size was wrong or addrlen too small,
* EAFNOSUPPORT if address family is unknown.
*/
int stun_find_xor_addr (const uint8_t *restrict msg, stun_attr_type_t type,
struct sockaddr *restrict addr, struct sockaddr *restrict addr,
socklen_t *restrict addrlen); socklen_t *restrict addrlen);
unsigned stun_find_unknown (const void *msg, uint16_t *list, unsigned max);
int stun_memcmp (const uint8_t *restrict msg, stun_attr_type_t type,
const void *data, size_t len);
int stun_strcmp (const uint8_t *restrict msg, stun_attr_type_t type,
const char *str);
bool stun_is_unknown (uint16_t type);
unsigned stun_find_unknown (const uint8_t *msg, uint16_t *list, unsigned max);
/* Message formatting functions */ /* Message formatting functions */
void stun_init (stun_msg_t *msg, stun_class_t c, stun_method_t m, void stun_init_request (uint8_t *msg, stun_method_t m);
const stun_transid_t id); void stun_init_response (uint8_t *ans, const uint8_t *req);
void stun_init_response (stun_msg_t *ans, const void *req); int stun_init_error (uint8_t *ans, size_t msize, const uint8_t *req,
void stun_make_transid (stun_transid_t id); stun_error_t err);
size_t stun_finish_short (stun_msg_t *restrict msg, int stun_init_error_unknown (uint8_t *ans, size_t msize, const uint8_t *req);
/**
* Completes a STUN message structure before sending it, and
* authenticates it with short-term credentials.
* No further attributes shall be added.
*
* @param msg STUN message buffer
* @param plen [IN/OUT] buffer size on entry, message length on return
* @param username nul-terminated STUN username (or NULL if none)
* @param password nul-terminated STUN secret password (or NULL if none)
* @param nonce STUN authentication nonce (or NULL if none)
* @param noncelen STUN authentication once byte length
* (ignored if nonce is NULL)
*
* @return 0 on success, ENOBUFS on error.
*/
size_t stun_finish_short (uint8_t *msg, size_t *restrict plen,
const char *username, const char *password, const char *username, const char *password,
const void *nonce, size_t noncelen); const void *nonce, size_t noncelen);
size_t stun_finish (stun_msg_t *m);
int stun_append32 (stun_msg_t *msg, stun_attr_type_t type, /**
uint32_t value); * Completes a STUN message structure before sending it.
int stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, * No further attributes shall be added.
*
* @param msg STUN message buffer
* @param plen [IN/OUT] buffer size on entry, message length on return
*
* @return 0 on success, ENOBUFS on error.
*/
size_t stun_finish (uint8_t *restrict msg, size_t *restrict plen);
int stun_append_flag (uint8_t *msg, size_t msize, stun_attr_type_t type);
int stun_append32 (uint8_t *msg, size_t msize,
stun_attr_type_t type, uint32_t value);
int stun_append64 (uint8_t *msg, size_t msize,
stun_attr_type_t type, uint64_t value);
int stun_append_string (uint8_t *restrict msg, size_t msize,
stun_attr_type_t type, const char *str);
int stun_append_addr (uint8_t *restrict msg, size_t msize,
stun_attr_type_t type,
const struct sockaddr *restrict addr, const struct sockaddr *restrict addr,
socklen_t addrlen); socklen_t addrlen);
int stun_append_xor_addr (stun_msg_t *restrict msg, stun_attr_type_t type, int stun_append_xor_addr (uint8_t *restrict msg, size_t msize,
stun_attr_type_t type,
const struct sockaddr *restrict addr, const struct sockaddr *restrict addr,
socklen_t addrlen); socklen_t addrlen);
...@@ -282,4 +425,14 @@ static inline bool stun_has_unknown (const void *msg) ...@@ -282,4 +425,14 @@ static inline bool stun_has_unknown (const void *msg)
return stun_find_unknown (msg, &dummy, 1); return stun_find_unknown (msg, &dummy, 1);
} }
/**
* @param msg valid STUN message
* @return whether there is a MESSAGE-INTEGRITY attribute
*/
static inline bool stun_has_integrity (const uint8_t *msg)
{
return stun_present (msg, STUN_MESSAGE_INTEGRITY);
}
#endif #endif
/*
* 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 <sys/types.h>
#include <sys/socket.h>
#include "stun/bind.h"
#include <unistd.h>
#include <getopt.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static void
printaddr (const char *str, const struct sockaddr *addr, socklen_t addrlen)
{
char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV];
int val = getnameinfo (addr, addrlen, hostbuf, sizeof (hostbuf),
servbuf, sizeof (servbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (val)
printf ("%s: %s\n", str, gai_strerror (val));
else
printf ("%s: %s port %s\n", str, hostbuf, servbuf);
}
static int run (int family, const char *hostname, const char *service)
{
struct addrinfo hints, *res;
int retval = -1;
memset (&hints, 0, sizeof (hints));
hints.ai_family = family;
hints.ai_socktype = SOCK_DGRAM;
if (service == NULL)
service = "3478";
int val = getaddrinfo (hostname, service, &hints, &res);
if (val)
{
fprintf (stderr, "%s (port %s): %s\n", hostname, service,
gai_strerror (val));
return -1;
}
for (const struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
{
struct sockaddr_storage addr;
socklen_t addrlen = sizeof (addr);
printaddr ("Server address", ptr->ai_addr, ptr->ai_addrlen);
val = stun_bind_run (-1, ptr->ai_addr, ptr->ai_addrlen,
(struct sockaddr *)&addr, &addrlen);
if (val)
fprintf (stderr, "%s\n", strerror (val));
else
{
printaddr ("Mapped address", (struct sockaddr *)&addr, addrlen);
retval = 0;
}
}
freeaddrinfo (res);
return retval;
}
int main (int argc, char *argv[])
{
static const struct option opts[] =
{
{ "ipv4", no_argument, NULL, '4' },
{ "ipv6", no_argument, NULL, '6' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }
};
const char *server = NULL, *port = NULL;
int family = AF_UNSPEC;
for (;;)
{
int val = getopt_long (argc, argv, "46hV", opts, NULL);
if (val == EOF)
break;
switch (val)
{
case '4':
family = AF_INET;
break;
case '6':
family = AF_INET6;
break;
case 'h':
printf ("Usage: %s [-4|-6] <server> [port number]\n"
"Performs STUN Binding Discovery\n"
"\n"
" -4, --ipv4 Force IP version 4\n"
" -6, --ipv6 Force IP version 6\n"
"\n", argv[0]);
return 0;
case 'V':
printf ("stunbcd: STUN Binding Discovery client (%s v%s)\n",
PACKAGE, VERSION);
return 0;
}
}
if (optind < argc)
server = argv[optind++];
if (optind < argc)
port = argv[optind++];
if (optind < argc)
{
fprintf (stderr, "%s: extra parameter `%s'\n", argv[0], argv[optind]);
return 2;
}
return run (family, server, port) ? 1 : 0;
}
...@@ -62,6 +62,7 @@ ...@@ -62,6 +62,7 @@
static static
int listen_socket (int fam, int type, int proto, uint16_t port) int listen_socket (int fam, int type, int proto, uint16_t port)
{ {
int yes = 1;
int fd = socket (fam, type, proto); int fd = socket (fam, type, proto);
if (fd == -1) if (fd == -1)
{ {
...@@ -88,7 +89,8 @@ int listen_socket (int fam, int type, int proto, uint16_t port) ...@@ -88,7 +89,8 @@ int listen_socket (int fam, int type, int proto, uint16_t port)
case AF_INET6: case AF_INET6:
#ifdef IPV6_V6ONLY #ifdef IPV6_V6ONLY
setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof (int)); if (setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &yes, sizeof (yes)))
goto error;
#endif #endif
((struct sockaddr_in6 *)&addr)->sin6_port = port; ((struct sockaddr_in6 *)&addr)->sin6_port = port;
break; break;
...@@ -105,13 +107,12 @@ int listen_socket (int fam, int type, int proto, uint16_t port) ...@@ -105,13 +107,12 @@ int listen_socket (int fam, int type, int proto, uint16_t port)
switch (fam) switch (fam)
{ {
case AF_INET: case AF_INET:
setsockopt (fd, SOL_IP, IP_PKTINFO, &(int){ 1 }, setsockopt (fd, SOL_IP, IP_PKTINFO, &yes, sizeof (yes));
sizeof (int));
break; break;
case AF_INET6: case AF_INET6:
setsockopt (fd, SOL_IPV6, IPV6_RECVPKTINFO, &(int){ 1 }, setsockopt (fd, SOL_IPV6, IPV6_RECVPKTINFO, &yes,
sizeof (int)); sizeof (yes));
break; break;
} }
} }
...@@ -132,14 +133,15 @@ error: ...@@ -132,14 +133,15 @@ error:
} }
#include "stun-msg.h" #include "stun-msg.h" // FIXME: remove
#include "bind.h"
static int dgram_process (int sock) static int dgram_process (int sock)
{ {
struct sockaddr_storage addr; struct sockaddr_storage addr;
stun_msg_t buf; stun_msg_t buf;
char ctlbuf[CMSG_SPACE (sizeof (struct in6_pktinfo))]; char ctlbuf[CMSG_SPACE (sizeof (struct in6_pktinfo))];
struct iovec iov = { &buf, sizeof (buf) }; struct iovec iov = { buf, sizeof (buf) };
struct msghdr mh; struct msghdr mh;
memset (&mh, 0, sizeof (mh)); memset (&mh, 0, sizeof (mh));
...@@ -163,37 +165,13 @@ static int dgram_process (int sock) ...@@ -163,37 +165,13 @@ static int dgram_process (int sock)
return EMSGSIZE; return EMSGSIZE;
} }
len = stun_validate (&buf, len); if (stun_validate (buf, len) <= 0)
if (len <= 0)
{
DBG ("Invalid STUN message ignored.\n");
return EINVAL; return EINVAL;
}
DBG ("%u-bytes STUN message received.\n", (unsigned)len);
if (stun_get_class (&buf) != STUN_REQUEST)
return 0;
if (stun_get_method (&buf) != STUN_BINDING) len = stun_bind_reply (buf, &iov.iov_len, buf,
return 0; // FIXME: SPEC: return an error response mh.msg_name, mh.msg_namelen, false);
// FIXME: check for unsupported attributes et al if (iov.iov_len == 0)
return len;
bool compat = !stun_demux (&buf);
DBG (" %s-style STUN message.\n", compat ? "Old" : "New");
stun_init_response (&buf, &buf);
len = compat
? stun_append_addr (&buf, STUN_MAPPED_ADDRESS,
(struct sockaddr *)&addr, mh.msg_namelen)
: stun_append_xor_addr (&buf, STUN_XOR_MAPPED_ADDRESS,
(struct sockaddr *)&addr, mh.msg_namelen);
if (len)
return len; // FIXME: send 600 error
iov.iov_len = len = stun_finish (&buf);
if (len == 0)
return ENOBUFS; // FIXME: send 600 error
len = sendmsg (sock, &mh, 0); len = sendmsg (sock, &mh, 0);
if (len == -1) if (len == -1)
......
...@@ -49,77 +49,67 @@ ...@@ -49,77 +49,67 @@
#ifndef NDEBUG #ifndef NDEBUG
static inline static inline
int stun_valid (const void *m) int stun_valid (const uint8_t *msg)
{ {
size_t length = 20u + stun_length (m); size_t length = 20u + stun_length (msg);
return stun_validate (m, length) == (ssize_t)length; return stun_validate (msg, length) == (ssize_t)length;
} }
#endif #endif
/** ssize_t stun_validate (const uint8_t *msg, size_t len)
* Verifies that a packet is a valid STUN message.
*
* @return actual byte length of the message if valid (>0),
* 0 if it the packet is incomplete or -1 in case of other error.
*/
ssize_t stun_validate (const void *m, size_t len)
{ {
const uint8_t *ptr = m;
DBG ("Validating message @%p (%u bytes):\n", m, (unsigned)len);
if (len < 1) if (len < 1)
{ {
DBG (" No data!\n"); DBG ("STUN error: No data!\n");
return 0; return 0;
} }
if (ptr[0] >> 6) if (msg[0] >> 6)
{ {
DBG (" RTP or other non-protocol packet!\n"); DBG ("STUN error: RTP or other non-protocol packet!\n");
return -1; // RTP or other non-STUN packet return -1; // RTP or other non-STUN packet
} }
if (len < 4) if (len < 4)
{ {
DBG (" Incomplete STUN message header!\n"); DBG ("STUN error: Incomplete STUN message header!\n");
return 0; return 0;
} }
size_t mlen = 20 + stun_length (ptr); size_t mlen = 20u + stun_length (msg);
if (stun_padding (mlen)) if (stun_padding (mlen))
{ {
DBG (" Invalid message length: %u!\n", (unsigned)mlen); DBG ("STUN error: Invalid message length: %u!\n", (unsigned)mlen);
return -1; // wrong padding return -1; // wrong padding
} }
if (len < mlen) if (len < mlen)
{ {
DBG (" Incomplete STUN message: %u of %u bytes!\n", len, DBG ("STUN error: Incomplete message: %u of %u bytes!\n", len,
(unsigned)mlen); (unsigned)mlen);
return 0; // partial message return 0; // partial message
} }
ptr += 20; msg += 20;
/* from then on, we know we have the entire packet in buffer */ /* from then on, we know we have the entire packet in buffer */
for (const uint8_t *end = ptr + (mlen - 20); end > ptr;) for (const uint8_t *end = msg + (mlen - 20); end > msg;)
{ {
ptr += 4; msg += 4;
/* thanks to padding check, if (end > ptr) then there is not only one /* thanks to padding check, if (end > msg) then there is not only one
* but at least 4 bytes left */ * but at least 4 bytes left */
assert ((end - ptr) >= 0); assert ((end - msg) >= 0);
size_t alen = stun_align (stun_getw (ptr - 2)); size_t alen = stun_align (stun_getw (msg - 2));
ptr += alen; msg += alen;
if ((end - ptr) < 0) if ((end - msg) < 0)
{ {
DBG (" No room for STUN attribute: %u instead of %u bytes!\n", DBG ("STUN error: %u instead of %u bytes for attribute!\n",
(unsigned)(end - (ptr - alen)), (unsigned)alen); (unsigned)(end - (msg - alen)), (unsigned)alen);
return -1; // no room for attribute value + padding return -1; // no room for attribute value + padding
} }
} }
DBG (" Valid message of %u bytes!\n", mlen);
return mlen; return mlen;
} }
...@@ -133,54 +123,61 @@ ssize_t stun_validate (const void *m, size_t len) ...@@ -133,54 +123,61 @@ ssize_t stun_validate (const void *m, size_t len)
* otherwise NULL. * otherwise NULL.
*/ */
static const void * static const void *
stun_find (const void *restrict msg, stun_attr_type_t type, stun_find (const uint8_t *restrict msg, stun_attr_type_t type,
uint16_t *restrict palen) uint16_t *restrict palen)
{ {
const uint8_t *ptr = msg;
assert (msg != NULL); assert (msg != NULL);
assert (stun_valid (msg)); assert (stun_valid (msg));
assert (palen != NULL); assert (palen != NULL);
size_t length = stun_length (ptr); size_t length = stun_length (msg);
ptr += 20; msg += 20;
while (length > 0) while (length > 0)
{ {
assert (length >= 4); assert (length >= 4);
uint16_t atype = stun_getw (ptr); uint16_t atype = stun_getw (msg);
unsigned alen = stun_length (ptr); unsigned alen = stun_length (msg);
length -= 4; length -= 4;
ptr += 4; msg += 4;
assert (length >= stun_align (alen)); assert (length >= stun_align (alen));
if (atype == type) if (atype == type)
{ {
assert (alen <= 0xffff); assert (alen <= 0xffff);
*palen = alen; *palen = alen;
return ptr; return msg;
} }
alen = stun_align (alen); alen = stun_align (alen);
length -= alen; length -= alen;
ptr += alen; msg += alen;
} }
return NULL; return NULL;
} }
#if 0 bool stun_present (const uint8_t *msg, stun_attr_type_t type)
/** {
* Extracts a 32-bits attribute from a valid STUN message. return stun_find (msg, type, &(uint16_t){ 0 }) != NULL;
* @param msg valid STUN message buffer }
* @param type STUN attribute type (host byte order)
* @param pval [OUT] where to store the host byte ordered value
* @return 0 on success, ENOENT if attribute not found, int stun_find_flag (const uint8_t *msg, stun_attr_type_t type)
* EINVAL if attribute payload was not 32-bits. {
*/ uint16_t len;
static int const void *ptr = stun_find (msg, type, &len);
stun_find32 (const void *restrict msg, stun_attr_type_t type,
if (ptr == NULL)
return ENOENT;
return (len == 0) ? 0 : EINVAL;
}
int
stun_find32 (const uint8_t *restrict msg, stun_attr_type_t type,
uint32_t *restrict pval) uint32_t *restrict pval)
{ {
uint16_t len; uint16_t len;
...@@ -197,23 +194,44 @@ stun_find32 (const void *restrict msg, stun_attr_type_t type, ...@@ -197,23 +194,44 @@ stun_find32 (const void *restrict msg, stun_attr_type_t type,
} }
return EINVAL; return EINVAL;
} }
#endif
/** int stun_find64 (const uint8_t *msg, stun_attr_type_t type, uint64_t *pval)
* Extracts a network address attribute from a valid STUN message. {
* @param msg valid STUN message buffer uint16_t len;
* @param type STUN attribute type (host byte order) const void *ptr = stun_find (msg, type, &len);
* @param addr [OUT] where to store the socket address if (ptr == NULL)
* @param addrlen [IN/OUT] pointer to the size of the socket address return ENOENT;
* buffer upon entry, set to the length of the extracted socket
* address upon return, if (len == 8)
* @return 0 on success, ENOENT if attribute not found, {
* EINVAL if attribute payload size was wrong or addrlen too small, uint32_t tab[2];
* EAFNOSUPPORT if address family is unknown. memcpy (tab, ptr, sizeof (tab));
*/ *pval = ((uint64_t)ntohl (tab[0]) << 32) | ntohl (tab[1]);
return 0;
}
return EINVAL;
}
ssize_t stun_find_string (const uint8_t *restrict msg, stun_attr_type_t type,
char *buf, size_t buflen)
{
uint16_t len;
const char *ptr = stun_find (msg, type, &len);
if (ptr == NULL)
return -1;
memcpy (buf, ptr, (len < buflen) ? len : buflen);
if (len < buflen)
buf[len] = '\0';
return len;
}
int int
stun_find_addr (const void *restrict msg, stun_attr_type_t type, stun_find_addr (const uint8_t *restrict msg, stun_attr_type_t type,
struct sockaddr *restrict addr, socklen_t *restrict addrlen) struct sockaddr *restrict addr, socklen_t *restrict addrlen)
{ {
uint16_t len; uint16_t len;
...@@ -272,7 +290,7 @@ stun_find_addr (const void *restrict msg, stun_attr_type_t type, ...@@ -272,7 +290,7 @@ stun_find_addr (const void *restrict msg, stun_attr_type_t type,
} }
int stun_xor_address (const void *restrict msg, int stun_xor_address (const uint8_t *restrict msg,
struct sockaddr *restrict addr, socklen_t addrlen) struct sockaddr *restrict addr, socklen_t addrlen)
{ {
switch (addr->sa_family) switch (addr->sa_family)
...@@ -304,20 +322,8 @@ int stun_xor_address (const void *restrict msg, ...@@ -304,20 +322,8 @@ int stun_xor_address (const void *restrict msg,
} }
/**
* Extracts an obfuscated network address attribute from a valid STUN message.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param addr [OUT] where to store the socket address
* @param addrlen [IN/OUT] pointer to the size of the socket address
* buffer upon entry, set to the length of the extracted socket
* address upon return,
* @return 0 on success, ENOENT if attribute not found,
* EINVAL if attribute payload size was wrong or addrlen too small,
* EAFNOSUPPORT if address family is unknown.
*/
int int
stun_find_xor_addr (const void *restrict msg, stun_attr_type_t type, stun_find_xor_addr (const uint8_t *restrict msg, stun_attr_type_t type,
struct sockaddr *restrict addr, struct sockaddr *restrict addr,
socklen_t *restrict addrlen) socklen_t *restrict addrlen)
{ {
...@@ -328,29 +334,59 @@ stun_find_xor_addr (const void *restrict msg, stun_attr_type_t type, ...@@ -328,29 +334,59 @@ stun_find_xor_addr (const void *restrict msg, stun_attr_type_t type,
return stun_xor_address (msg, addr, *addrlen); return stun_xor_address (msg, addr, *addrlen);
} }
#if 0
/**
* Compares the length and content of an attribute.
*
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param data pointer to value to compare with
* @param len byte length of the value
* @return 0 in case of match, ENOENT if attribute was not found,
* EINVAL if it did not match
*/
int stun_memcmp (const uint8_t *restrict msg, stun_attr_type_t type,
const void *data, size_t len)
{
uint16_t alen;
const void *ptr = stun_find (msg, type, &alen);
if (ptr == NULL)
return ENOENT;
if ((len != alen) || memcmp (ptr, data, len))
return EINVAL;
return 0;
}
/**
* Compares the content of an attribute with a string.
* @param msg valid STUN message buffer
* @param type STUN attribute type (host byte order)
* @param str string to compare with
* @return 0 in case of match, ENOENT if attribute was not found,
* EINVAL if it did not match
*/
int stun_strcmp (const uint8_t *restrict msg, stun_attr_type_t type,
const char *str)
{
return stun_memcmp (msg, type, str, strlen (str));
}
#endif
static bool check_cookie (const void *msg) static inline bool check_cookie (const uint8_t *msg)
{ {
uint32_t value; return memcmp (msg + 4, &(uint32_t){ htonl (STUN_COOKIE) }, 4) == 0;
memcpy (&value, ((const uint8_t *)msg) + 4, sizeof (value));
return value == htonl (STUN_COOKIE);
} }
static const uint8_t *stun_end (const void *msg) static const uint8_t *stun_end (const uint8_t *msg)
{ {
return ((const uint8_t *)msg) + 20 + stun_length (msg); return msg + 20 + stun_length (msg);
} }
/** bool stun_demux (const uint8_t *msg)
* Checks whether a packet on a mutiplexed STUN/non-STUN channel looks like a
* STUN message. It is assumed that stun_validate succeeded first (i.e.
* returned a stricly positive value).
*
* @return true if STUN message with cookie and fingerprint, 0 otherwise.
*/
bool stun_demux (const void *msg)
{ {
assert (stun_valid (msg)); assert (stun_valid (msg));
...@@ -395,11 +431,14 @@ bool stun_demux (const void *msg) ...@@ -395,11 +431,14 @@ bool stun_demux (const void *msg)
* valid MESSAGE-INTEGRITY attribute. * valid MESSAGE-INTEGRITY attribute.
*/ */
int int
stun_verify_key (const void *msg, const void *key, size_t keylen) stun_verify_key (const uint8_t *msg, const void *key, size_t keylen)
{ {
uint8_t sha[20]; uint8_t sha[20];
uint16_t hlen; uint16_t hlen;
assert (msg != NULL);
assert ((keylen == 0) || (key != NULL));
DBG ("Authenticating STUN message @%p\n", msg); DBG ("Authenticating STUN message @%p\n", msg);
const uint8_t *hash = stun_end (msg) - 20; const uint8_t *hash = stun_end (msg) - 20;
...@@ -416,7 +455,17 @@ stun_verify_key (const void *msg, const void *key, size_t keylen) ...@@ -416,7 +455,17 @@ stun_verify_key (const void *msg, const void *key, size_t keylen)
stun_sha1 (msg, sha, key, keylen); stun_sha1 (msg, sha, key, keylen);
if (memcmp (sha, hash, sizeof (sha))) if (memcmp (sha, hash, sizeof (sha)))
{ {
DBG (" Message HMAC-SHA1 fingerprint mismatch!\n"); DBG (" Message HMAC-SHA1 fingerprint mismatch!"
"\n key : 0x");
for (unsigned i = 0; i < keylen; i++)
DBG ("%02x", ((uint8_t *)key)[i]);
DBG ("\n expected: 0x");
for (unsigned i = 0; i < 20; i++)
DBG ("%02x", sha[i]);
DBG ("\n received: 0x");
for (unsigned i = 0; i < 20; i++)
DBG ("%02x", hash[i]);
DBG ("\n");
return EPERM; return EPERM;
} }
...@@ -425,10 +474,15 @@ stun_verify_key (const void *msg, const void *key, size_t keylen) ...@@ -425,10 +474,15 @@ stun_verify_key (const void *msg, const void *key, size_t keylen)
} }
int stun_verify_password (const void *msg, const char *pw) /**
* @param msg valid STUN message
* @param pw nul-terminated HMAC shared secret password
* @return 0 if the message integrity has been successfully verified with the
* specified key. EPERM if the hash was incorrect. ENOENT if there was no
* valid MESSAGE-INTEGRITY attribute.
*/
int stun_verify_password (const uint8_t *msg, const char *pw)
{ {
assert (msg != NULL);
assert (pw != NULL);
return stun_verify_key (msg, pw, strlen (pw)); return stun_verify_key (msg, pw, strlen (pw));
} }
...@@ -437,14 +491,17 @@ int stun_verify_password (const void *msg, const char *pw) ...@@ -437,14 +491,17 @@ int stun_verify_password (const void *msg, const char *pw)
* @param msg valid STUN message * @param msg valid STUN message
* @param method STUN method number (host byte order) * @param method STUN method number (host byte order)
* @param id STUN transaction id * @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 error [OUT] set to true iif the response is an error response * @param error [OUT] set to true iif the response is an error response
* *
* @return true if and only if the message is a response or an error response * @return true if and only if the message is a response or an error response
* with the STUN cookie and specified method and transaction identifier. * with the STUN cookie and specified method and transaction identifier.
*/ */
bool static bool
stun_match_answer (const void *msg, stun_method_t method, stun_match_answer (const uint8_t *msg, stun_method_t method,
const stun_transid_t id, bool *restrict error) const uint8_t *id, const uint8_t *key, size_t keylen,
bool *restrict error)
{ {
assert (stun_valid (msg)); assert (stun_valid (msg));
assert (error != NULL); assert (error != NULL);
...@@ -464,9 +521,86 @@ stun_match_answer (const void *msg, stun_method_t method, ...@@ -464,9 +521,86 @@ stun_match_answer (const void *msg, stun_method_t method,
break; break;
} }
return (stun_get_method (msg) == method) if ((stun_get_method (msg) != method) /* wrong request type */
&& check_cookie (msg) || !check_cookie (msg) /* response to old-style request */
&& !memcmp (((const uint8_t *)msg) + 8, id, 12); || memcmp (msg + 8, id, 12)) /* wrong transaction ID */
return false;
if ((key != NULL) && stun_verify_key (msg, key, keylen))
return false;
return true;
}
/**
* Matches a response (or error response) to a request.
*
* @param msg valid STUN message
* @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 error [OUT] set to true iif the response is an error response
*
* @return true if and only if the message is a response or an error response
* with the STUN cookie and specified method and transaction identifier.
*/
bool stun_match_messages (const uint8_t *restrict resp,
const uint8_t *restrict req,
const uint8_t *key, size_t keylen,
bool *restrict error)
{
assert (stun_valid (resp));
assert (stun_valid (req));
assert ((stun_get_class (req) >> 1) == 0);
return stun_match_answer (resp, stun_get_method (req),
stun_id (req), key, keylen, error);
}
/**
* @param type host-byte order STUN attribute type
*
* @return true if @a type is an attribute type unknown to this library
* (regardless of being a mandatory or optional attribute type)
*/
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_PASSWORD:
case STUN_MESSAGE_INTEGRITY:
case STUN_ERROR_CODE:
case STUN_UNKNOWN_ATTRIBUTES:
case STUN_OLD_REFLECTED_FROM:
case STUN_REALM:
case STUN_NONCE:
case STUN_XOR_MAPPED_ADDRESS:
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;
} }
...@@ -478,38 +612,33 @@ stun_match_answer (const void *msg, stun_method_t method, ...@@ -478,38 +612,33 @@ stun_match_answer (const void *msg, stun_method_t method,
* @return the number of unknown mandatory attributes up to max. * @return the number of unknown mandatory attributes up to max.
*/ */
unsigned unsigned
stun_find_unknown (const void *restrict msg, uint16_t *restrict list, stun_find_unknown (const uint8_t *restrict msg, uint16_t *restrict list,
unsigned max) unsigned max)
{ {
const uint8_t *ptr = msg;
unsigned count = 0; unsigned count = 0;
uint16_t len = stun_length (ptr); uint16_t len = stun_length (msg);
assert (stun_valid (msg)); assert (stun_valid (msg));
ptr += 20; msg += 20;
while ((len > 0) && (count < max)) while ((len > 0) && (count < max))
{ {
uint16_t type = stun_getw (ptr); uint16_t type = stun_getw (msg);
uint16_t alen = stun_length (ptr); size_t alen = stun_align (stun_length (msg));
ptr += 4 + alen; msg += 4 + alen;
assert (len >= (4 + alen));
len -= 4 + alen; len -= 4 + alen;
if (stun_optional (type)) if (!stun_optional (type)
continue; /* non-mandatory attribute */ && stun_is_unknown (type))
if ((type >= STUN_MAPPED_ADDRESS) {
&& (type <= STUN_OLD_REFLECTED_FROM))
continue;
if ((type >= STUN_REALM) && (type <= STUN_NONCE))
continue;
if (type == STUN_XOR_MAPPED_ADDRESS)
continue;
DBG (" found unknown attribute: 0x%04x (%u bytes)\n", DBG (" found unknown attribute: 0x%04x (%u bytes)\n",
(unsigned)type, (unsigned)alen); (unsigned)type, (unsigned)alen);
list[count++] = type; list[count++] = type;
} }
}
DBG (" %u unknown mandatory attributes\n", count); DBG (" %u unknown mandatory attribute%s\n", count,
(count != 1) ? "s" : "");
return count; return count;
} }
...@@ -44,25 +44,37 @@ ...@@ -44,25 +44,37 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <errno.h> #include <errno.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <pthread.h> #include <pthread.h>
static inline static inline
void stun_set_type (stun_hdr_t *h, stun_class_t c, stun_method_t m) void *stun_setw (uint8_t *ptr, uint16_t value)
{
*ptr++ = value >> 8;
*ptr++ = value & 0xff;
return ptr;
}
static inline
void stun_set_type (uint8_t *h, stun_class_t c, stun_method_t m)
{ {
assert (c < 4); assert (c < 4);
assert (m < (1 << 12)); assert (m < (1 << 12));
uint16_t t = ((c << 7) & 0x0100) | ((c << 4) & 0x0010) h[0] = (c >> 1) | ((m >> 6) & 0x3e);
| ((m << 2) & 0x3e00) | ((m << 1) & 0x00e0) | (m & 0x000f); h[1] = ((c << 4) & 0x10) | ((m << 1) & 0xe0) | (m & 0x0f);
assert (t < (1 << 14)); assert (stun_getw (h) < (1 << 14));
h->msg_type = htons (t); assert (stun_get_class (h) == c);
assert (stun_get_method (h) == m);
} }
void stun_make_transid (stun_transid_t id) static void stun_make_transid (stun_transid_t id)
{ {
static struct static struct
{ {
...@@ -82,23 +94,39 @@ void stun_make_transid (stun_transid_t id) ...@@ -82,23 +94,39 @@ void stun_make_transid (stun_transid_t id)
/** /**
* Initializes a STUN message structure, with no attributes. * Initializes a STUN message buffer, with no attributes.
* @param c STUN message class (host byte order) * @param c STUN message class (host byte order)
* @param m STUN message method (host byte order) * @param m STUN message method (host byte order)
* @param id 12-bytes transaction ID * @param id 12-bytes transaction ID
*/ */
void stun_init (stun_msg_t *msg, stun_class_t c, stun_method_t m, static void stun_init (uint8_t *msg, stun_class_t c, stun_method_t m,
const stun_transid_t id) const stun_transid_t id)
{ {
stun_set_type (&msg->hdr, c, m); static const uint8_t init[8] = { 0, 0, 0, 0, 0x21, 0x12, 0xA4, 0x42 };
msg->hdr.msg_len = 0; memcpy (msg, init, sizeof (init));
msg->hdr.msg_cookie = htonl (STUN_COOKIE); stun_set_type (msg, c, m);
memcpy (msg->hdr.msg_id, id, sizeof (msg->hdr.msg_id));
msg += 8;
if (msg != id)
memcpy (msg, id, 12);
}
/**
* Initializes a STUN request message buffer, with no attributes.
* @param m STUN message method (host byte order)
*/
void stun_init_request (uint8_t *req, stun_method_t m)
{
stun_transid_t id;
stun_make_transid (id);
stun_init (req, STUN_REQUEST, m, id);
} }
/** /**
* Initializes a STUN message structure with no attributes, * Initializes a STUN message buffer with no attributes,
* in response to a given valid STUN request messsage. * in response to a given valid STUN request messsage.
* STUN method and transaction ID are copied from the request message. * STUN method and transaction ID are copied from the request message.
* *
...@@ -107,18 +135,19 @@ void stun_init (stun_msg_t *msg, stun_class_t c, stun_method_t m, ...@@ -107,18 +135,19 @@ void stun_init (stun_msg_t *msg, stun_class_t c, stun_method_t m,
* *
* ans == req is allowed. * ans == req is allowed.
*/ */
void stun_init_response (stun_msg_t *ans, const void *req) void stun_init_response (uint8_t *ans, const uint8_t *req)
{ {
//assert (stun_valid (req)); //assert (stun_valid (req));
assert (stun_get_class (req) == STUN_REQUEST); assert (stun_get_class (req) == STUN_REQUEST);
stun_init (ans, STUN_RESPONSE, stun_get_method (req), stun_init (ans, STUN_RESPONSE, stun_get_method (req), stun_id (req));
((const uint8_t *)req) + 8);
} }
/** /**
* Reserves room for appending an attribute to an unfinished STUN message. * Reserves room for appending an attribute to an unfinished STUN message.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type message type (host byte order) * @param type message type (host byte order)
* @param length attribute payload byte length * @param length attribute payload byte length
* @return a pointer to an unitialized buffer of <length> bytes to * @return a pointer to an unitialized buffer of <length> bytes to
...@@ -127,86 +156,246 @@ void stun_init_response (stun_msg_t *ans, const void *req) ...@@ -127,86 +156,246 @@ void stun_init_response (stun_msg_t *ans, const void *req)
* 32-bits boundary. * 32-bits boundary.
*/ */
static void * static void *
stun_append (stun_msg_t *msg, stun_attr_type_t type, size_t length) stun_append (uint8_t *msg, size_t msize, stun_attr_type_t type, size_t length)
{ {
uint16_t mlen = ntohs (msg->hdr.msg_len); uint16_t mlen = stun_length (msg);
assert (stun_padding (mlen) == 0); assert (stun_padding (mlen) == 0);
if (length > 0xffff) if (msize > STUN_MAXMSG)
return NULL; msize = STUN_MAXMSG;
if ((((size_t)mlen) + 4 + length) > sizeof (msg->buf))
if ((((size_t)mlen) + 24u + length) > msize)
return NULL; return NULL;
stun_attr_hdr_t *a = (stun_attr_hdr_t *)(msg->buf + mlen); assert (length < 0xffff);
a->attr_type = htons (type);
a->attr_len = htons (length); uint8_t *a = msg + 20u + mlen;
a = stun_setw (a, type);
a = stun_setw (a, length);
mlen += 4 + length; mlen += 4 + length;
/* Add padding if needed */ /* Add padding if needed */
memset (msg->buf + mlen, ' ', stun_padding (length)); memset (a + length, ' ', stun_padding (length));
mlen += stun_padding (length); mlen += stun_padding (length);
msg->hdr.msg_len = htons (mlen); stun_setw (msg + 2, mlen);
return a + 1; return a;
} }
#if 0
/** /**
* Appends an attribute consisting of a 32-bits value to a STUN message. * Appends an attribute from memory.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribute type (host byte order) * @param type attribute type (host byte order)
* @param value payload (host byte order) * @param data memory address to copy payload from
* @param len attribute payload length
* @return 0 on success, ENOBUFS on error. * @return 0 on success, ENOBUFS on error.
*/ */
int static int
stun_append32 (stun_msg_t *msg, stun_attr_type_t type, uint32_t value) stun_append_bytes (uint8_t *restrict msg, size_t msize, stun_attr_type_t type,
const void *data, size_t len)
{ {
void *ptr = stun_append (msg, type, sizeof (value)); void *ptr = stun_append (msg, msize, type, len);
if (ptr == NULL) if (ptr == NULL)
return ENOBUFS; return ENOBUFS;
memcpy (ptr, &(uint32_t){ htonl (value) }, sizeof (value)); memcpy (ptr, data, len);
return 0; return 0;
} }
#endif
/** /**
* Appends an attribute from memory. * Appends an empty ("flag") attribute to a STUN message.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribute type (host byte order) * @param type attribute type (host byte order)
* @param data memory address to copy payload from
* @param len attribute payload length
* @return 0 on success, ENOBUFS on error. * @return 0 on success, ENOBUFS on error.
*/ */
static int int stun_append_flag (uint8_t *msg, size_t msize, stun_attr_type_t type)
stun_append_bytes (stun_msg_t *restrict msg, stun_attr_type_t type,
const void *data, size_t len)
{ {
void *ptr = stun_append (msg, type, len); return stun_append_bytes (msg, msize, type, NULL, 0);
if (ptr == NULL) }
return ENOBUFS;
memcpy (ptr, data, len);
return 0; /**
* Appends an attribute consisting of a 32-bits value to a STUN message.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribute type (host byte order)
* @param value payload (host byte order)
* @return 0 on success, ENOBUFS on error.
*/
int
stun_append32 (uint8_t *msg, size_t msize, stun_attr_type_t type,
uint32_t value)
{
value = htonl (value);
return stun_append_bytes (msg, msize, type, &value, 4);
}
/**
* Appends an attribute consisting of a 64-bits value to a STUN message.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribute type (host byte order)
* @param value payload (host byte order)
* @return 0 on success, ENOBUFS on error.
*/
int stun_append64 (uint8_t *msg, size_t msize, stun_attr_type_t type,
uint64_t value)
{
uint32_t tab[2];
tab[0] = htonl ((uint32_t)(value >> 32));
tab[1] = htonl ((uint32_t)value);
return stun_append_bytes (msg, msize, type, tab, 8);
} }
/** /**
* Appends an attribute from a nul-terminated string. * Appends an attribute from a nul-terminated string.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribute type (host byte order) * @param type attribute type (host byte order)
* @param str nul-terminated string * @param str nul-terminated string
* @return 0 on success, ENOBUFS on error. * @return 0 on success, ENOBUFS on error.
*/ */
int
stun_append_string (uint8_t *restrict msg, size_t msize,
stun_attr_type_t type, const char *str)
{
return stun_append_bytes (msg, msize, type, str, strlen (str));
}
/**
* @param code host-byte order error code
* @return a static pointer to a nul-terminated error message string.
*/
static const char *stun_strerror (stun_error_t code)
{
static const struct
{
stun_error_t code;
char phrase[32];
} tab[] =
{
{ STUN_TRY_ALTERNATE, "Try alternate server" },
{ STUN_BAD_REQUEST, "Bad request" },
{ STUN_UNAUTHORIZED, "Authorization required" },
{ 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" },
{ STUN_MISSING_NONCE, "Authentication token missing" },
{ STUN_UNKNOWN_USERNAME, "Unknown user name" },
{ STUN_STALE_NONCE, "Authentication token expired" },
{ STUN_ROLE_CONFLICT, "Role conflict" },
{ STUN_SERVER_ERROR, "Temporary server error" },
{ STUN_GLOBAL_FAILURE, "Unrecoverable failure" },
{ 0, "" }
};
for (unsigned i = 0; tab[i].phrase[0]; i++)
{
if (tab[i].code == code)
return tab[i].phrase;
}
return "Unknown error";
}
/**
* Appends an ERROR-CODE attribute.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param code STUN host-byte order integer error code
* @return 0 on success, or ENOBUFS otherwise
*/
static int static int
stun_append_string (stun_msg_t *restrict msg, stun_attr_type_t type, stun_append_error (uint8_t *restrict msg, size_t msize, stun_error_t code)
const char *str) {
const char *str = stun_strerror (code);
size_t len = strlen (str);
div_t d = div (code, 100);
uint8_t *ptr = stun_append (msg, msize, STUN_ERROR_CODE, 4 + len);
if (ptr == NULL)
return ENOBUFS;
memset (ptr, 0, 2);
assert (d.quot <= 0x7);
ptr[2] = d.quot;
ptr[3] = d.rem;
memcpy (ptr + 4, str, len);
return 0;
}
/**
* Initializes a STUN error response message buffer with an ERROR-CODE
* attribute, in response to a given valid STUN request messsage.
* STUN method and transaction ID are copied from the request message.
*
* @param ans [OUT] STUN message buffer
* @param msize STUN message buffer size
* @param req STUN message to copy method and transaction ID from
* @param err host-byte order STUN integer error code
*
* @return 0 on success, ENOBUFS on error
*
* ans == req is allowed.
*/
int stun_init_error (uint8_t *ans, size_t msize, const uint8_t *req,
stun_error_t err)
{
//assert (stun_valid (req));
stun_init (ans, STUN_ERROR, stun_get_method (req), stun_id (req));
return stun_append_error (ans, msize, err);
}
/**
* Initializes a STUN error response message buffer, in response to a valid
* STUN request messsage with unknown attributes. STUN method, transaction ID
* and unknown attribute IDs are copied from the request message.
*
* @param ans [OUT] STUN message buffer
* @param msize STUN message buffer size
* @param req STUN request message
* @return 0 on success, ENOBUFS otherwise
*
* ans == req is allowed.
*/
int stun_init_error_unknown (uint8_t *ans, size_t msize, const uint8_t *req)
{ {
return stun_append_bytes (msg, type, str, strlen (str)); uint16_t ids[stun_length (req) / 4];
unsigned counter;
//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);
if (stun_init_error (ans, msize, req, STUN_UNKNOWN_ATTRIBUTE))
return ENOBUFS;
for (unsigned i = 0; i < counter; i++)
ids[i] = htons (ids[i]);
return stun_append_bytes (ans, msize, STUN_UNKNOWN_ATTRIBUTES, ids,
counter * 2);
} }
/** /**
* Appends an attribute consisting of a network address to a STUN message. * Appends an attribute consisting of a network address to a STUN message.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribyte type (host byte order) * @param type attribyte type (host byte order)
* @param addr socket address to convert into an attribute * @param addr socket address to convert into an attribute
* @param addrlen byte length of socket address * @param addrlen byte length of socket address
...@@ -215,7 +404,7 @@ stun_append_string (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -215,7 +404,7 @@ stun_append_string (stun_msg_t *restrict msg, stun_attr_type_t type,
* EINVAL if the socket address length is too small w.r.t. the address family. * EINVAL if the socket address length is too small w.r.t. the address family.
*/ */
int int
stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, stun_append_addr (uint8_t *restrict msg, size_t msize, stun_attr_type_t type,
const struct sockaddr *restrict addr, socklen_t addrlen) const struct sockaddr *restrict addr, socklen_t addrlen)
{ {
if (addrlen < sizeof (struct sockaddr)) if (addrlen < sizeof (struct sockaddr))
...@@ -230,9 +419,7 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -230,9 +419,7 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type,
case AF_INET: case AF_INET:
{ {
const struct sockaddr_in *ip4 = (const struct sockaddr_in *)addr; const struct sockaddr_in *ip4 = (const struct sockaddr_in *)addr;
if (addrlen < sizeof (*ip4)) assert (addrlen >= sizeof (*ip4));
return EINVAL;
family = 1; family = 1;
port = ip4->sin_port; port = ip4->sin_port;
alen = 4; alen = 4;
...@@ -257,7 +444,7 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -257,7 +444,7 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type,
return EAFNOSUPPORT; return EAFNOSUPPORT;
} }
uint8_t *ptr = stun_append (msg, type, 4 + alen); uint8_t *ptr = stun_append (msg, msize, type, 4 + alen);
if (ptr == NULL) if (ptr == NULL)
return ENOBUFS; return ENOBUFS;
...@@ -271,6 +458,8 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -271,6 +458,8 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type,
/** /**
* Appends an attribute consisting of a xor'ed network address. * Appends an attribute consisting of a xor'ed network address.
* @param msg STUN message buffer
* @param msize STUN message buffer size
* @param type attribyte type (host byte order) * @param type attribyte type (host byte order)
* @param addr socket address to convert into an attribute * @param addr socket address to convert into an attribute
* @param addrlen byte length of socket address * @param addrlen byte length of socket address
...@@ -278,7 +467,8 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -278,7 +467,8 @@ stun_append_addr (stun_msg_t *restrict msg, stun_attr_type_t type,
* EAFNOSUPPORT is the socket address family is not supported, * EAFNOSUPPORT is the socket address family is not supported,
* EINVAL if the socket address length is too small w.r.t. the address family. * EINVAL if the socket address length is too small w.r.t. the address family.
*/ */
int stun_append_xor_addr (stun_msg_t *restrict msg, stun_attr_type_t type, int stun_append_xor_addr (uint8_t *restrict msg, size_t msize,
stun_attr_type_t type,
const struct sockaddr *restrict addr, const struct sockaddr *restrict addr,
socklen_t addrlen) socklen_t addrlen)
{ {
...@@ -294,85 +484,90 @@ int stun_append_xor_addr (stun_msg_t *restrict msg, stun_attr_type_t type, ...@@ -294,85 +484,90 @@ int stun_append_xor_addr (stun_msg_t *restrict msg, stun_attr_type_t type,
if (val) if (val)
return val; return val;
return stun_append_addr (msg, type, &xor.addr, addrlen); return stun_append_addr (msg, msize, type, &xor.addr, addrlen);
} }
static size_t static size_t
stun_finish_long (stun_msg_t *restrict msg, stun_finish_long (uint8_t *msg, size_t *restrict plen,
const char *realm, const char *username, const char *realm, const char *username,
const void *key, size_t keylen, const void *key, size_t keylen,
const void *nonce, size_t noncelen) const void *nonce, size_t noncelen)
{ {
void *sha = NULL; assert (plen != NULL);
size_t len = *plen;
uint8_t *sha = NULL;
int val = ENOBUFS;
if (realm != NULL) if (realm != NULL)
{ {
int val = stun_append_string (msg, STUN_REALM, realm); val = stun_append_string (msg, len, STUN_REALM, realm);
if (val) if (val)
return val; return val;
} }
if (username != NULL) if (username != NULL)
{ {
int val = stun_append_string (msg, STUN_USERNAME, username); val = stun_append_string (msg, len, STUN_USERNAME, username);
if (val) if (val)
return val; return val;
} }
if (nonce != NULL) if (nonce != NULL)
{ {
int val = stun_append_bytes (msg, STUN_NONCE, nonce, noncelen); val = stun_append_bytes (msg, len, STUN_NONCE, nonce, noncelen);
if (val) if (val)
return val; return val;
} }
if (key != NULL) if (key != NULL)
{ {
sha = stun_append (msg, STUN_MESSAGE_INTEGRITY, 20); sha = stun_append (msg, len, STUN_MESSAGE_INTEGRITY, 20);
if (sha == NULL) if (sha == NULL)
return ENOBUFS; return ENOBUFS;
} }
void *crc = stun_append (msg, STUN_FINGERPRINT, 4); void *crc = stun_append (msg, len, STUN_FINGERPRINT, 4);
if (crc == NULL) if (crc == NULL)
return ENOBUFS; return ENOBUFS;
if (sha != NULL) if (sha != NULL)
{
stun_sha1 (msg, sha, key, keylen); stun_sha1 (msg, sha, key, keylen);
#if 0
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]);
DBG ("\n");
#endif
}
uint32_t fpr = htonl (stun_fingerprint (&msg->hdr)); uint32_t fpr = htonl (stun_fingerprint (msg));
memcpy (crc, &fpr, sizeof (fpr)); memcpy (crc, &fpr, sizeof (fpr));
return sizeof (msg->hdr) + ntohs (msg->hdr.msg_len);
*plen = 20u + stun_length (msg);
return 0;
} }
/** size_t stun_finish_short (uint8_t *msg, size_t *restrict plen,
* Finishes a STUN message structure before sending it, and
* authenticates it with short-term credentials.
* No further attributes shall be added.
*
* @return length of the message in bytes, or 0 on error (and sets errno).
*/
size_t stun_finish_short (stun_msg_t *restrict msg,
const char *username, const char *password, const char *username, const char *password,
const void *nonce, size_t noncelen) const void *nonce, size_t noncelen)
{ {
size_t passlen = password ? strlen (password) : 0; size_t passlen = (password != NULL) ? strlen (password) : 0;
return stun_finish_long (msg, NULL, username, password, passlen, return stun_finish_long (msg, plen, NULL, username, password, passlen,
nonce, noncelen); nonce, noncelen);
} }
/** size_t stun_finish (uint8_t *msg, size_t *restrict plen)
* Finishes a STUN message structure before sending it.
* No further attributes shall be added.
*
* @return length of the message in bytes, or 0 on error (and sets errno).
*/
size_t stun_finish (stun_msg_t *m)
{ {
return stun_finish_short (m, NULL, NULL, NULL, 0); return stun_finish_short (msg, plen, NULL, NULL, NULL, 0);
} }
...@@ -6,17 +6,12 @@ ...@@ -6,17 +6,12 @@
# Licensed under MPL 1.1/LGPL 2.1. See file COPYING. # Licensed under MPL 1.1/LGPL 2.1. See file COPYING.
include $(top_srcdir)/common.mk include $(top_srcdir)/common.mk
AM_CPPFLAGS = -I$(srcdir)/.. AM_CPPFLAGS = -I$(top_srcdir)
LDADD = ../libstun.la
check_PROGRAMS = test-crc32 test-parse test-format test-bind check_PROGRAMS = test-crc32 test-parse test-format \
test_crc32_SOURCES = crc32.c test-bind test-bindserv test-conncheck
test_parse_SOURCES = parse.c
test_parse_LDADD = ../libstun.la
test_format_SOURCES = format.c
test_format_LDADD = ../libstun.la
test_bind_SOURCES = bind.c
test_bind_LDADD = ../libstun.la
dist_check_SCRIPTS = check-bind.sh dist_check_SCRIPTS = check-bind.sh
TESTS = test-crc32 test-parse test-format check-bind.sh TESTS = $(check_PROGRAMS) $(dist_check_SCRIPTS)
#! /bin/sh #! /bin/sh
LOG=test-bind.log STUNC=../stunbdc
rm -f "$LOG" STUND=../stund
set -xe
# Dummy command line parsing tests
$STUNC -h
$STUNC -V
! $STUNC server port dummy
# Timeout tests
! $STUNC -4 127.0.0.1 1
! $STUNC -6 ::1 1
# Real tests
# Start the STUN test daemon if needed # Start the STUN test daemon if needed
../stund -4 & rm -f stund-*.err stunc-*.log
../stund -6 &
exit 77
# FIXME: kill daemons properly
# FIXME: use custom port number
( $STUND -4 || echo ABORT > stund-IPv4.err ) &
( $STUND -6 || echo ABORT > stund-IPv6.err ) &
# Run the test client # Run the test client
{ $STUNC -4 > stunc-IPv4.log || test -f stund-IPv4.err
./test-bind $STUNC -6 > stunc-IPv6.log || test -f stund-IPv6.err
echo "test-bind returned $?"
} | tee "$LOG"
# Terminate the test daemon # Terminate the test daemon
kill -INT %1 kill -INT %1 2>/dev/null || true
kill -INT %2 kill -INT %2 2>/dev/null || true
if ! grep "test-bind returned 0" "$LOG"; then # Check client results
echo "test-bind failed" >&2 if test -f stund-IPv4.err; then exit 77; fi
exit 1 grep -e "^Mapped address: 127.0.0.1" stunc-IPv4.log || exit 4
fi
if test -f stund-IPv6.err; then exit 77; fi
for a in 127.0.0.1 ::1; do grep -e "^Mapped address: ::1" stunc-IPv6.log || exit 6
for t in Auto UDP; do
if ! grep -e "^$t discovery *: $a port " "$LOG"; then
echo "Unexpected mapping from test-bind" >&2
exit 1
fi
done
done
rm -f "$LOG"
exit 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include "stun/bind.h"
#include "stun/stun-msg.h"
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#undef NDEBUG /* ensure assertions are built-in */
#include <assert.h>
static int listen_dgram (void)
{
struct addrinfo hints, *res;
memset (&hints, 0, sizeof (hints));
hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo (NULL, "0", &hints, &res))
return -1;
int val = -1;
for (const struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
{
int fd = socket (ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (fd == -1)
continue;
if (bind (fd, ptr->ai_addr, ptr->ai_addrlen))
{
close (fd);
continue;
}
val = fd;
break;
}
freeaddrinfo (res);
return val;
}
/** Incorrect socket family test */
static void bad_family (void)
{
struct sockaddr addr, dummy;
memset (&addr, 0, sizeof (addr));
addr.sa_family = AF_UNSPEC;
#ifdef HAVE_SA_LEN
addr.sa_len = sizeof (addr);
#endif
int val = stun_bind_run (-1, &addr, sizeof (addr),
&dummy, &(socklen_t){ sizeof (dummy) });
assert (val != 0);
}
/** Too small socket address test */
static void small_srv_addr (void)
{
struct sockaddr addr, dummy;
memset (&addr, 0, sizeof (addr));
addr.sa_family = AF_INET;
#ifdef HAVE_SA_LEN
addr.sa_len = sizeof (addr);
#endif
int val = stun_bind_run (-1, &addr, 1,
&dummy, &(socklen_t){ sizeof (dummy) });
assert (val == EINVAL);
}
/** Too big socket address test */
static void big_srv_addr (void)
{
uint8_t buf[sizeof (struct sockaddr_storage) + 16];
struct sockaddr dummy;
int fd = socket (AF_INET, SOCK_DGRAM, 0);
assert (fd != -1);
memset (buf, 0, sizeof (buf));
int val = stun_bind_run (fd, (struct sockaddr *)buf, sizeof (buf),
&dummy, &(socklen_t){ sizeof (dummy) });
assert (val == ENOBUFS);
close (fd);
}
/** Timeout test */
static void timeout (void)
{
int val;
struct sockaddr_storage srv;
struct sockaddr dummy;
socklen_t srvlen = sizeof (srv);
/* Allocate a local UDP port, so we are 100% sure nobody responds there */
int servfd = listen_dgram ();
assert (servfd != -1);
val = getsockname (servfd, (struct sockaddr *)&srv, &srvlen);
assert (val == 0);
val = stun_bind_run (-1, (struct sockaddr *)&srv, srvlen,
&dummy, &(socklen_t){ sizeof (dummy) });
assert (val == ETIMEDOUT);
close (servfd);
}
/** Malformed responses test */
static void bad_responses (void)
{
ssize_t val;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof (addr);
stun_bind_t *ctx;
uint8_t buf[1000];
/* Allocate a local UDP port */
int servfd = listen_dgram ();
assert (servfd != -1);
val = getsockname (servfd, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
val = stun_bind_start (&ctx, -1, (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);
assert (val == 0);
val = connect (servfd, (struct sockaddr *)&addr, addrlen);
assert (val == 0);
/* Send crap response */
send (servfd, "foobar", 6, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == EAGAIN);
/* Send non-matching message (request instead of response) */
val = recv (servfd, buf, 1000, MSG_DONTWAIT);
assert (val >= 0);
send (servfd, buf, val, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == EAGAIN);
stun_bind_cancel (ctx);
close (servfd);
}
/** Various responses test */
static void responses (void)
{
ssize_t val;
size_t len;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof (addr);
stun_bind_t *ctx;
stun_msg_t buf;
/* Allocate a local UDP port for server */
int servfd = listen_dgram ();
assert (servfd != -1);
val = getsockname (servfd, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
/* Allocate a client socket and connect to server */
int fd = socket (addr.ss_family, SOCK_DGRAM, 0);
assert (fd != -1);
val = connect (fd, (struct sockaddr *)&addr, addrlen);
assert (val == 0);
/* Send to/receive from our client instance only */
val = getsockname (fd, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
val = connect (servfd, (struct sockaddr *)&addr, addrlen);
assert (val == 0);
/* Send error response */
val = stun_bind_start (&ctx, fd, NULL, 0);
assert (val == 0);
val = recv (servfd, buf, 1000, MSG_DONTWAIT);
assert (val >= 0);
stun_init_error (buf, sizeof (buf), buf, STUN_GLOBAL_FAILURE);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
send (servfd, buf, len, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == ECONNREFUSED);
/* Send response with an unknown attribute */
val = stun_bind_start (&ctx, fd, NULL, 0);
assert (val == 0);
val = recv (servfd, buf, 1000, MSG_DONTWAIT);
assert (val >= 0);
stun_init_response (buf, buf);
val = stun_append_string (buf, sizeof (buf), 0x6000,
"This is an unknown attribute!");
assert (val == 0);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
send (servfd, buf, len, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == EPROTO);
/* Send response with a no mapped address at all */
val = stun_bind_start (&ctx, fd, NULL, 0);
assert (val == 0);
val = recv (servfd, buf, 1000, MSG_DONTWAIT);
assert (val >= 0);
stun_init_response (buf, buf);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
send (servfd, buf, len, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == ENOENT);
/* Send old-style response */
val = stun_bind_start (&ctx, fd, NULL, 0);
assert (val == 0);
val = recv (servfd, buf, 1000, MSG_DONTWAIT);
assert (val >= 0);
stun_init_response (buf, buf);
val = stun_append_addr (buf, sizeof (buf), STUN_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen);
assert (val == 0);
len = sizeof (buf);
val = stun_finish (buf, &len);
assert (val == 0);
send (servfd, buf, len, 0);
val = stun_bind_resume (ctx, (struct sockaddr *)&addr, &addrlen);
assert (val == 0);
/* End */
close (servfd);
val = close (fd);
assert (val == 0);
}
static void test (void (*func) (void), const char *name)
{
alarm (10);
printf ("%s test... ", name);
func ();
puts ("OK");
}
int main (void)
{
test (bad_family, "Bad socket family");
test (small_srv_addr, "Too small server address");
test (big_srv_addr, "Too big server address");
test (timeout, "Binding discovery timeout");
test (bad_responses, "Bad responses");
test (responses, "Error responses");
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include "stun/bind.h"
#include "stun/stun-msg.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#undef NDEBUG /* ensure assertions are built-in */
#include <assert.h>
int main (void)
{
struct sockaddr_in ip4;
stun_msg_t buf;
ssize_t val;
size_t len;
memset (&ip4, 0, sizeof (ip4));
ip4.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
ip4.sin_len = sizeof (addr);
#endif
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));
/* Incorrect message class */
stun_init_request (buf, STUN_BINDING);
stun_init_response (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);
/* Non-multiplexed message */
static const uint8_t req[] =
"\x00\x01" "\x00\x00"
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F";
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);
assert (len > 0);
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include "stun/conncheck.h"
#include "stun/stun-msg.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#undef NDEBUG /* ensure assertions are built-in */
#include <assert.h>
int main (void)
{
struct sockaddr_in ip4;
stun_msg_t req, resp;
ssize_t val;
size_t len;
const uint64_t tie = 0x8000000000000000LL;
static const char name[] = "admin", pass[] = "secret";
bool control = false;
memset (&ip4, 0, sizeof (ip4));
ip4.sin_family = AF_INET;
#ifdef HAVE_SA_LEN
ip4.sin_len = sizeof (addr);
#endif
ip4.sin_port = htons (12345);
ip4.sin_addr.s_addr = htonl (0x7f000001);
/* Unauthenticated message */
stun_init_request (req, STUN_BINDING);
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 == EPERM);
assert (len > 0);
// FIXME: check error code
/* No username */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
val = stun_finish_short (req, &len, NULL, pass, NULL, 0);
assert (val == 0);
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EPERM);
assert (len > 0);
/* Good message */
stun_init_request (req, STUN_BINDING);
len = sizeof (req);
val = stun_finish_short (req, &len, name, pass, NULL, 0);
assert (val == 0);
len = sizeof (resp);
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == 0);
assert (len > 0);
/* Bad fingerprint */
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), "bad", &control, tie);
assert (val == EPERM);
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);
assert (val == 0);
len = sizeof (resp);
control = true;
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == EACCES);
assert (len > 0);
assert (control == false);
/* 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);
assert (val == 0);
len = sizeof (resp);
control = false;
val = stun_conncheck_reply (resp, &len, req, (struct sockaddr *)&ip4,
sizeof (ip4), pass, &control, tie);
assert (val == 0);
assert (len > 0);
assert (control == false);
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "../crc32.c"
static void test (const void *in, size_t n, uint32_t out)
{
static unsigned num = 0;
num++;
if (crc32 (in, n) != out)
{
fprintf (stderr, "Test %u failed: %08x instead of %08x\n",
num, crc32 (in, n), out);
exit (1);
}
}
int main (void)
{
test (NULL, 0, 0);
test (&(uint32_t ){ 0 }, 0, 0);
test ("foo", 3, 0x8c736521);
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include "stun/stun-msg.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
static void fatal (const char *msg, ...)
{
va_list ap;
va_start (ap, msg);
vfprintf (stderr, msg, ap);
va_end (ap);
fputc ('\n', stderr);
exit (1);
}
static void
dynamic_check (const uint8_t *msg, size_t len)
{
size_t len2 = stun_validate (msg, len);
if ((len != len2) || (len2 & 3))
fatal ("Invalid message (%u, %u)\n",
(unsigned)len, (unsigned)len2);
if (!stun_demux (msg))
fatal ("Invalid message multiplexing");
printf ("Built message of %u bytes\n", (unsigned)len);
}
static size_t
finish_check (uint8_t *msg)
{
stun_msg_t mshort;
size_t len = sizeof (stun_msg_t);
memcpy (mshort, msg, sizeof (mshort));
if (stun_finish (msg, &len))
fatal ("Cannot finish message");
dynamic_check (msg, len);
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))
fatal ("Cannot finish message with short-term creds");
dynamic_check (mshort, len);
if (stun_verify_password (mshort, "admin") != 0)
fatal ("Valid HMAC test failed");
return len;
}
static void
check_af (const char *name, int family, socklen_t addrlen)
{
struct sockaddr_storage addr;
stun_msg_t msg;
assert (addrlen <= sizeof (addr));
memset (&addr, 0, sizeof (addr));
stun_init_request (msg, STUN_BINDING);
if (stun_append_addr (msg, sizeof (msg), STUN_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen) != EAFNOSUPPORT)
fatal ("Unknown address family test failed");
if (stun_append_xor_addr (msg, sizeof (msg), STUN_XOR_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen) != EAFNOSUPPORT)
fatal ("Unknown address family xor test failed");
addr.ss_family = family;
if (stun_append_addr (msg, sizeof (msg), STUN_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen - 1) != EINVAL)
fatal ("Too small %s sockaddr test failed", name);
if (stun_append_xor_addr (msg, sizeof (msg), STUN_XOR_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen - 1) != EINVAL)
fatal ("Too small %s sockaddr xor test failed", name);
if (stun_append_addr (msg, sizeof (msg), STUN_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen))
fatal ("%s sockaddr test failed", name);
if (stun_append_xor_addr (msg, sizeof (msg), STUN_XOR_MAPPED_ADDRESS,
(struct sockaddr *)&addr, addrlen))
fatal ("%s sockaddr xor test failed", name);
}
int main (void)
{
uint8_t msg[STUN_MAXMSG + 8];
size_t len;
/* Request formatting test */
stun_init_request (msg, STUN_BINDING);
finish_check (msg);
if (memcmp (msg, "\x00\x01", 2))
fatal ("Request formatting test failed");
/* Response formatting test */
stun_init_response (msg, msg);
finish_check (msg);
if (memcmp (msg, "\x01\x01", 2))
fatal ("Response formatting test failed");
/* Error formatting test */
if (stun_init_error (msg, sizeof (msg), msg, 400))
fatal ("Error initialization test failed");
finish_check (msg);
if (memcmp (msg, "\x01\x11", 2))
fatal ("Error formatting test failed");
/* Unknown error formatting test */
if (stun_init_error (msg, sizeof (msg), msg, 666))
fatal ("Unknown error initialization test failed");
finish_check (msg);
if (memcmp (msg, "\x01\x11", 2))
fatal ("Unknown error formatting test failed");
/* Overflow tests */
stun_init_request (msg, STUN_BINDING);
for (unsigned i = 0;
stun_append_flag (msg, sizeof (msg), 0xffff) != ENOBUFS;
i++)
{
if ((i << 2) > 0xffff)
fatal ("Overflow protection test failed");
}
if (stun_append32 (msg, sizeof (msg), 0xffff, 0x12345678) != ENOBUFS)
fatal ("Double-word overflow test failed");
if (stun_append64 (msg, sizeof (msg), 0xffff,
0x123456789abcdef0) != ENOBUFS)
fatal ("Quad-word overflow test failed");
if (stun_append_string (msg, sizeof (msg), 0xffff, "foobar") != ENOBUFS)
fatal ("String overflow test failed");
struct sockaddr addr;
memset (&addr, 0, sizeof (addr));
addr.sa_family = AF_INET;
#ifdef HAVE_SA_LEN
addr.sa_len = sizeof (addr);
#endif
if (stun_append_xor_addr (msg, sizeof (msg), 0xffff, &addr,
sizeof (addr)) != ENOBUFS)
fatal ("Address overflow test failed");
len = sizeof (msg);
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)
fatal ("Message integrity overflow test failed");
len = sizeof (msg);
if (stun_finish_short (msg, &len, "login", "secret", NULL, 0) != ENOBUFS)
fatal ("Username overflow test failed");
len = sizeof (msg);
if (stun_finish_short (msg, &len, NULL, "secret", "foobar", 6) != ENOBUFS)
fatal ("Nonce overflow test failed");
/* Address attributes tests */
check_af ("IPv4", AF_INET, sizeof (struct sockaddr_in));
#ifdef AF_INET6
check_af ("IPv6", AF_INET6, sizeof (struct sockaddr_in6));
#endif
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include "stun/stun-msg.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
static void fatal (const char *msg, ...)
{
va_list ap;
va_start (ap, msg);
vfprintf (stderr, msg, ap);
va_end (ap);
fputc ('\n', stderr);
exit (1);
}
static void validate (const uint8_t *msg, unsigned len)
{
unsigned i = 0;
do
{
size_t vlen = stun_validate (msg, i);
if ((vlen & 3) || (vlen != ((i >= len) * len)))
fatal ("%u/%u short message test failed", i, len);
}
while (i++ < (len + 4));
}
/* Tests for generic message validation routines */
static void test_message (void)
{
static const uint8_t simple_resp[] =
"\x15\x55\x00\x00"
"\x21\x12\xA4\x42" // cookie
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\xaa\xbb\xcc\xdd"; //extra garbage
static const uint8_t old_ind[] =
"\x14\x55\x00\x00"
"\xfe\xdc\xba\x98" // NO cookie
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"; //extra garbage
static const uint8_t fpr_resp[] =
"\x15\x55\x00\x10"
"\x21\x12\xA4\x42" // cookie
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\x00\x06\x00\x04" // dummy USERNAME header
"\x41\x42\x43\x44"
"\x80\x28\x00\x04" // FINGERPRINT header
"\xdc\x8d\xa7\x74" // CRC32
"\xcc\xdd\xee\xff"; // extra garbage
static const uint8_t bad1[32] =
"\x15\x55\x00\x08"
"\x21\x12\xA4\x42" // cookie
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\x00\x06\x00\x05" // too big attribute for message
"\x11\x22\x33\x44"
"\x55\x66\x77\x88";
static const uint8_t bad2[24] =
"\x15\x55\x00\x05" // invalid message length
"\x21\x12\xA4\x42"
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\x00\x06\x00\x01";
static const uint8_t bad3[27] =
"\x15\x55\x00\x08"
"\x21\x12\xA4\x42"
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\x00\x06\x00\x03"
"\x11\x22\x33"; // missing padding
static const uint8_t bad_crc[] =
"\x15\x55\x00\x08"
"\x21\x12\xA4\x42"
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
"\x80\x28\x00\x04" // FINGERPRINT header
"\x04\x91\xcd\x78"; // CRC32
static uint8_t bad_crc_offset[] =
"\x15\x55\x00\x10"
"\x21\x12\xA4\x42"
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x20\x67\xc4\x09"
"\x80\x28\x00\x04" // FINGERPRINT header
"\x00\x00\x00\x00"
"\x00\x06\x00\x04"
"\x41\x42\x43\x44";
if (stun_validate (NULL, 0) != 0)
fatal ("0 bytes test failed");
if (stun_validate ((uint8_t *)"\xf0", 1) >= 0)
fatal ("1 byte test failed");
validate (simple_resp, 20);
validate (old_ind, 20);
validate (fpr_resp, 36);
if (stun_demux (simple_resp))
fatal ("Missing CRC test failed");
if (stun_demux (old_ind))
fatal ("Missing cookie test failed");
if (!stun_demux (fpr_resp))
fatal ("Good CRC test failed");
if (stun_demux (bad_crc))
fatal ("Bad CRC test failed");
if (stun_demux (bad_crc_offset))
fatal ("Bad CRC offset test failed");
if (stun_validate (bad1, sizeof (bad1)) >= 0)
fatal ("Badness 1 test failed");
if (stun_validate (bad2, sizeof (bad2)) >= 0)
fatal ("Badness 2 test failed");
if (stun_validate (bad3, sizeof (bad3)) != 0)
fatal ("Badness 3 test failed");
if (stun_get_class (simple_resp) != 3)
fatal ("Class test failed");
if (stun_get_method (simple_resp) != 0x525)
fatal ("Method test failed");
}
/* Tests for message attribute parsing */
static void test_attribute (void)
{
uint8_t acme[] =
"\x15\x55\x00\x4c" // <-- update message length if needed!!
"\x21\x12\xA4\x42" // cookie
"\x76\x54\x32\x10"
"\xfe\xdc\xba\x98"
"\x76\x54\x32\x10"
/* FF01: empty */
"\xff\x01\x00\x00"
/* FF02: address of unknown family, 32-bits */
"\xff\x02\x00\x04"
"\x41\x42\x43\x44"
/* FF03: too short IPv6 address */
"\xff\x03\x00\x06"
"\x00\x02\x12\x34"
"\x20\x01\x0d\xb8"
/* FF04: valid IPv4 address, 64-bits */
"\xff\x04\x00\x08"
"\x00\x01\x12\x34"
"\xc0\x00\x02\x01"
/* FF05: too long IPv4 address */
"\xff\x05\x00\x0A"
"\x00\x01\x12\x34"
"\xc0\x00\x02\x01"
"\x66\x60\x00\x00"
/* MESSAGE-INTEGRITY attribute */
"\x00\x08\x00\x14"
"\xd2\x0c\x85\xcd"
"\x43\x3b\xec\x9e"
"\x4d\x84\x2d\x87"
"\x82\x80\x5b\x3b"
"\xd5\x54\xd8\xa8"
;
struct sockaddr addr;
socklen_t addrlen;
uint32_t dword;
uint64_t qword;
char str[5];
printf ("Attribute test message length: %u\n", sizeof (acme));
if (stun_validate (acme, sizeof (acme)) <= 0)
fatal ("Attributes tests message broken");
if (stun_present (acme, 0xff00))
fatal ("Absent attribute test failed");
if (!stun_present (acme, 0xff01))
fatal ("Present attribute test failed");
if (stun_find_flag (acme, 0xff00) != ENOENT)
fatal ("Absent flag test failed");
if (stun_find_flag (acme, 0xff01) != 0)
fatal ("Flag test failed");
if (stun_find_flag (acme, 0xff02) != EINVAL)
fatal ("Too big flag test failed");
if (stun_find32 (acme, 0xff00, &dword) != ENOENT)
fatal ("Absent dword test failed");
if (stun_find32 (acme, 0xff01, &dword) != EINVAL)
fatal ("Bad dword test failed");
if (stun_find32 (acme, 0xff02, &dword) != 0)
fatal ("Double-word test failed");
if (stun_find64 (acme, 0xff00, &qword) != ENOENT)
fatal ("Absent qword test failed");
if (stun_find64 (acme, 0xff01, &qword) != EINVAL)
fatal ("Bad qword test failed");
if (stun_find64 (acme, 0xff04, &qword) !=0)
fatal ("Quad-word test failed");
if (stun_find_string (acme, 0xff00, str, sizeof (str)) != -1)
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)
|| strcmp (str, "ABCD"))
fatal ("String test failed");
addrlen = sizeof (addr);
if (stun_find_addr (acme, 0xff01, &addr, &addrlen) != EINVAL)
fatal ("Too short addres test failed");
addrlen = sizeof (addr);
if (stun_find_addr (acme, 0xff02, &addr, &addrlen) != EAFNOSUPPORT)
fatal ("Unknown address family test failed");
addrlen = sizeof (addr);
if (stun_find_addr (acme, 0xff03, &addr, &addrlen) != EINVAL)
fatal ("Too short IPv6 address test failed");
addrlen = sizeof (addr);
if (stun_find_addr (acme, 0xff04, &addr, &addrlen) != 0)
fatal ("IPv4 address test failed");
addrlen = sizeof (addr);
if (stun_find_addr (acme, 0xff05, &addr, &addrlen) != EINVAL)
fatal ("Too big IPv4 address test failed");
if (stun_verify_key (acme, "good_guy", 8) != 0)
fatal ("Good secret HMAC test failed");
if (stun_verify_key (acme, "bad__guy", 8) != EPERM)
fatal ("Bad secret HMAC test failed");
}
int main (void)
{
test_message ();
test_attribute ();
return 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.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "timer.h"
#include <stdlib.h> /* div() */
#include <time.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
/**
* Initial STUN timeout (milliseconds). The spec says it should be 100ms,
* but that's way too short for most types of wireless Internet access.
*/
#define STUN_INIT_TIMEOUT 600
#define STUN_END_TIMEOUT 4800
/**
* Clock used throughout the STUN code.
* STUN requires a monotonic 1kHz clock to operate properly.
*/
static void stun_gettime (struct timespec *restrict now)
{
#if (_POSIX_MONOTONIC_CLOCK - 0) >= 0
if (clock_gettime (CLOCK_MONOTONIC, now))
#endif
{ // fallback to wall clock
struct timeval tv;
gettimeofday (&tv, NULL);
now->tv_sec = tv.tv_sec;
now->tv_nsec = tv.tv_usec * 1000;
}
}
static inline void add_delay (struct timespec *ts, unsigned delay)
{
div_t d = div (delay, 1000);
ts->tv_sec += d.quot;
ts->tv_nsec += d.rem * 1000000;
while (ts->tv_nsec > 1000000000)
{
ts->tv_nsec -= 1000000000;
ts->tv_sec++;
}
}
/**
* Starts a STUN transaction retransmission timer.
* @param timer structure for internal timer state
*/
void stun_timer_start (stun_timer_t *timer)
{
stun_gettime (&timer->deadline);
add_delay (&timer->deadline, timer->delay = STUN_INIT_TIMEOUT);
}
unsigned stun_timer_remainder (const stun_timer_t *timer)
{
unsigned delay;
struct timespec now;
stun_gettime (&now);
if (now.tv_sec > timer->deadline.tv_sec)
return 0;
delay = timer->deadline.tv_sec - now.tv_sec;
if ((delay == 0) && (now.tv_nsec >= timer->deadline.tv_nsec))
return 0;
delay *= 1000;
delay += ((signed)(timer->deadline.tv_nsec - now.tv_nsec)) / 1000000;
return delay;
}
/**
* Updates a STUN transaction retransmission timer.
* @param timer internal timer state
* @return -1 if the transaction timed out,
* 0 if the transaction should be retransmitted,
* otherwise milliseconds left until next time out or retransmit.
*/
int stun_timer_refresh (stun_timer_t *timer)
{
unsigned delay = stun_timer_remainder (timer);
if (delay == 0)
{
if (timer->delay >= STUN_END_TIMEOUT)
return -1;
add_delay (&timer->deadline, timer->delay *= 2);
}
return delay;
}
/*
* 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 STUN_TIMER_H
# define STUN_TIMER_H 1
# include <sys/types.h>
# include <time.h>
typedef struct stun_timer_s
{
struct timespec deadline;
unsigned delay;
} stun_timer_t;
# ifdef __cplusplus
extern "C" {
# endif
void stun_timer_start (stun_timer_t *timer);
int stun_timer_refresh (stun_timer_t *timer);
unsigned stun_timer_remainder (const stun_timer_t *timer);
# ifdef __cplusplus
}
# endif
#endif /* !STUN_TIMER_H */
/*
* 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 <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include "stun-msg.h"
#include "trans.h"
/**
* Initializes a new STUN request transaction
*/
int stun_trans_init (stun_trans_t *restrict tr, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen)
{
if (fd == -1)
{
if (srvlen < sizeof (struct sockaddr))
return EINVAL;
fd = socket (srv->sa_family, SOCK_DGRAM, 0);
if (fd == -1)
return errno;
#ifdef FD_CLOEXEC
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
#endif
#ifdef O_NONBLOCK
fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | O_NONBLOCK);
#endif
if (connect (fd, srv, srvlen))
{
close (fd);
return errno;
}
tr->ownfd = true;
tr->srvlen = 0;
}
else
{
if (srvlen > sizeof (tr->srv))
return ENOBUFS;
tr->ownfd = false;
memcpy (&tr->srv, srv, tr->srvlen = srvlen);
}
tr->fd = fd;
return 0;
}
/**
* Sends a STUN request
*/
static int
stun_trans_send (stun_trans_t *tr)
{
/* FIXME: support for TCP */
ssize_t val;
if (tr->srvlen > 0)
val = sendto (tr->fd, tr->msg, tr->msglen, 0,
(struct sockaddr *)&tr->srv, tr->srvlen);
else
val = send (tr->fd, tr->msg, tr->msglen, 0);
if (val == -1)
return errno;
if (val < (ssize_t)tr->msglen)
return EMSGSIZE;
return 0;
}
void stun_trans_deinit (stun_trans_t *tr)
{
int saved = errno;
if (tr->ownfd)
close (tr->fd);
#ifndef NDEBUG
tr->fd = -1;
#endif
errno = saved;
}
int stun_trans_start (stun_trans_t *tr)
{
int val = stun_trans_send (tr);
if (val)
return val;
stun_timer_start (&tr->timer);
DBG ("STUN transaction @%p started (timeout: %ums)\n", tr,
stun_trans_timeout (tr));
return 0;
}
/**
* This is meant to integrate with I/O pooling loops and event frameworks.
*
* @return recommended maximum delay (in milliseconds) to wait for a
* response.
*/
unsigned stun_trans_timeout (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->fd != -1);
return stun_timer_remainder (&tr->timer);
}
/**
* 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_trans_fd (const stun_trans_t *tr)
{
assert (tr != NULL);
assert (tr->fd != -1);
return tr->fd;
}
int stun_trans_tick (stun_trans_t *tr)
{
switch (stun_timer_refresh (&tr->timer))
{
case -1:
DBG ("STUN transaction @%p failed: time out.\n", tr);
return ETIMEDOUT; // fatal error!
case 0:
stun_trans_send (tr);
DBG ("STUN transaction @%p retransmitted (timeout: %ums).\n", tr,
stun_trans_timeout (tr));
}
return EAGAIN;
}
/*
* 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 STUN_TRANS_H
# define STUN_TRANS_H 1
# include <sys/types.h>
# include <sys/socket.h>
# include <stdbool.h>
# include "timer.h"
typedef struct stun_trans_s
{
stun_timer_t timer;
size_t msglen;
stun_msg_t msg;
bool ownfd;
int fd;
socklen_t srvlen;
struct sockaddr_storage srv;
} stun_trans_t;
# ifdef __cplusplus
extern "C" {
# endif
int stun_trans_init (stun_trans_t *restrict tr, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen);
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);
unsigned stun_trans_timeout (const stun_trans_t *tr);
int stun_trans_fd (const stun_trans_t *tr);
# ifdef __cplusplus
}
# endif
#endif /* !STUN_TRANS_H */
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