Squashed 'third_party/rawrtc/re/' content from commit f3163ce8b
Change-Id: I6a235e6ac0f03269d951026f9d195da05c40fdab
git-subtree-dir: third_party/rawrtc/re
git-subtree-split: f3163ce8b526a13b35ef71ce4dd6f43585064d8a
diff --git a/src/natbd/filtering.c b/src/natbd/filtering.c
new file mode 100644
index 0000000..8d0acc5
--- /dev/null
+++ b/src/natbd/filtering.c
@@ -0,0 +1,242 @@
+/**
+ * @file filtering.c NAT Filtering Behaviour Discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_filtering"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Determining NAT Filtering Behavior
+
+ This will also require at most three tests. These tests should be
+ performed using a port that wasn't used for mapping or other tests as
+ packets sent during those tests may affect results. In test I, the
+ client performs the UDP connectivity test. The server will return
+ its alternate address and port in OTHER-ADDRESS in the binding
+ response. If OTHER-ADDRESS is not returned, the server does not
+ support this usage and this test cannot be run.
+
+ In test II, the client sends a binding request to the primary address
+ of the server with the CHANGE-REQUEST attribute set to change-port
+ and change-IP. This will cause the server to send its response from
+ its alternate IP address and alternate port. If the client receives
+ a response the current behaviour of the NAT is Endpoint-Independent
+ Filtering.
+
+ If no response is received, test III must be performed to distinguish
+ between Address-Dependent Filtering and Address and Port-Dependent
+ Filtering. In test III, the client sends a binding request to the
+ original server address with CHANGE-REQUEST set to change-port. If
+ the client receives a response the current behaviour is Address-
+ Dependent Filtering; if no response is received the current behaviour
+ is Address and Port-Dependent Filtering.
+ */
+
+
+/** Defines a NAT Filtering Behaviour Discovery session */
+struct nat_filtering {
+ struct stun *stun; /**< STUN instance */
+ struct sa srv; /**< Server IP address and port */
+ int test_phase; /**< State machine */
+ nat_filtering_h *fh; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_change_req change_req;
+ struct nat_filtering *nf = arg;
+ struct stun_attr *attr;
+ (void)reason;
+
+ if (err == ECONNABORTED) {
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_OTHER_ADDR);
+ if (!err && !attr) {
+ DEBUG_WARNING("no OTHER-ADDRESS in response - abort\n");
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ switch (nf->test_phase) {
+
+ case 1:
+ /* Test I completed */
+
+ if (err || scode) {
+ DEBUG_WARNING("Test I: stun_response_handler: %m\n",
+ err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+
+ /* Start Test II */
+
+ /*
+ In test II, the client sends a binding request to the
+ primary address of the server with the CHANGE-REQUEST
+ attribute set to change-port and change-IP.
+ */
+
+ ++nf->test_phase;
+
+ change_req.ip = true;
+ change_req.port = true;
+
+ err = stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv,
+ 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 2,
+ STUN_ATTR_SOFTWARE, stun_software,
+ STUN_ATTR_CHANGE_REQ, &change_req);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ break;
+
+ case 2:
+ /* Test II completed */
+
+ /*
+ If the client receives a response the current behaviour of
+ the NAT is Endpoint Independent Filtering.
+ */
+
+ if (0 == err) {
+ if (!scode) {
+ nf->fh(0, NAT_TYPE_ENDP_INDEP, nf->arg);
+ }
+ else {
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ return;
+ }
+
+ /* Start Test III - the client sends a binding request to the
+ original server address with CHANGE-REQUEST set to
+ change-port
+ */
+ ++nf->test_phase;
+
+ change_req.ip = false;
+ change_req.port = true;
+
+ err = stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv,
+ 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 2,
+ STUN_ATTR_SOFTWARE, stun_software,
+ STUN_ATTR_CHANGE_REQ, &change_req);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nf->fh(err, NAT_TYPE_UNKNOWN, nf->arg);
+ }
+ break;
+
+ case 3:
+ /* Test III completed */
+ DEBUG_INFO("Test III completed\n");
+
+ if (0 == err && !scode) {
+ nf->fh(0, NAT_TYPE_ADDR_DEP, nf->arg);
+ }
+ else {
+ nf->fh(0, NAT_TYPE_ADDR_PORT_DEP, nf->arg);
+ }
+ break;
+
+ default:
+ DEBUG_WARNING("invalid test phase %d\n", nf->test_phase);
+ nf->fh(EINVAL, NAT_TYPE_UNKNOWN, nf->arg);
+ return;
+ }
+}
+
+
+static void filtering_destructor(void *data)
+{
+ struct nat_filtering *nf = data;
+
+ mem_deref(nf->stun);
+}
+
+
+/**
+ * Allocate a NAT Filtering Behaviour Discovery session
+ *
+ * @param nfp Pointer to allocated NAT filtering object
+ * @param srv STUN Server IP address and port number
+ * @param conf STUN configuration (Optional)
+ * @param fh Filtering result handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_filtering_alloc(struct nat_filtering **nfp, const struct sa *srv,
+ const struct stun_conf *conf,
+ nat_filtering_h *fh, void *arg)
+{
+ struct nat_filtering *nf;
+ int err;
+
+ if (!nfp || !srv || !fh)
+ return EINVAL;
+
+ nf = mem_zalloc(sizeof(*nf), filtering_destructor);
+ if (!nf)
+ return ENOMEM;
+
+ err = stun_alloc(&nf->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&nf->srv, srv);
+
+ nf->fh = fh;
+ nf->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(nf);
+ else
+ *nfp = nf;
+
+ return err;
+}
+
+
+/**
+ * Start a NAT Filtering Behaviour Discovery session
+ *
+ * @param nf NAT filtering object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_filtering_start(struct nat_filtering *nf)
+{
+ if (!nf)
+ return EINVAL;
+
+ nf->test_phase = 1;
+
+ return stun_request(NULL, nf->stun, IPPROTO_UDP, NULL, &nf->srv, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nf, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
diff --git a/src/natbd/genalg.c b/src/natbd/genalg.c
new file mode 100644
index 0000000..50b3827
--- /dev/null
+++ b/src/natbd/genalg.c
@@ -0,0 +1,152 @@
+/**
+ * @file genalg.c Detecting Generic ALGs
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_genalg"
+#define DEBUG_LEVEL 7
+#include <re_dbg.h>
+
+
+/*
+ Detecting Generic ALGs
+
+ A number of NAT boxes are now being deployed into the market which
+ try to provide "generic" ALG functionality. These generic ALGs hunt
+ for IP addresses, either in text or binary form within a packet, and
+ rewrite them if they match a binding. This behavior can be detected
+ because the STUN server returns both the MAPPED-ADDRESS and XOR-
+ MAPPED-ADDRESS in the same response. If the result in the two does
+ not match, there a NAT with a generic ALG in the path.
+ */
+
+
+/** Defines a NAT Generic ALG detection session */
+struct nat_genalg {
+ struct stun *stun; /**< STUN Client */
+ struct sa srv; /**< Server address and port */
+ int proto; /**< IP protocol */
+ nat_genalg_h *h; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct stun_attr *xmap, *map;
+ struct nat_genalg *ng = arg;
+ int status = 0;
+ (void)reason;
+
+ if (err) {
+ ng->h(err, 0, NULL, -1, NULL, ng->arg);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ map = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+ xmap = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!map || !xmap) {
+ ng->h(EINVAL, scode, reason, -1, NULL, ng->arg);
+ break;
+ }
+
+ status = sa_cmp(&map->v.sa, &xmap->v.sa, SA_ALL) ? -1 : 1;
+
+ ng->h(0, scode, reason, status, &xmap->v.sa, ng->arg);
+ break;
+
+ default:
+ ng->h(0, scode, reason, -1, NULL, ng->arg);
+ break;
+ }
+}
+
+
+static void genalg_destructor(void *data)
+{
+ struct nat_genalg *ng = data;
+
+ mem_deref(ng->stun);
+}
+
+
+/**
+ * Allocate a new NAT Generic ALG detection session
+ *
+ * @param ngp Pointer to allocated NAT Generic ALG object
+ * @param srv STUN Server IP address and port
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param gh Generic ALG handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_genalg_alloc(struct nat_genalg **ngp, const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_genalg_h *gh, void *arg)
+{
+ struct nat_genalg *ng;
+ int err;
+
+ if (!ngp || !srv || !proto || !gh)
+ return EINVAL;
+
+ ng = mem_zalloc(sizeof(*ng), genalg_destructor);
+ if (!ng)
+ return ENOMEM;
+
+ err = stun_alloc(&ng->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&ng->srv, srv);
+ ng->proto = proto;
+ ng->h = gh;
+ ng->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(ng);
+ else
+ *ngp = ng;
+
+ return err;
+}
+
+
+/**
+ * Start the NAT Generic ALG detection
+ *
+ * @param ng NAT Generic ALG object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_genalg_start(struct nat_genalg *ng)
+{
+ int err;
+
+ if (!ng)
+ return EINVAL;
+
+ err = stun_request(NULL, ng->stun, ng->proto, NULL, &ng->srv, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, ng, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ return err;
+}
diff --git a/src/natbd/hairpinning.c b/src/natbd/hairpinning.c
new file mode 100644
index 0000000..396d50e
--- /dev/null
+++ b/src/natbd/hairpinning.c
@@ -0,0 +1,363 @@
+/**
+ * @file hairpinning.c NAT Hairpinning Behaviour discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_hairpinning"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/*
+ Diagnosing NAT Hairpinning
+
+ STUN Binding Requests allow a a client to determine whether it is
+ behind a NAT that support hairpinning of datagrams. To perform this
+ test, the client first sends a Binding Request to its STUN server to
+ determine its mapped address. The client then sends a STUN Binding
+ Request to this mapped address from a different port. If the client
+ receives its own request, the NAT hairpins datagrams. This test
+ applies to UDP, TCP, or TCP/TLS connections.
+
+ */
+
+
+/** Defines NAT Hairpinning Behaviour Discovery */
+struct nat_hairpinning {
+ struct stun *stun; /**< STUN Client */
+ int proto; /**< IP Protocol */
+ struct sa srv; /**< Server address and port */
+ struct udp_sock *us; /**< UDP socket */
+ struct tcp_conn *tc; /**< Client TCP Connection */
+ struct tcp_sock *ts; /**< Server TCP Socket */
+ struct tcp_conn *tc2; /**< Server TCP Connection */
+ nat_hairpinning_h *hph; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void hairpinning_destructor(void *data)
+{
+ struct nat_hairpinning *nh = data;
+
+ mem_deref(nh->us);
+ mem_deref(nh->tc);
+ mem_deref(nh->ts);
+ mem_deref(nh->tc2);
+ mem_deref(nh->stun);
+}
+
+
+static void msg_recv(struct nat_hairpinning *nh, int proto, void *sock,
+ const struct sa *src, struct mbuf *mb)
+{
+ struct stun_unknown_attr ua;
+ struct stun_msg *msg;
+
+ if (0 != stun_msg_decode(&msg, mb, &ua))
+ return;
+
+ switch (stun_msg_class(msg)) {
+
+ case STUN_CLASS_REQUEST:
+ (void)stun_reply(proto, sock, src, 0, msg, NULL, 0, false, 3,
+ STUN_ATTR_XOR_MAPPED_ADDR, src,
+ STUN_ATTR_MAPPED_ADDR, src,
+ STUN_ATTR_SOFTWARE, stun_software);
+ break;
+
+ case STUN_CLASS_ERROR_RESP:
+ case STUN_CLASS_SUCCESS_RESP:
+ (void)stun_ctrans_recv(nh->stun, msg, &ua);
+ break;
+
+ default:
+ DEBUG_WARNING("unknown class 0x%04x\n", stun_msg_class(msg));
+ break;
+ }
+
+ mem_deref(msg);
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ msg_recv(nh, IPPROTO_UDP, nh->us, src, mb);
+}
+
+
+static void stun_response_handler2(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ (void)reason;
+ (void)msg;
+
+ if (err || scode) {
+ nh->hph(0, false, nh->arg);
+ return;
+ }
+
+ /* Hairpinning supported */
+ nh->hph(0, true, nh->arg);
+}
+
+
+static int hairpin_send(struct nat_hairpinning *nh, const struct sa *srv)
+{
+ return stun_request(NULL, nh->stun, nh->proto, NULL,
+ srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler2, nh, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+/*
+ * TCP Connections: STUN Client2 to Embedded STUN Server
+ */
+
+
+static void tcp_recv_handler2(struct mbuf *mb, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ msg_recv(nh, IPPROTO_TCP, nh->tc2, NULL, mb);
+}
+
+
+static void tcp_close_handler2(int err, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ const struct stun_attr *attr;
+ (void)reason;
+
+ if (err) {
+ nh->hph(err, false, nh->arg);
+ return;
+ }
+
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr)
+ attr = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+ if (scode || !attr) {
+ nh->hph(EBADMSG, false, nh->arg);
+ return;
+ }
+
+ /* Send hairpinning test message */
+ err = hairpin_send(nh, &attr->v.sa);
+ if (err) {
+ DEBUG_WARNING("hairpin_send: (%m)\n", err);
+ }
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+static int mapped_send(struct nat_hairpinning *nh)
+{
+ return stun_request(NULL, nh->stun, nh->proto, nh->us ?
+ (void *)nh->us : (void *)nh->tc,
+ &nh->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nh, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void tcp_conn_handler(const struct sa *peer, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ (void)peer;
+
+ err = tcp_accept(&nh->tc2, nh->ts, NULL, tcp_recv_handler2,
+ tcp_close_handler2, nh);
+ if (err) {
+ DEBUG_WARNING("TCP conn: tcp_accept: %m\n", err);
+ }
+}
+
+
+/*
+ * TCP Connection: STUN Client to STUN Server
+ */
+
+static void tcp_estab_handler(void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ err = mapped_send(nh);
+ if (err) {
+ DEBUG_WARNING("TCP established: mapped_send (%m)\n", err);
+ nh->hph(err, false, nh->arg);
+ }
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+ int err;
+
+ err = stun_recv(nh->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("stun recv: %m\n", err);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct nat_hairpinning *nh = arg;
+
+ if (err)
+ nh->hph(err, false, nh->arg);
+}
+
+
+/**
+ * Allocate a new NAT Hairpinning discovery session
+ *
+ * @param nhp Pointer to allocated NAT Hairpinning object
+ * @param proto Transport protocol
+ * @param srv STUN Server IP address and port number
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param hph Hairpinning result handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_hairpinning_alloc(struct nat_hairpinning **nhp,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_hairpinning_h *hph, void *arg)
+{
+ struct nat_hairpinning *nh;
+ struct sa local;
+ int err;
+
+ if (!srv || !hph)
+ return EINVAL;
+
+ nh = mem_zalloc(sizeof(*nh), hairpinning_destructor);
+ if (!nh)
+ return ENOMEM;
+
+ err = stun_alloc(&nh->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ sa_cpy(&nh->srv, srv);
+ nh->proto = proto;
+ nh->hph = hph;
+ nh->arg = arg;
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_listen(&nh->us, NULL, udp_recv_handler, nh);
+ break;
+
+ case IPPROTO_TCP:
+ sa_set_in(&local, 0, 0);
+
+ /*
+ * Part I - Allocate and bind all sockets
+ */
+ err = tcp_sock_alloc(&nh->ts, &local, tcp_conn_handler, nh);
+ if (err)
+ break;
+
+ err = tcp_conn_alloc(&nh->tc, &nh->srv,
+ tcp_estab_handler, tcp_recv_handler,
+ tcp_close_handler, nh);
+ if (err)
+ break;
+
+ err = tcp_sock_bind(nh->ts, &local);
+ if (err)
+ break;
+
+ err = tcp_sock_local_get(nh->ts, &local);
+ if (err)
+ break;
+
+ err = tcp_conn_bind(nh->tc, &local);
+ if (err)
+ break;
+
+ /*
+ * Part II - Listen and connect all sockets
+ */
+ err = tcp_sock_listen(nh->ts, 5);
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ break;
+ }
+
+ out:
+ if (err)
+ mem_deref(nh);
+ else
+ *nhp = nh;
+
+ return err;
+}
+
+
+/**
+ * Start a new NAT Hairpinning discovery session
+ *
+ * @param nh NAT Hairpinning object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_hairpinning_start(struct nat_hairpinning *nh)
+{
+ if (!nh)
+ return EINVAL;
+
+ switch (nh->proto) {
+
+ case IPPROTO_UDP:
+ return mapped_send(nh);
+
+ case IPPROTO_TCP:
+ return tcp_conn_connect(nh->tc, &nh->srv);
+
+ default:
+ return EPROTONOSUPPORT;
+ }
+}
diff --git a/src/natbd/lifetime.c b/src/natbd/lifetime.c
new file mode 100644
index 0000000..0bbffb9
--- /dev/null
+++ b/src/natbd/lifetime.c
@@ -0,0 +1,332 @@
+/**
+ * @file lifetime.c NAT Binding Lifetime Discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#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_udp.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_lifetime"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Binding Lifetime Discovery
+
+ STUN can also be used to probe the lifetimes of the bindings created
+ by the NAT. For many NAT devices, an absolute refresh interval
+ cannot be determined; bindings might be closed quicker under heavy
+ load or might not behave as the tests suggest. For this reason
+ applications that require reliable bindings must send keep-alives as
+ frequently as required by all NAT devices that will be encountered.
+ */
+
+
+/** Defines a NAT Binding Lifetime Discovery session */
+struct nat_lifetime {
+ struct stun *stun; /**< STUN Client */
+ struct stun_ctrans *ctx; /**< STUN Transaction 1 */
+ struct stun_ctrans *cty; /**< STUN Transaction 2 */
+ struct udp_sock *us_x; /**< First UDP socket */
+ struct udp_sock *us_y; /**< Second UDP socket */
+ struct sa srv; /**< Server IP-address/port */
+ struct sa map; /**< Mapped IP address/port */
+ struct tmr tmr; /**< Refresh timer */
+ bool probing; /**< Probing flag */
+ struct nat_lifetime_interval interval; /**< Lifetime intervals */
+ nat_lifetime_h *lh; /**< Result handler */
+ void *arg; /**< Handler argument */
+};
+
+
+static void timeout(void *arg);
+static void binding_ok(struct nat_lifetime *nl);
+static void binding_expired(struct nat_lifetime *nl);
+
+
+/*
+ * X socket
+ */
+
+
+static void msg_recv(struct stun *stun, const struct sa *src,
+ struct mbuf *mb)
+{
+ int err;
+ (void)src;
+
+ err = stun_recv(stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("msg_recv: stunc_recv(): (%m)\n", err);
+ }
+}
+
+
+static void udp_recv_handler_x(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_lifetime *nl = arg;
+
+ /* Forward response to socket */
+ msg_recv(nl->stun, src, mb);
+}
+
+
+static void stun_response_handler_x(int err, uint16_t scode,
+ const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ struct stun_attr *attr;
+
+ (void)reason;
+
+ if (err) {
+ DEBUG_WARNING("stun_response_handler X: %m\n", err);
+ goto out;
+ }
+
+ switch (scode) {
+
+ case 0:
+ attr = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!attr) {
+ err = EPROTO;
+ break;
+ }
+
+ nl->map = attr->v.xor_mapped_addr;
+
+ DEBUG_INFO("Starting timer of %d seconds...[zzz]...\n",
+ nl->interval.cur);
+
+ tmr_start(&nl->tmr, nl->interval.cur*1000, timeout, nl);
+ return;
+
+ default:
+ err = EPROTO;
+ break;
+ }
+
+ out:
+ nl->lh(err, &nl->interval, nl->arg);
+ binding_expired(nl);
+}
+
+
+/*
+ * Y socket
+ */
+
+
+static void udp_recv_handler_y(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_lifetime *nl = arg;
+
+ (void)src;
+ (void)mb;
+
+ if (!nl->probing) {
+ DEBUG_WARNING("Y: hmm, not probing?\n");
+ }
+
+ binding_expired(nl);
+}
+
+
+static void stun_response_handler_y(int err, uint16_t scode,
+ const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ (void)reason;
+ (void)msg;
+
+ if (err) {
+ binding_expired(nl);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ binding_ok(nl);
+ break;
+
+ default:
+ nl->lh(EBADMSG, &nl->interval, nl->arg);
+ break;
+ }
+}
+
+
+/*
+ * Common
+ */
+
+
+static int start_test(struct nat_lifetime *nl)
+{
+ nl->probing = false;
+
+ tmr_cancel(&nl->tmr);
+
+ nl->ctx = mem_deref(nl->ctx);
+ return stun_request(&nl->ctx, nl->stun, IPPROTO_UDP, nl->us_x,
+ &nl->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler_x, nl, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+}
+
+
+static void timeout(void *arg)
+{
+ struct nat_lifetime *nl = arg;
+ const uint16_t rp = sa_port(&nl->map);
+ int err;
+
+ nl->probing = true;
+
+ nl->cty = mem_deref(nl->cty);
+ err = stun_request(&nl->cty, nl->stun, IPPROTO_UDP, nl->us_y,
+ &nl->srv, 0, STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler_y, nl, 2,
+ STUN_ATTR_RESP_PORT, &rp,
+ STUN_ATTR_SOFTWARE, stun_software);
+ if (err)
+ goto out;
+
+ return;
+
+ out:
+ DEBUG_WARNING("timeout: (%m)\n", err);
+
+ nl->lh(err, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+/* Binding OK - recalculate current interval */
+static void binding_ok(struct nat_lifetime *nl)
+{
+ nl->interval.min = max(1, nl->interval.cur);
+
+ if (nl->interval.max > 0)
+ nl->interval.cur = (nl->interval.min + nl->interval.max) / 2;
+ else
+ nl->interval.cur *= 2;
+
+ nl->lh(0, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+/* Request timed out - recalculate current interval */
+static void binding_expired(struct nat_lifetime *nl)
+{
+ nl->interval.max = nl->interval.cur;
+
+ nl->interval.cur = (nl->interval.min + nl->interval.max) / 2;
+
+ nl->lh(0, &nl->interval, nl->arg);
+
+ (void)start_test(nl);
+}
+
+
+static void lifetime_destructor(void *data)
+{
+ struct nat_lifetime *nl = data;
+
+ tmr_cancel(&nl->tmr);
+
+ mem_deref(nl->ctx);
+ mem_deref(nl->cty);
+ mem_deref(nl->us_x);
+ mem_deref(nl->us_y);
+ mem_deref(nl->stun);
+}
+
+
+/**
+ * Allocate a new NAT Lifetime discovery session
+ *
+ * @param nlp Pointer to allocated NAT Lifetime object
+ * @param srv STUN Server IP address and port number
+ * @param interval Initial interval in [seconds]
+ * @param conf STUN configuration (Optional)
+ * @param lh Lifetime handler - called for each probe
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_lifetime_alloc(struct nat_lifetime **nlp, const struct sa *srv,
+ uint32_t interval, const struct stun_conf *conf,
+ nat_lifetime_h *lh, void *arg)
+{
+ struct nat_lifetime *nl;
+ int err;
+
+ if (!nlp || !srv || !interval || !lh)
+ return EINVAL;
+
+ nl = mem_zalloc(sizeof(*nl), lifetime_destructor);
+ if (!nl)
+ return ENOMEM;
+
+ tmr_init(&nl->tmr);
+
+ err = stun_alloc(&nl->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ err = udp_listen(&nl->us_x, NULL, udp_recv_handler_x, nl);
+ if (err)
+ goto out;
+
+ err = udp_listen(&nl->us_y, NULL, udp_recv_handler_y, nl);
+ if (err)
+ goto out;
+
+ sa_cpy(&nl->srv, srv);
+ nl->interval.min = 1;
+ nl->interval.cur = interval;
+ nl->lh = lh;
+ nl->arg = arg;
+
+ out:
+ if (err)
+ mem_deref(nl);
+ else
+ *nlp = nl;
+
+ return err;
+}
+
+
+/**
+ * Start a new NAT Lifetime discovery session
+ *
+ * @param nl NAT Lifetime object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_lifetime_start(struct nat_lifetime *nl)
+{
+ if (!nl)
+ return EINVAL;
+
+ return start_test(nl);
+}
diff --git a/src/natbd/mapping.c b/src/natbd/mapping.c
new file mode 100644
index 0000000..e304560
--- /dev/null
+++ b/src/natbd/mapping.c
@@ -0,0 +1,354 @@
+/**
+ * @file mapping.c NAT Mapping Behaviour discovery
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_sa.h>
+#include <re_udp.h>
+#include <re_tcp.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+#define DEBUG_MODULE "natbd_mapping"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+/* Determining NAT Mapping Behavior
+
+ This will require at most three tests. In test I, the client
+ performs the UDP connectivity test. The server will return its
+ alternate address and port in OTHER-ADDRESS in the binding response.
+ If OTHER-ADDRESS is not returned, the server does not support this
+ usage and this test cannot be run. The client examines the XOR-
+ MAPPED-ADDRESS attribute. If this address and port are the same as
+ the local IP address and port of the socket used to send the request,
+ the client knows that it is not NATed and the effective mapping will
+ be Endpoint-Independent.
+
+ In test II, the client sends a Binding Request to the alternate
+ address, but primary port. If the XOR-MAPPED-ADDRESS in the Binding
+ Response is the same as test I the NAT currently has Endpoint-
+ Independent Mapping. If not, test III is performed: the client sends
+ a Binding Request to the alternate address and port. If the XOR-
+ MAPPED-ADDRESS matches test II, the NAT currently has Address-
+ Dependent Mapping; if it doesn't match it currently has Address and
+ Port-Dependent Mapping.
+ */
+
+
+/** Defines a NAT Mapping Behaviour Discovery session */
+struct nat_mapping {
+ struct stun *stun; /**< STUN Instance */
+ struct udp_sock *us; /**< UDP socket */
+ struct tcp_conn *tc; /**< TCP connection */
+ struct sa laddr; /**< Local IP address and port */
+ struct sa map[3]; /**< XOR Mapped address/ports */
+ struct sa srv; /**< STUN server address/port */
+ nat_mapping_h *mh; /**< Result handler */
+ void *arg; /**< Handler argument */
+ int proto; /**< IP Protocol */
+ uint32_t test_phase; /**< State machine */
+ struct tcp_conn *tcv[3]; /**< TCP Connections */
+};
+
+
+static int mapping_send(struct nat_mapping *nm);
+
+
+static void stun_response_handler(int err, uint16_t scode, const char *reason,
+ const struct stun_msg *msg, void *arg)
+{
+ struct nat_mapping *nm = arg;
+ struct stun_attr *map, *other;
+
+ if (err) {
+ DEBUG_WARNING("stun_response_handler: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ switch (scode) {
+
+ case 0:
+ other = stun_msg_attr(msg, STUN_ATTR_OTHER_ADDR);
+ map = stun_msg_attr(msg, STUN_ATTR_XOR_MAPPED_ADDR);
+ if (!map)
+ map = stun_msg_attr(msg, STUN_ATTR_MAPPED_ADDR);
+
+ if (!map || !other) {
+ DEBUG_WARNING("missing attributes: %s %s\n",
+ map ? "" : "MAPPED-ADDR",
+ other ? "" : "OTHER-ADDR");
+ nm->mh(EPROTO, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ nm->map[nm->test_phase-1] = map->v.sa;
+ break;
+
+ default:
+ DEBUG_WARNING("Binding Error Resp: %u %s\n", scode, reason);
+ nm->mh(EPROTO, NAT_TYPE_UNKNOWN, nm->arg);
+ return;
+ }
+
+ switch (nm->test_phase) {
+
+ case 1:
+ /* Test I completed */
+ if (sa_cmp(&nm->laddr, &nm->map[0], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ENDP_INDEP, nm->arg);
+ return;
+ }
+
+ /* Start Test II - the client sends a Binding Request to the
+ alternate *address* */
+ ++nm->test_phase;
+
+ sa_set_port(&other->v.other_addr, sa_port(&nm->srv));
+ sa_cpy(&nm->srv, &other->v.other_addr);
+
+ err = mapping_send(nm);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+ break;
+
+ case 2:
+ /* Test II completed */
+ if (sa_cmp(&nm->map[0], &nm->map[1], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ENDP_INDEP, nm->arg);
+ return;
+ }
+
+ /* Start Test III - the client sends a Binding Request
+ to the alternate address and port */
+ ++nm->test_phase;
+
+ sa_set_port(&nm->srv, sa_port(&other->v.other_addr));
+ err = mapping_send(nm);
+ if (err) {
+ DEBUG_WARNING("stunc_request_send: (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+ break;
+
+ case 3:
+ /* Test III completed */
+ if (sa_cmp(&nm->map[1], &nm->map[2], SA_ALL)) {
+ nm->mh(0, NAT_TYPE_ADDR_DEP, nm->arg);
+ }
+ else {
+ nm->mh(0, NAT_TYPE_ADDR_PORT_DEP, nm->arg);
+ }
+ ++nm->test_phase;
+ break;
+
+ default:
+ DEBUG_WARNING("invalid test phase %d\n", nm->test_phase);
+ nm->mh(EINVAL, NAT_TYPE_UNKNOWN, nm->arg);
+ break;
+ }
+}
+
+
+static int mapping_send(struct nat_mapping *nm)
+{
+ switch (nm->proto) {
+
+ case IPPROTO_UDP:
+ return stun_request(NULL, nm->stun, IPPROTO_UDP, nm->us,
+ &nm->srv, 0, STUN_METHOD_BINDING, NULL, 0,
+ false, stun_response_handler, nm, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ case IPPROTO_TCP:
+ nm->tc = mem_deref(nm->tc);
+ nm->tc = mem_ref(nm->tcv[nm->test_phase-1]);
+ return tcp_conn_connect(nm->tc, &nm->srv);
+
+ default:
+ return EPROTONOSUPPORT;
+ }
+}
+
+
+static void udp_recv_handler(const struct sa *src, struct mbuf *mb,
+ void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+ (void)src;
+
+ err = stun_recv(nm->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("udp_recv_handler: stunc_recv(): (%m)\n", err);
+ }
+}
+
+
+static void mapping_destructor(void *data)
+{
+ struct nat_mapping *nm = data;
+ int i;
+
+ mem_deref(nm->us);
+ mem_deref(nm->tc);
+
+ for (i=0; i<3; i++)
+ mem_deref(nm->tcv[i]);
+
+ mem_deref(nm->stun);
+}
+
+
+static void tcp_estab_handler(void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+
+ err = stun_request(NULL, nm->stun, IPPROTO_TCP, nm->tc, NULL, 0,
+ STUN_METHOD_BINDING, NULL, 0, false,
+ stun_response_handler, nm, 1,
+ STUN_ATTR_SOFTWARE, stun_software);
+
+ if (err) {
+ DEBUG_WARNING("TCP established: mapping_send (%m)\n", err);
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+}
+
+
+static void tcp_recv_handler(struct mbuf *mb, void *arg)
+{
+ struct nat_mapping *nm = arg;
+ int err;
+
+ err = stun_recv(nm->stun, mb);
+ if (err && ENOENT != err) {
+ DEBUG_WARNING("stunc recv: %m\n", err);
+ }
+}
+
+
+static void tcp_close_handler(int err, void *arg)
+{
+ struct nat_mapping *nm = arg;
+
+ DEBUG_NOTICE("TCP Connection Closed (%m)\n", err);
+
+ if (err) {
+ nm->mh(err, NAT_TYPE_UNKNOWN, nm->arg);
+ }
+}
+
+
+/**
+ * Allocate a new NAT Mapping Behaviour Discovery session
+ *
+ * @param nmp Pointer to allocated NAT Mapping object
+ * @param laddr Local IP address
+ * @param srv STUN Server IP address and port
+ * @param proto Transport protocol
+ * @param conf STUN configuration (Optional)
+ * @param mh Mapping handler
+ * @param arg Handler argument
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_mapping_alloc(struct nat_mapping **nmp, const struct sa *laddr,
+ const struct sa *srv, int proto,
+ const struct stun_conf *conf,
+ nat_mapping_h *mh, void *arg)
+{
+ struct nat_mapping *nm;
+ int i, err;
+
+ if (!nmp || !laddr || !srv || !mh)
+ return EINVAL;
+
+ nm = mem_zalloc(sizeof(*nm), mapping_destructor);
+ if (!nm)
+ return ENOMEM;
+
+ err = stun_alloc(&nm->stun, conf, NULL, NULL);
+ if (err)
+ goto out;
+
+ nm->proto = proto;
+ sa_cpy(&nm->laddr, laddr);
+
+ switch (proto) {
+
+ case IPPROTO_UDP:
+ err = udp_listen(&nm->us, &nm->laddr, udp_recv_handler, nm);
+ if (err)
+ goto out;
+ err = udp_local_get(nm->us, &nm->laddr);
+ if (err)
+ goto out;
+ break;
+
+ case IPPROTO_TCP:
+
+ /* Allocate and bind 3 TCP Sockets */
+ for (i=0; i<3; i++) {
+ err = tcp_conn_alloc(&nm->tcv[i], srv,
+ tcp_estab_handler,
+ tcp_recv_handler,
+ tcp_close_handler, nm);
+ if (err)
+ goto out;
+
+ err = tcp_conn_bind(nm->tcv[i], &nm->laddr);
+ if (err)
+ goto out;
+
+ err = tcp_conn_local_get(nm->tcv[i], &nm->laddr);
+ if (err)
+ goto out;
+ }
+ break;
+
+ default:
+ err = EPROTONOSUPPORT;
+ goto out;
+ }
+
+ sa_cpy(&nm->srv, srv);
+ nm->mh = mh;
+ nm->arg = arg;
+
+ *nmp = nm;
+
+ out:
+ if (err)
+ mem_deref(nm);
+ return err;
+}
+
+
+/**
+ * Start a new NAT Mapping Behaviour Discovery session
+ *
+ * @param nm NAT Mapping object
+ *
+ * @return 0 if success, errorcode if failure
+ */
+int nat_mapping_start(struct nat_mapping *nm)
+{
+ if (!nm)
+ return EINVAL;
+
+ nm->test_phase = 1;
+
+ return mapping_send(nm);
+}
diff --git a/src/natbd/mod.mk b/src/natbd/mod.mk
new file mode 100644
index 0000000..7a267fe
--- /dev/null
+++ b/src/natbd/mod.mk
@@ -0,0 +1,12 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+SRCS += natbd/filtering.c
+SRCS += natbd/genalg.c
+SRCS += natbd/hairpinning.c
+SRCS += natbd/lifetime.c
+SRCS += natbd/mapping.c
+SRCS += natbd/natstr.c
diff --git a/src/natbd/natstr.c b/src/natbd/natstr.c
new file mode 100644
index 0000000..96a8fa8
--- /dev/null
+++ b/src/natbd/natstr.c
@@ -0,0 +1,32 @@
+/**
+ * @file natstr.c NAT Behaviour Discovery strings
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_stun.h>
+#include <re_natbd.h>
+
+
+/**
+ * Get the name of the NAT Mapping/Filtering type
+ *
+ * @param type NAT Mapping/Filtering type
+ *
+ * @return Name of the NAT Mapping/Filtering type
+ */
+const char *nat_type_str(enum nat_type type)
+{
+ switch (type) {
+
+ case NAT_TYPE_UNKNOWN: return "Unknown";
+ case NAT_TYPE_ENDP_INDEP: return "Endpoint Independent";
+ case NAT_TYPE_ADDR_DEP: return "Address Dependent";
+ case NAT_TYPE_ADDR_PORT_DEP: return "Address and Port Dependent";
+ default: return "???";
+ }
+}