summaryrefslogtreecommitdiff
path: root/modules/natpmp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/natpmp')
-rw-r--r--modules/natpmp/libnatpmp.c235
-rw-r--r--modules/natpmp/libnatpmp.h53
-rw-r--r--modules/natpmp/module.mk10
-rw-r--r--modules/natpmp/natpmp.c313
4 files changed, 611 insertions, 0 deletions
diff --git a/modules/natpmp/libnatpmp.c b/modules/natpmp/libnatpmp.c
new file mode 100644
index 0000000..7969007
--- /dev/null
+++ b/modules/natpmp/libnatpmp.c
@@ -0,0 +1,235 @@
+/**
+ * @file libnatpmp.c NAT-PMP Client library
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <string.h>
+#include <re.h>
+#include <baresip.h>
+#include "libnatpmp.h"
+
+
+enum {
+ NATPMP_DELAY = 250,
+ NATPMP_MAXTX = 9,
+};
+
+struct natpmp_req {
+ struct natpmp_req **npp;
+ struct udp_sock *us;
+ struct tmr tmr;
+ struct mbuf *mb;
+ struct sa srv;
+ unsigned n;
+ natpmp_resp_h *resph;
+ void *arg;
+};
+
+
+static void completed(struct natpmp_req *np, int err,
+ const struct natpmp_resp *resp)
+{
+ natpmp_resp_h *resph = np->resph;
+ void *arg = np->arg;
+
+ tmr_cancel(&np->tmr);
+
+ if (np->npp) {
+ *np->npp = NULL;
+ np->npp = NULL;
+ }
+
+ np->resph = NULL;
+
+ /* must be destroyed before calling handler */
+ mem_deref(np);
+
+ if (resph)
+ resph(err, resp, arg);
+}
+
+
+static void destructor(void *arg)
+{
+ struct natpmp_req *np = arg;
+
+ tmr_cancel(&np->tmr);
+ mem_deref(np->us);
+ mem_deref(np->mb);
+}
+
+
+static void timeout(void *arg)
+{
+ struct natpmp_req *np = arg;
+ int err;
+
+ if (np->n > NATPMP_MAXTX) {
+ completed(np, ETIMEDOUT, NULL);
+ return;
+ }
+
+ tmr_start(&np->tmr, NATPMP_DELAY<<np->n, timeout, arg);
+
+#if 1
+ debug("natpmp: {n=%u} tx %u bytes\n", np->n, np->mb->end);
+#endif
+
+ np->n++;
+
+ np->mb->pos = 0;
+ err = udp_send(np->us, &np->srv, np->mb);
+ if (err) {
+ completed(np, err, NULL);
+ }
+}
+
+
+static int resp_decode(struct natpmp_resp *resp, struct mbuf *mb)
+{
+ resp->vers = mbuf_read_u8(mb);
+ resp->op = mbuf_read_u8(mb);
+ resp->result = ntohs(mbuf_read_u16(mb));
+ resp->epoch = ntohl(mbuf_read_u32(mb));
+
+ if (!(resp->op & 0x80))
+ return EPROTO;
+ resp->op &= ~0x80;
+
+ switch (resp->op) {
+
+ case NATPMP_OP_EXTERNAL:
+ resp->u.ext_addr = ntohl(mbuf_read_u32(mb));
+ break;
+
+ case NATPMP_OP_MAPPING_UDP:
+ case NATPMP_OP_MAPPING_TCP:
+ resp->u.map.int_port = ntohs(mbuf_read_u16(mb));
+ resp->u.map.ext_port = ntohs(mbuf_read_u16(mb));
+ resp->u.map.lifetime = ntohl(mbuf_read_u32(mb));
+ break;
+
+ default:
+ warning("natmap: unknown opcode %d\n", resp->op);
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct natpmp_req *np = arg;
+ struct natpmp_resp resp;
+
+ if (!sa_cmp(src, &np->srv, SA_ALL))
+ return;
+
+ if (resp_decode(&resp, mb))
+ return;
+
+ completed(np, 0, &resp);
+}
+
+
+static int natpmp_init(struct natpmp_req *np, const struct sa *srv,
+ uint8_t opcode, natpmp_resp_h *resph, void *arg)
+{
+ int err;
+
+ if (!np || !srv)
+ return EINVAL;
+
+ /* a new UDP socket for each NAT-PMP request */
+ err = udp_listen(&np->us, NULL, udp_recv, np);
+ if (err)
+ return err;
+
+ np->srv = *srv;
+ np->resph = resph;
+ np->arg = arg;
+
+ udp_connect(np->us, srv);
+
+ np->mb = mbuf_alloc(512);
+ if (!np->mb)
+ return ENOMEM;
+
+ err |= mbuf_write_u8(np->mb, NATPMP_VERSION);
+ err |= mbuf_write_u8(np->mb, opcode);
+
+ return err;
+}
+
+
+int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv,
+ natpmp_resp_h *resph, void *arg)
+{
+ struct natpmp_req *np;
+ int err;
+
+ np = mem_zalloc(sizeof(*np), destructor);
+ if (!np)
+ return ENOMEM;
+
+ err = natpmp_init(np, srv, NATPMP_OP_EXTERNAL, resph, arg);
+ if (err)
+ goto out;
+
+ timeout(np);
+
+ out:
+ if (err)
+ mem_deref(np);
+ else if (npp) {
+ np->npp = npp;
+ *npp = np;
+ }
+ else {
+ /* Destroy the transaction now */
+ mem_deref(np);
+ }
+
+ return err;
+}
+
+
+int natpmp_mapping_request(struct natpmp_req **npp, const struct sa *srv,
+ uint16_t int_port, uint16_t ext_port,
+ uint32_t lifetime, natpmp_resp_h *resph, void *arg)
+{
+ struct natpmp_req *np;
+ int err;
+
+ np = mem_zalloc(sizeof(*np), destructor);
+ if (!np)
+ return ENOMEM;
+
+ err = natpmp_init(np, srv, NATPMP_OP_MAPPING_UDP, resph, arg);
+ if (err)
+ goto out;
+
+ err |= mbuf_write_u16(np->mb, 0x0000);
+ err |= mbuf_write_u16(np->mb, htons(int_port));
+ err |= mbuf_write_u16(np->mb, htons(ext_port));
+ err |= mbuf_write_u32(np->mb, htonl(lifetime));
+ if (err)
+ goto out;
+
+ timeout(np);
+
+ out:
+ if (err)
+ mem_deref(np);
+ else if (npp) {
+ np->npp = npp;
+ *npp = np;
+ }
+ else {
+ /* Destroy the transaction now */
+ mem_deref(np);
+ }
+
+ return err;
+}
diff --git a/modules/natpmp/libnatpmp.h b/modules/natpmp/libnatpmp.h
new file mode 100644
index 0000000..d1cc33c
--- /dev/null
+++ b/modules/natpmp/libnatpmp.h
@@ -0,0 +1,53 @@
+/**
+ * @file libnatpmp.h Interface to NAT-PMP Client library
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+
+
+enum {
+ NATPMP_VERSION = 0,
+ NATPMP_PORT = 5351,
+};
+
+enum natpmp_op {
+ NATPMP_OP_EXTERNAL = 0,
+ NATPMP_OP_MAPPING_UDP = 1,
+ NATPMP_OP_MAPPING_TCP = 2,
+};
+
+enum natpmp_result {
+ NATPMP_SUCCESS = 0,
+ NATPMP_UNSUP_VERSION = 1,
+ NATPMP_REFUSED = 2,
+ NATPMP_NETWORK_FAILURE = 3,
+ NATPMP_OUT_OF_RESOURCES = 4,
+ NATPMP_UNSUP_OPCODE = 5
+};
+
+struct natpmp_resp {
+ uint8_t vers;
+ uint8_t op;
+ uint16_t result;
+ uint32_t epoch;
+
+ union {
+ uint32_t ext_addr;
+ struct {
+ uint16_t int_port;
+ uint16_t ext_port;
+ uint32_t lifetime;
+ } map;
+ } u;
+};
+
+struct natpmp_req;
+
+typedef void (natpmp_resp_h)(int err, const struct natpmp_resp *resp,
+ void *arg);
+
+int natpmp_external_request(struct natpmp_req **npp, const struct sa *srv,
+ natpmp_resp_h *h, void *arg);
+int natpmp_mapping_request(struct natpmp_req **natpmpp, const struct sa *srv,
+ uint16_t int_port, uint16_t ext_port,
+ uint32_t lifetime, natpmp_resp_h *resph, void *arg);
diff --git a/modules/natpmp/module.mk b/modules/natpmp/module.mk
new file mode 100644
index 0000000..3087c77
--- /dev/null
+++ b/modules/natpmp/module.mk
@@ -0,0 +1,10 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := natpmp
+$(MOD)_SRCS += natpmp.c libnatpmp.c
+
+include mk/mod.mk
diff --git a/modules/natpmp/natpmp.c b/modules/natpmp/natpmp.c
new file mode 100644
index 0000000..04b45f0
--- /dev/null
+++ b/modules/natpmp/natpmp.c
@@ -0,0 +1,313 @@
+/**
+ * @file natpmp.c NAT-PMP Module for Media NAT-traversal
+ *
+ * Copyright (C) 2010 Creytiv.com
+ */
+#include <re.h>
+#include <baresip.h>
+#include "libnatpmp.h"
+
+
+/**
+ * @defgroup natpmp natpmp
+ *
+ * NAT Port Mapping Protocol (NAT-PMP)
+ *
+ * https://tools.ietf.org/html/rfc6886
+ */
+
+enum {
+ LIFETIME = 300 /* seconds */
+};
+
+struct mnat_sess {
+ struct list medial;
+ mnat_estab_h *estabh;
+ void *arg;
+};
+
+struct mnat_media {
+ struct le le;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ struct natpmp_req *natpmp;
+ struct tmr tmr;
+ uint16_t int_port;
+ uint32_t lifetime;
+ bool granted;
+};
+
+
+static struct mnat *mnat;
+static struct sa natpmp_srv, natpmp_extaddr;
+static struct natpmp_req *natpmp_ext;
+
+
+static void natpmp_resp_handler(int err, const struct natpmp_resp *resp,
+ void *arg);
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_flush(&sess->medial);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ /* Destroy the mapping */
+ if (m->granted) {
+ (void)natpmp_mapping_request(NULL, &natpmp_srv,
+ m->int_port, 0, 0, NULL, NULL);
+ }
+
+ list_unlink(&m->le);
+ tmr_cancel(&m->tmr);
+ mem_deref(m->sdpm);
+ mem_deref(m->natpmp);
+}
+
+
+static void is_complete(struct mnat_sess *sess)
+{
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+
+ if (!m->granted)
+ return;
+ }
+
+ if (sess->estabh) {
+ sess->estabh(0, 0, "done", sess->arg);
+
+ sess->estabh = NULL;
+ }
+}
+
+
+static void refresh_timeout(void *arg)
+{
+ struct mnat_media *m = arg;
+
+ m->natpmp = mem_deref(m->natpmp);
+ (void)natpmp_mapping_request(&m->natpmp, &natpmp_srv,
+ m->int_port, 0, m->lifetime,
+ natpmp_resp_handler, m);
+}
+
+
+static void natpmp_resp_handler(int err, const struct natpmp_resp *resp,
+ void *arg)
+{
+ struct mnat_media *m = arg;
+ struct sa map_addr;
+
+ if (err) {
+ warning("natpmp: response error: %m\n", err);
+ return;
+ }
+
+ if (resp->op != NATPMP_OP_MAPPING_UDP)
+ return;
+ if (resp->result != NATPMP_SUCCESS) {
+ warning("natpmp: request failed with result code: %d\n",
+ resp->result);
+ return;
+ }
+
+ if (resp->u.map.int_port != m->int_port) {
+ info("natpmp: ignoring response for internal_port=%u\n",
+ resp->u.map.int_port);
+ return;
+ }
+
+ info("natpmp: mapping granted:"
+ " internal_port=%u, external_port=%u, lifetime=%u\n",
+ resp->u.map.int_port, resp->u.map.ext_port,
+ resp->u.map.lifetime);
+
+ map_addr = natpmp_extaddr;
+ sa_set_port(&map_addr, resp->u.map.ext_port);
+ m->lifetime = resp->u.map.lifetime;
+
+ /* Update SDP media with external IP-address mapping */
+ sdp_media_set_laddr(m->sdpm, &map_addr);
+
+ m->granted = true;
+
+ tmr_start(&m->tmr, m->lifetime * 1000 * 3/4, refresh_timeout, m);
+
+ is_complete(m->sess);
+}
+
+
+static int session_alloc(struct mnat_sess **sessp, struct dnsc *dnsc,
+ int af, const char *srv, uint16_t port,
+ const char *user, const char *pass,
+ struct sdp_session *ss, bool offerer,
+ mnat_estab_h *estabh, void *arg)
+{
+ struct mnat_sess *sess;
+ int err = 0;
+ (void)af;
+ (void)port;
+ (void)user;
+ (void)pass;
+ (void)ss;
+ (void)offerer;
+
+ if (!sessp || !dnsc || !srv || !ss || !estabh)
+ return EINVAL;
+
+ sess = mem_zalloc(sizeof(*sess), session_destructor);
+ if (!sess)
+ return ENOMEM;
+
+ sess->estabh = estabh;
+ sess->arg = arg;
+
+ if (err)
+ mem_deref(sess);
+ else
+ *sessp = sess;
+
+ return err;
+}
+
+
+static int media_alloc(struct mnat_media **mp, struct mnat_sess *sess,
+ int proto, void *sock1, void *sock2,
+ struct sdp_media *sdpm)
+{
+ struct mnat_media *m;
+ struct sa laddr;
+ int err = 0;
+ (void)sock2;
+
+ if (!mp || !sess || !sdpm || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sess = sess;
+ m->sdpm = mem_ref(sdpm);
+ m->lifetime = LIFETIME;
+
+ err = udp_local_get(sock1, &laddr);
+ if (err)
+ goto out;
+
+ m->int_port = sa_port(&laddr);
+
+ info("natpmp: local UDP port is %u\n", m->int_port);
+
+ err = natpmp_mapping_request(&m->natpmp, &natpmp_srv,
+ m->int_port, 0, m->lifetime,
+ natpmp_resp_handler, m);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(m);
+ else {
+ *mp = m;
+ }
+
+ return err;
+}
+
+
+static void extaddr_handler(int err, const struct natpmp_resp *resp, void *arg)
+{
+ (void)arg;
+
+ if (err) {
+ warning("natpmp: external address ERROR: %m\n", err);
+ return;
+ }
+
+ if (resp->result != NATPMP_SUCCESS) {
+ warning("natpmp: external address failed"
+ " with result code: %d\n", resp->result);
+ return;
+ }
+
+ if (resp->op != NATPMP_OP_EXTERNAL)
+ return;
+
+ sa_set_in(&natpmp_extaddr, resp->u.ext_addr, 0);
+
+ info("natpmp: discovered External address: %j\n", &natpmp_extaddr);
+}
+
+
+static bool net_rt_handler(const char *ifname, const struct sa *dst,
+ int dstlen, const struct sa *gw, void *arg)
+{
+ (void)dstlen;
+ (void)arg;
+
+ if (sa_af(dst) != AF_INET)
+ return false;
+
+ if (sa_in(dst) == 0) {
+ natpmp_srv = *gw;
+ sa_set_port(&natpmp_srv, NATPMP_PORT);
+ info("natpmp: found default gateway %j on interface '%s'\n",
+ gw, ifname);
+ return true;
+ }
+
+ return false;
+}
+
+
+static int module_init(void)
+{
+ int err;
+
+ sa_init(&natpmp_srv, AF_INET);
+ sa_set_port(&natpmp_srv, NATPMP_PORT);
+
+ net_rt_list(net_rt_handler, NULL);
+
+ conf_get_sa(conf_cur(), "natpmp_server", &natpmp_srv);
+
+ info("natpmp: using NAT-PMP server at %J\n", &natpmp_srv);
+
+ err = natpmp_external_request(&natpmp_ext, &natpmp_srv,
+ extaddr_handler, NULL);
+ if (err)
+ return err;
+
+ return mnat_register(&mnat, "natpmp", NULL,
+ session_alloc, media_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ mnat = mem_deref(mnat);
+ natpmp_ext = mem_deref(natpmp_ext);
+
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(natpmp) = {
+ "natpmp",
+ "mnat",
+ module_init,
+ module_close,
+};