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 @@
# Licensed under MPL 1.1/LGPL 2.1. See file COPYING.
SUBDIRS = . tests
DIST_SUBDIRS = tests
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)
......@@ -18,14 +20,16 @@ noinst_LTLIBRARIES = libstun.la
libstun_la_SOURCES = stun.h stun.c \
stun-msg.h stunsend.c stunrecv.c crc32.c hmac.c \
bind.h bind.c
libstun_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS)
timer.h timer.c trans.h trans.c \
bind.h conncheck.h bind.c bindserv.c
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
stund_LDADD = libstun.la -lpthread
stunbdc_LDADD = libstun.la
check_PROGRAMS = \
test-attribute-pack \
......
This diff is collapsed.
......@@ -36,23 +36,178 @@
#ifndef STUN_BIND_H
# 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;
# include <stdbool.h>
# include <stdint.h>
# ifdef __cplusplus
extern "C" {
# 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,
const struct sockaddr *restrict srv, socklen_t srvlen,
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,
const struct sockaddr *restrict srv,
socklen_t srvlen);
const struct sockaddr *restrict srv, 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,
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
}
......
/*
* 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);
* Computes the FINGERPRINT of a STUN message.
* @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);
/* Don't hash last 8-bytes (= the FINGERPRINT attribute) */
assert (stun_length (ptr) >= 8);
size_t len = 20 + stun_length (ptr) - 8;
assert (stun_length (msg) >= 8);
size_t len = 20u + stun_length (msg) - 8;
return crc32 (msg, len) ^ 0x5354554e;
}
......
......@@ -45,7 +45,7 @@
#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);
assert (mlen >= 32);
......
This diff is collapsed.
/*
* 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