blob: b32785cb4d62c88b263ac7ef69917b71794c0597 [file] [log] [blame]
/**
* @file stunsrv.c Basic STUN Server for Connectivity checks
*
* Copyright (C) 2010 Creytiv.com
*/
#include <string.h>
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_tmr.h>
#include <re_sa.h>
#include <re_stun.h>
#include <re_ice.h>
#include <re_udp.h>
#include <re_tcp.h>
#include <re_sys.h>
#include <re_trice.h>
#include "trice.h"
#define DEBUG_MODULE "stunsrv"
#define DEBUG_LEVEL 5
#include <re_dbg.h>
static const char *sw = "ice stunsrv v" VERSION " (" ARCH "/" OS ")";
/*
* NOTE about TCP-candidates:
*
* Note that STUN requests received on a passive TCP candidate
* will typically produce a remote peer reflexive candidate.
*/
static int handle_stun_full(struct trice *icem, struct ice_lcand *lcand,
void *sock, const struct sa *src,
uint32_t prio, bool use_cand)
{
struct ice_candpair *pair = NULL;
struct ice_rcand *rcand;
enum ice_tcptype tcptype_rev;
int err = 0;
trice_tracef(icem, 36,
"[%u] STUNSRV: Rx Binding Request [%H <--- %J] %s\n",
lcand->attr.compid,
trice_cand_print, lcand,
src,
use_cand ? "[USE]" : "");
tcptype_rev = ice_tcptype_reverse(lcand->attr.tcptype);
rcand = trice_rcand_find(icem, lcand->attr.compid,
lcand->attr.proto, src);
if (!rcand) {
if (icem->conf.enable_prflx) {
err = trice_rcand_add(&rcand, icem,
lcand->attr.compid,
"444", lcand->attr.proto, prio,
src, ICE_CAND_TYPE_PRFLX,
tcptype_rev);
if (err)
return err;
trice_printf(icem, "{%u} added PRFLX "
"remote candidate (%H)\n",
lcand->attr.compid,
trice_cand_print, rcand);
}
}
/* already valid, skip */
pair = trice_candpair_find(&icem->validl, lcand, rcand);
if (pair)
goto out;
/* note: the candidate-pair can exist in either list */
pair = trice_candpair_find(&icem->checkl, lcand, rcand);
if (!pair) {
if (icem->conf.enable_prflx) {
DEBUG_WARNING("{%u} candidate pair not found:"
" source=%J\n",
lcand->attr.compid, src);
}
goto out;
}
/* 7.2.1.5. Updating the Nominated Flag */
if (use_cand) {
if (icem->lrole == ICE_ROLE_CONTROLLED) {
pair->nominated = true;
}
}
out:
/*
send a triggered request
*/
if (pair && use_cand) {
if (icem->checklist && !pair->trigged) {
err = trice_conncheck_trigged(icem, pair,
sock, use_cand);
if (err) {
DEBUG_WARNING("ice_checklist_stun_request"
" failed (%m)\n",
err);
}
pair->trigged = true;
}
}
return 0;
}
static int stunsrv_ereply(struct trice *icem, struct ice_lcand *lcand,
void *sock, const struct sa *src,
size_t presz, const struct stun_msg *req,
uint16_t scode, const char *reason)
{
DEBUG_WARNING("[%H] replying error to %J (%u %s)\n",
trice_cand_print, lcand,
src,
scode, reason);
trice_tracef(icem, 31,
"[%u] STUNSRV: Tx error [%J <--- %H] (%u %s)\n",
lcand->attr.compid,
src,
trice_cand_print, lcand,
scode, reason);
return stun_ereply(lcand->attr.proto, sock, src, presz, req,
scode, reason,
(uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 1,
STUN_ATTR_SOFTWARE, icem->sw ? icem->sw : sw);
}
int trice_stund_recv(struct trice *icem, struct ice_lcand *lcand,
void *sock, const struct sa *src,
struct stun_msg *req, size_t presz)
{
struct stun_attr *attr;
struct pl lu, ru;
int err;
/* RFC 5389: Fingerprint errors are silently discarded */
err = stun_msg_chk_fingerprint(req);
if (err)
return err;
err = stun_msg_chk_mi(req, (uint8_t *)icem->lpwd, strlen(icem->lpwd));
if (err) {
DEBUG_WARNING("message-integrity failed (src=%J)\n", src);
if (err == EBADMSG)
goto unauth;
else
goto badmsg;
}
attr = stun_msg_attr(req, STUN_ATTR_USERNAME);
if (!attr)
goto badmsg;
err = re_regex(attr->v.username, strlen(attr->v.username),
"[^:]+:[^]+", &lu, &ru);
if (err) {
DEBUG_WARNING("could not parse USERNAME attribute (%s)\n",
attr->v.username);
goto unauth;
}
if (pl_strcmp(&lu, icem->lufrag)) {
DEBUG_WARNING("local ufrag err (expected %s, actual %r)\n",
icem->lufrag, &lu);
goto unauth;
}
if (str_isset(icem->rufrag) && pl_strcmp(&ru, icem->rufrag)) {
DEBUG_WARNING("remote ufrag err (expected %s, actual %r)\n",
icem->rufrag, &ru);
goto unauth;
}
if (icem->lrole == ICE_ROLE_UNKNOWN) {
err = trice_reqbuf_append(icem, lcand, sock, src, req, presz);
if (err) {
DEBUG_WARNING("unable to buffer STUN request: %m\n",
err);
}
}
return trice_stund_recv_role_set(icem, lcand, sock, src, req, presz);
badmsg:
return stunsrv_ereply(icem, lcand, sock, src, presz, req,
400, "Bad Request");
unauth:
return stunsrv_ereply(icem, lcand, sock, src, presz, req,
401, "Unauthorized");
}
int trice_stund_recv_role_set(struct trice *icem, struct ice_lcand *lcand,
void *sock, const struct sa *src,
struct stun_msg *req, size_t presz)
{
struct stun_attr *attr;
enum ice_role remote_role = ICE_ROLE_UNKNOWN;
uint64_t tiebrk = 0;
uint32_t prio_prflx;
int err;
bool use_cand = false;
attr = stun_msg_attr(req, STUN_ATTR_CONTROLLED);
if (attr) {
remote_role = ICE_ROLE_CONTROLLED;
tiebrk = attr->v.uint64;
}
attr = stun_msg_attr(req, STUN_ATTR_CONTROLLING);
if (attr) {
remote_role = ICE_ROLE_CONTROLLING;
tiebrk = attr->v.uint64;
}
if (remote_role == ICE_ROLE_UNKNOWN)
goto badmsg;
if (remote_role == icem->lrole) {
DEBUG_NOTICE("role conflict detected (both %s)\n",
ice_role2name(remote_role));
if (icem->tiebrk >= tiebrk)
trice_switch_local_role(icem);
else
goto conflict;
}
attr = stun_msg_attr(req, STUN_ATTR_PRIORITY);
if (attr)
prio_prflx = attr->v.uint32;
else
goto badmsg;
attr = stun_msg_attr(req, STUN_ATTR_USE_CAND);
if (attr)
use_cand = true;
err = handle_stun_full(icem, lcand, sock, src, prio_prflx, use_cand);
if (err)
goto badmsg;
trice_tracef(icem, 32,
"[%u] STUNSRV: Tx success respons [%H ---> %J]\n",
lcand->attr.compid,
trice_cand_print, lcand, src);
return stun_reply(lcand->attr.proto, sock, src, presz, req,
(uint8_t *)icem->lpwd, strlen(icem->lpwd), true, 2,
STUN_ATTR_XOR_MAPPED_ADDR, src,
STUN_ATTR_SOFTWARE, icem->sw ? icem->sw : sw);
badmsg:
return stunsrv_ereply(icem, lcand, sock, src, presz, req,
400, "Bad Request");
conflict:
return stunsrv_ereply(icem, lcand, sock, src, presz, req,
487, "Role Conflict");
}