diff options
Diffstat (limited to 'modules/natpmp')
-rw-r--r-- | modules/natpmp/libnatpmp.c | 235 | ||||
-rw-r--r-- | modules/natpmp/libnatpmp.h | 53 | ||||
-rw-r--r-- | modules/natpmp/module.mk | 10 | ||||
-rw-r--r-- | modules/natpmp/natpmp.c | 313 |
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, +}; |