Commit 77bc5bcf authored by Youness Alaoui's avatar Youness Alaoui

Refactored stun bind usage

parent f6632bda
......@@ -52,412 +52,199 @@
#include <unistd.h>
#include <sys/time.h>
#include <fcntl.h>
/** Non-blocking mode STUN binding discovery */
#include "timer.h"
#include "trans.h"
struct stun_bind_s
{
stun_trans_t trans;
StunAgent agent;
};
/** 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
stun_bind_alloc (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv, socklen_t srvlen, int compat)
{
int val;
stun_bind_t *ctx = malloc (sizeof (*ctx));
if (ctx == NULL)
return ENOMEM;
memset (ctx, 0, sizeof (*ctx));
*context = ctx;
val = (fd != -1)
? stun_trans_init (&ctx->trans, fd, srv, srvlen)
: stun_trans_create (&ctx->trans, SOCK_DGRAM, 0, srv, srvlen);
if (val)
{
free (ctx);
return val;
}
if (compat == 1) {
stun_agent_init (&ctx->agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_RFC3489,
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
} else {
stun_agent_init (&ctx->agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_3489BIS,
STUN_AGENT_USAGE_ADD_SERVER |
STUN_AGENT_USAGE_USE_FINGERPRINT);
}
stun_agent_init_request (&ctx->agent, &ctx->trans.message,
ctx->trans.msg.buf, sizeof (ctx->trans.msg.buf), STUN_BINDING);
ctx->trans.msg.length = ctx->trans.message.buffer_len;
return 0;
}
int stun_bind_start (stun_bind_t **restrict context, int fd,
const struct sockaddr *restrict srv,
socklen_t srvlen, int compat)
{
stun_bind_t *ctx;
int val = stun_bind_alloc (context, fd, srv, srvlen, compat);
if (val)
return val;
ctx = *context;
val = stun_agent_finish_message (&ctx->agent, &ctx->trans.message, NULL, 0);
if (val == 0)
goto error;
ctx->trans.msg.length = val;
val = stun_trans_start (&ctx->trans);
if (val)
goto error;
return 0;
error:
stun_bind_cancel (ctx);
return val;
}
void stun_bind_cancel (stun_bind_t *context)
{
stun_trans_deinit (&context->trans);
free (context);
}
/** Timer and retransmission handling */
/** Non-blocking mode STUN binding discovery */
unsigned stun_bind_timeout (const stun_bind_t *context)
size_t stun_usage_bind_create (StunAgent *agent, StunMessage *msg,
uint8_t *buffer, size_t buffer_len)
{
assert (context != NULL);
return stun_trans_timeout (&context->trans);
}
stun_agent_init_request (agent, msg, buffer, buffer_len, STUN_BINDING);
int stun_bind_elapse (stun_bind_t *context)
{
int val = stun_trans_tick (&context->trans);
if (val != EAGAIN)
stun_bind_cancel (context);
return val;
return stun_agent_finish_message (agent, msg, NULL, 0);
}
/** Incoming packets handling */
int stun_bind_process (stun_bind_t *restrict ctx,
const void *restrict buf, size_t len,
struct sockaddr *restrict addr, socklen_t *addrlen)
StunUsageBindReturn stun_usage_bind_process (StunMessage *msg,
struct sockaddr *addr, socklen_t *addrlen,
struct sockaddr *alternate_server, socklen_t *alternate_server_len)
{
int val, code;
assert (ctx != NULL);
int val, code = -1;
val = stun_trans_preprocess (&ctx->agent, &ctx->trans, &code, buf, len);
switch (val)
switch (stun_message_get_class (msg))
{
case EAGAIN:
return EAGAIN;
case 0:
case STUN_REQUEST:
case STUN_INDICATION:
return STUN_USAGE_BIND_RETURN_RETRY;
case STUN_RESPONSE:
break;
default:
if (code == STUN_ERROR_ROLE_CONFLICT)
val = ECONNRESET;
stun_bind_cancel (ctx);
return val;
case STUN_ERROR:
if (stun_message_find_error (msg, &code) != 0) {
/* missing ERROR-CODE: ignore message */
return STUN_USAGE_BIND_RETURN_RETRY;
}
/* NOTE: currently we ignore unauthenticated messages if the context
* is authenticated, for security reasons. */
stun_debug (" STUN error message received (code: %d)\n", code);
/* ALTERNATE-SERVER mechanism */
if ((code / 100) == 3) {
if (stun_message_find_addr (msg, STUN_ATTRIBUTE_ALTERNATE_SERVER,
alternate_server, alternate_server_len)) {
stun_debug (" Unexpectedly missing ALTERNATE-SERVER attribute\n");
return STUN_USAGE_BIND_RETURN_ERROR;
}
stun_debug ("Found alternate server\n");
return STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER;
}
return STUN_USAGE_BIND_RETURN_ERROR;
}
val = stun_message_find_xor_addr (&ctx->trans.message,
stun_debug ("Received %u-bytes STUN message\n", stun_message_length (msg));
val = stun_message_find_xor_addr (msg,
STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, addr, addrlen);
if (val)
{
stun_debug (" No XOR-MAPPED-ADDRESS: %s\n", strerror (val));
val = stun_message_find_addr (&ctx->trans.message,
val = stun_message_find_addr (msg,
STUN_ATTRIBUTE_MAPPED_ADDRESS, addr, addrlen);
if (val)
{
stun_debug (" No MAPPED-ADDRESS: %s\n", strerror (val));
stun_bind_cancel (ctx);
return val;
return STUN_USAGE_BIND_RETURN_ERROR;
}
}
stun_debug (" Mapped address found!\n");
stun_bind_cancel (ctx);
return 0;
return STUN_USAGE_BIND_RETURN_SUCCESS;
}
/** Blocking mode STUN binding discovery */
/** Binding keep-alive (Binding discovery indication!) */
int stun_bind_run (int fd,
const struct sockaddr *restrict srv, socklen_t srvlen,
struct sockaddr *restrict addr, socklen_t *addrlen, int compat)
size_t
stun_usage_bind_keepalive (StunAgent *agent, StunMessage *msg,
uint8_t *buf, size_t len)
{
stun_bind_t *ctx;
uint8_t buf[STUN_MAX_MESSAGE_SIZE];
ssize_t val;
val = stun_bind_start (&ctx, fd, srv, srvlen, compat);
if (val)
return val;
do
{
val = stun_trans_recv (&ctx->trans, buf, sizeof (buf));
if (val == -1)
{
val = errno;
continue;
}
val = stun_bind_process (ctx, buf, val, addr, addrlen);
}
while (val == EAGAIN);
return val;
stun_agent_init_indication (agent, msg,
buf, len, STUN_BINDING);
return stun_agent_finish_message (agent, msg, NULL, 0);
}
/** ICE keep-alives (Binding discovery indication!) */
int
stun_bind_keepalive (int fd, const struct sockaddr *restrict srv,
socklen_t srvlen, int compat)
/** Blocking mode STUN binding discovery */
StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
socklen_t srvlen, struct sockaddr *addr, socklen_t *addrlen)
{
uint8_t buf[28];
size_t len = sizeof (buf);
stun_timer_t timer;
stun_trans_t trans;
StunAgent agent;
StunMessage req;
uint8_t req_buf[STUN_MAX_MESSAGE_SIZE];
StunMessage msg;
if (compat == 1) {
stun_agent_init (&agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_RFC3489,
STUN_AGENT_USAGE_SHORT_TERM_CREDENTIALS |
STUN_AGENT_USAGE_IGNORE_CREDENTIALS);
} else {
stun_agent_init (&agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_3489BIS,
STUN_AGENT_USAGE_ADD_SERVER |
STUN_AGENT_USAGE_USE_FINGERPRINT);
}
stun_agent_init_indication (&agent, &msg,
buf, sizeof (buf), STUN_BINDING);
len = stun_agent_finish_message (&agent, &msg, NULL, 0);
assert (len == sizeof(buf));
/* NOTE: hopefully, this is only needed for non-stream sockets */
if (stun_sendto (fd, buf, len, srv, srvlen) == -1)
return errno;
return 0;
}
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, uint32_t compat)
{
uint8_t buf[STUN_MAX_MESSAGE_SIZE];
StunValidationStatus valid;
size_t len;
ssize_t ret;
int val;
stun_bind_t *ctx;
assert (username != NULL);
assert (password != NULL);
val = stun_bind_alloc (context, fd, srv, srvlen, compat);
if (val)
return val;
ctx = *context;
if (compat != 1) {
ctx->trans.key.length = strlen (password);
ctx->trans.key.value = malloc (ctx->trans.key.length);
if (ctx->trans.key.value == NULL)
{
val = ENOMEM;
goto error;
}
memcpy (ctx->trans.key.value, password, ctx->trans.key.length);
struct sockaddr_storage alternate_server;
socklen_t alternate_server_len = sizeof (alternate_server);
StunUsageBindReturn bind_ret;
stun_agent_init (&agent, STUN_ALL_KNOWN_ATTRIBUTES,
STUN_COMPATIBILITY_3489BIS,
STUN_AGENT_USAGE_ADD_SERVER |
STUN_AGENT_USAGE_USE_FINGERPRINT);
len = stun_usage_bind_create (&agent, &req, req_buf, sizeof(req_buf));
ret = stun_trans_create (&trans, SOCK_DGRAM, 0, srv, srvlen);
if (ret) {
errno = ret;
stun_debug ("STUN transaction failed: couldn't create transport.\n");
return STUN_USAGE_BIND_RETURN_ERROR;
}
if (compat != 1) {
if (cand_use)
{
val = stun_message_append_flag (&ctx->trans.message,
STUN_ATTRIBUTE_USE_CANDIDATE);
if (val)
goto error;
}
val = stun_message_append32 (&ctx->trans.message,
STUN_ATTRIBUTE_PRIORITY, priority);
if (val)
goto error;
val = stun_message_append64 (&ctx->trans.message,
controlling ? STUN_ATTRIBUTE_ICE_CONTROLLING
: STUN_ATTRIBUTE_ICE_CONTROLLED, tie);
if (val)
goto error;
}
if (username) {
val = stun_message_append_string (&ctx->trans.message,
STUN_ATTRIBUTE_USERNAME, username);
if (val)
goto error;
val = stun_trans_send (&trans, req_buf, len);
if (val < -1) {
stun_debug ("STUN transaction failed: couldn't send request.\n");
return STUN_USAGE_BIND_RETURN_ERROR;
}
val = stun_agent_finish_message (&ctx->agent, &ctx->trans.message,
compat == 1 ? NULL : (const uint8_t *) password,
compat == 1 ? 0 : strlen (password));
stun_timer_start (&timer);
stun_debug ("STUN transaction started (timeout %dms).\n",
stun_timer_remainder (&timer));
if (val == 0)
goto error;
ctx->trans.msg.length = val;
val = stun_trans_start (&ctx->trans);
if (val)
goto error;
return 0;
error:
stun_bind_cancel (ctx);
return val;
}
/** STUN NAT control */
struct stun_nested_s
{
stun_bind_t *bind;
struct sockaddr_storage mapped;
uint32_t refresh;
uint32_t bootnonce;
};
int stun_nested_start (stun_nested_t **restrict context, int fd,
const struct sockaddr *restrict mapad,
const struct sockaddr *restrict natad,
socklen_t adlen, uint32_t refresh, int compat)
{
stun_nested_t *ctx;
int val;
if (adlen > sizeof (ctx->mapped))
return ENOBUFS;
ctx = malloc (sizeof (*ctx));
memcpy (&ctx->mapped, mapad, adlen);
ctx->refresh = 0;
ctx->bootnonce = 0;
/* TODO: forcily set port to 3478 */
val = stun_bind_alloc (&ctx->bind, fd, natad, adlen, compat);
if (val)
return val;
*context = ctx;
val = stun_message_append32 (&ctx->bind->trans.message,
STUN_ATTRIBUTE_REFRESH_INTERVAL, refresh);
if (val)
goto error;
val = stun_agent_finish_message (&ctx->bind->agent,
&ctx->bind->trans.message, NULL, 0);
if (val)
goto error;
val = stun_trans_start (&ctx->bind->trans);
if (val)
goto error;
return 0;
error:
stun_bind_cancel (ctx->bind);
return val;
}
int stun_nested_process (stun_nested_t *restrict ctx,
const void *restrict buf, size_t len,
struct sockaddr *restrict intad, socklen_t *adlen)
{
struct sockaddr_storage mapped;
socklen_t mappedlen = sizeof (mapped);
int val;
assert (ctx != NULL);
val = stun_bind_process (ctx->bind, buf, len,
(struct sockaddr *)&mapped, &mappedlen);
if (val)
return val;
/* Mapped address mistmatch! (FIXME: what are we really supposed to do
* in this case???) */
if (sockaddrcmp ((struct sockaddr *)&mapped,
(struct sockaddr *)&ctx->mapped))
do
{
stun_debug (" Mapped address mismatch! (Symmetric NAT?)\n");
return ECONNREFUSED;
}
for (;;) {
unsigned delay = stun_timer_remainder (&timer);
ret = stun_trans_poll (&trans, delay);
if (ret == EAGAIN) {
switch (stun_timer_refresh (&timer)) {
case -1:
stun_debug ("STUN transaction failed: time out.\n");
return STUN_USAGE_BIND_RETURN_TIMEOUT; // fatal error!
case 0:
stun_debug ("STUN transaction retransmitted (timeout %dms).\n",
stun_timer_remainder (&timer));
val = stun_trans_send (&trans, req_buf, len);
if (val < -1) {
stun_debug ("STUN transaction failed: couldn't resend request.\n");
return STUN_USAGE_BIND_RETURN_ERROR;
}
ret = EAGAIN;
continue;
}
}
val = stun_trans_recv (&trans, buf, sizeof (buf));
if (val >= 0) {
break;
}
}
val = stun_message_find_xor_addr (&ctx->bind->trans.message,
STUN_ATTRIBUTE_XOR_INTERNAL_ADDRESS,
intad, adlen);
if (val)
{
stun_debug (" No XOR-INTERNAL-ADDRESS: %s\n", strerror (val));
return val;
valid = stun_agent_validate (&agent, &msg, buf, val, NULL, NULL);
if (valid == STUN_VALIDATION_UNKNOWN_ATTRIBUTE)
return STUN_USAGE_BIND_RETURN_ERROR;
if (valid != STUN_VALIDATION_SUCCESS) {
ret = EAGAIN;
} else {
bind_ret = stun_usage_bind_process (&msg, addr, addrlen,
(struct sockaddr *) &alternate_server, &alternate_server_len);
if (bind_ret == STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER) {
stun_trans_deinit (&trans);
ret = stun_trans_create (&trans, SOCK_DGRAM, 0,
(struct sockaddr *) &alternate_server, alternate_server_len);
if (ret) {
errno = ret;
return STUN_USAGE_BIND_RETURN_ERROR;
}
val = stun_trans_send (&trans, req_buf, len);
if (val < -1)
return STUN_USAGE_BIND_RETURN_ERROR;
stun_timer_start (&timer);
ret = EAGAIN;
} else if (bind_ret == STUN_USAGE_BIND_RETURN_RETRY) {
ret = EAGAIN;
} else {
return bind_ret;
}
}
}
while (ret == EAGAIN);
stun_message_find32 (&ctx->bind->trans.message,
STUN_ATTRIBUTE_REFRESH_INTERVAL, &ctx->refresh);
/* TODO: give this to caller */
stun_debug (" Internal address found!\n");
stun_bind_cancel (ctx->bind);
ctx->bind = NULL;
return 0;
return STUN_USAGE_BIND_RETURN_SUCCESS;
}
......@@ -41,12 +41,6 @@
* @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>
......@@ -56,156 +50,28 @@ typedef struct stun_bind_s stun_bind_t;
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, int compat);
/**
* Starts STUN Binding discovery in non-blocking mode.
*
* @param context pointer to an opaque pointer that will be passed to
* other stun_bind_*() functions 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,
int compat);
/**
* 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);
/**
* 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)
* - ECONNRESET: role conflict error from server
* - ECONNREFUSED: any other fatal 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);
typedef enum {
STUN_USAGE_BIND_RETURN_SUCCESS,
STUN_USAGE_BIND_RETURN_ERROR,
STUN_USAGE_BIND_RETURN_RETRY,
STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER,
STUN_USAGE_BIND_RETURN_TIMEOUT,
} StunUsageBindReturn;
/**
* Sends a STUN Binding indication, aka ICE keep-alive packet.
*
* @param fd socket descriptor to send packet through
* @param srv destination socket address (possibly NULL if connected)
* @param srvlen destination socket address length (possibly 0)
* @return 0 on success, an error code from sendto() otherwise.
*/
int stun_bind_keepalive (int fd, const struct sockaddr *restrict srv,
socklen_t srvlen, int compat);
/**
* 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, uint32_t compat);
/**
* <b>Provisional</b> and incomplete STUN NAT control API
* Subject to change.
*/
typedef struct stun_nested_s stun_nested_t;
int stun_nested_start (stun_nested_t **restrict context, int fd,
const struct sockaddr *restrict mapad,
const struct sockaddr *restrict natad,
socklen_t adlen, uint32_t refresh, int compat);
size_t stun_usage_bind_create (StunAgent *agent, StunMessage *msg,
uint8_t *buffer, size_t buffer_len);
int stun_nested_process (stun_nested_t *restrict ctx,
const void *restrict buf, size_t len,
struct sockaddr *restrict intad, socklen_t *adlen);
StunUsageBindReturn stun_usage_bind_process (StunMessage *msg,
struct sockaddr *addr, socklen_t *addrlen,
struct sockaddr *alternate_server, socklen_t *alternate_server_len);
size_t stun_usage_bind_keepalive (StunAgent *agent, StunMessage *msg,
uint8_t *buf, size_t len);
StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
socklen_t srvlen, struct sockaddr *addr, socklen_t *addrlen);
# ifdef __cplusplus
}
......
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