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

Refactored stun bind usage

parent f6632bda
...@@ -52,412 +52,199 @@ ...@@ -52,412 +52,199 @@
#include <unistd.h> #include <unistd.h>
#include <sys/time.h> #include <sys/time.h>
#include <fcntl.h> #include <fcntl.h>
#include "timer.h"
/** Non-blocking mode STUN binding discovery */
#include "trans.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); /** Non-blocking mode STUN binding discovery */
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 */
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); stun_agent_init_request (agent, msg, buffer, buffer_len, STUN_BINDING);
return stun_trans_timeout (&context->trans);
}
int stun_bind_elapse (stun_bind_t *context) return stun_agent_finish_message (agent, msg, NULL, 0);
{
int val = stun_trans_tick (&context->trans);
if (val != EAGAIN)
stun_bind_cancel (context);
return val;
} }
StunUsageBindReturn stun_usage_bind_process (StunMessage *msg,
/** Incoming packets handling */ struct sockaddr *addr, socklen_t *addrlen,
struct sockaddr *alternate_server, socklen_t *alternate_server_len)
int stun_bind_process (stun_bind_t *restrict ctx,
const void *restrict buf, size_t len,
struct sockaddr *restrict addr, socklen_t *addrlen)
{ {
int val, code; int val, code = -1;
assert (ctx != NULL);
val = stun_trans_preprocess (&ctx->agent, &ctx->trans, &code, buf, len); switch (stun_message_get_class (msg))
switch (val)
{ {
case EAGAIN: case STUN_REQUEST:
return EAGAIN; case STUN_INDICATION:
case 0: return STUN_USAGE_BIND_RETURN_RETRY;
case STUN_RESPONSE:
break; break;
default:
if (code == STUN_ERROR_ROLE_CONFLICT) case STUN_ERROR:
val = ECONNRESET; if (stun_message_find_error (msg, &code) != 0) {
stun_bind_cancel (ctx); /* missing ERROR-CODE: ignore message */
return val; 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); STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, addr, addrlen);
if (val) if (val)
{ {
stun_debug (" No XOR-MAPPED-ADDRESS: %s\n", strerror (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); STUN_ATTRIBUTE_MAPPED_ADDRESS, addr, addrlen);
if (val) if (val)
{ {
stun_debug (" No MAPPED-ADDRESS: %s\n", strerror (val)); stun_debug (" No MAPPED-ADDRESS: %s\n", strerror (val));
stun_bind_cancel (ctx); return STUN_USAGE_BIND_RETURN_ERROR;
return val;
} }
} }
stun_debug (" Mapped address found!\n"); stun_debug (" Mapped address found!\n");
stun_bind_cancel (ctx); return STUN_USAGE_BIND_RETURN_SUCCESS;
return 0;
} }
/** Blocking mode STUN binding discovery */ /** Binding keep-alive (Binding discovery indication!) */
int stun_bind_run (int fd, size_t
const struct sockaddr *restrict srv, socklen_t srvlen, stun_usage_bind_keepalive (StunAgent *agent, StunMessage *msg,
struct sockaddr *restrict addr, socklen_t *addrlen, int compat) 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 stun_agent_init_indication (agent, msg,
{ buf, len, STUN_BINDING);
val = stun_trans_recv (&ctx->trans, buf, sizeof (buf)); return stun_agent_finish_message (agent, msg, NULL, 0);
if (val == -1)
{
val = errno;
continue;
}
val = stun_bind_process (ctx, buf, val, addr, addrlen);
}
while (val == EAGAIN);
return val;
} }
/** ICE keep-alives (Binding discovery indication!) */ /** Blocking mode STUN binding discovery */
StunUsageBindReturn stun_usage_bind_run (const struct sockaddr *srv,
int socklen_t srvlen, struct sockaddr *addr, socklen_t *addrlen)
stun_bind_keepalive (int fd, const struct sockaddr *restrict srv,
socklen_t srvlen, int compat)
{ {
uint8_t buf[28]; stun_timer_t timer;
size_t len = sizeof (buf); stun_trans_t trans;
StunAgent agent; StunAgent agent;
StunMessage req;
uint8_t req_buf[STUN_MAX_MESSAGE_SIZE];
StunMessage msg; StunMessage msg;
uint8_t buf[STUN_MAX_MESSAGE_SIZE];
if (compat == 1) { StunValidationStatus valid;
stun_agent_init (&agent, STUN_ALL_KNOWN_ATTRIBUTES, size_t len;
STUN_COMPATIBILITY_RFC3489, ssize_t ret;
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)
{
int val; int val;
stun_bind_t *ctx; struct sockaddr_storage alternate_server;
socklen_t alternate_server_len = sizeof (alternate_server);
assert (username != NULL); StunUsageBindReturn bind_ret;
assert (password != NULL);
stun_agent_init (&agent, STUN_ALL_KNOWN_ATTRIBUTES,
val = stun_bind_alloc (context, fd, srv, srvlen, compat); STUN_COMPATIBILITY_3489BIS,
if (val) STUN_AGENT_USAGE_ADD_SERVER |
return val; STUN_AGENT_USAGE_USE_FINGERPRINT);
ctx = *context; len = stun_usage_bind_create (&agent, &req, req_buf, sizeof(req_buf));
if (compat != 1) {
ctx->trans.key.length = strlen (password); ret = stun_trans_create (&trans, SOCK_DGRAM, 0, srv, srvlen);
ctx->trans.key.value = malloc (ctx->trans.key.length); if (ret) {
if (ctx->trans.key.value == NULL) errno = ret;
{ stun_debug ("STUN transaction failed: couldn't create transport.\n");
val = ENOMEM; return STUN_USAGE_BIND_RETURN_ERROR;
goto error;
}
memcpy (ctx->trans.key.value, password, ctx->trans.key.length);
} }
if (compat != 1) { val = stun_trans_send (&trans, req_buf, len);
if (cand_use) if (val < -1) {
{ stun_debug ("STUN transaction failed: couldn't send request.\n");
val = stun_message_append_flag (&ctx->trans.message, return STUN_USAGE_BIND_RETURN_ERROR;
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_agent_finish_message (&ctx->agent, &ctx->trans.message, stun_timer_start (&timer);
compat == 1 ? NULL : (const uint8_t *) password, stun_debug ("STUN transaction started (timeout %dms).\n",
compat == 1 ? 0 : strlen (password)); stun_timer_remainder (&timer));
if (val == 0) do
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))
{ {
stun_debug (" Mapped address mismatch! (Symmetric NAT?)\n"); for (;;) {
return ECONNREFUSED; 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, valid = stun_agent_validate (&agent, &msg, buf, val, NULL, NULL);
STUN_ATTRIBUTE_XOR_INTERNAL_ADDRESS, if (valid == STUN_VALIDATION_UNKNOWN_ATTRIBUTE)
intad, adlen); return STUN_USAGE_BIND_RETURN_ERROR;
if (val)
{ if (valid != STUN_VALIDATION_SUCCESS) {
stun_debug (" No XOR-INTERNAL-ADDRESS: %s\n", strerror (val)); ret = EAGAIN;
return val; } 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, return STUN_USAGE_BIND_RETURN_SUCCESS;
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;
} }
...@@ -41,12 +41,6 @@ ...@@ -41,12 +41,6 @@
* @brief STUN binding discovery * @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 <stdbool.h>
# include <stdint.h> # include <stdint.h>
...@@ -56,156 +50,28 @@ typedef struct stun_bind_s stun_bind_t; ...@@ -56,156 +50,28 @@ typedef struct stun_bind_s stun_bind_t;
extern "C" { extern "C" {
# endif # endif
/** typedef enum {
* Performs STUN Binding discovery in blocking mode. STUN_USAGE_BIND_RETURN_SUCCESS,
* STUN_USAGE_BIND_RETURN_ERROR,
* @param fd socket to use for binding discovery, or -1 to create one STUN_USAGE_BIND_RETURN_RETRY,
* @param srv STUN server socket address STUN_USAGE_BIND_RETURN_ALTERNATE_SERVER,
* @param srvlen STUN server socket address byte length STUN_USAGE_BIND_RETURN_TIMEOUT,
* @param addr [OUT] pointer to a socket address structure to hold } StunUsageBindReturn;
* 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);
/**
* 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, size_t stun_usage_bind_create (StunAgent *agent, StunMessage *msg,
const struct sockaddr *restrict mapad, uint8_t *buffer, size_t buffer_len);
const struct sockaddr *restrict natad,
socklen_t adlen, uint32_t refresh, int compat);
int stun_nested_process (stun_nested_t *restrict ctx, StunUsageBindReturn stun_usage_bind_process (StunMessage *msg,
const void *restrict buf, size_t len, struct sockaddr *addr, socklen_t *addrlen,
struct sockaddr *restrict intad, socklen_t *adlen); 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 # 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