summaryrefslogtreecommitdiff
path: root/modules/pcp
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2016-07-24 16:15:32 +0200
committerAlfred E. Heggestad <aeh@db.org>2016-07-24 16:15:32 +0200
commit0a987e0a95b3473f8bc5e70455c92232d9990114 (patch)
tree3fa45e8211a7de9e1f6f60ca56029b7101ce9d67 /modules/pcp
parent8f7ec5c701feb01521b3b97e75778fd96fcc7981 (diff)
pcp: new module for Port Control Protocol (PCP)
Diffstat (limited to 'modules/pcp')
-rw-r--r--modules/pcp/listener.c124
-rw-r--r--modules/pcp/module.mk12
-rw-r--r--modules/pcp/pcp.c368
-rw-r--r--modules/pcp/pcp.h15
4 files changed, 519 insertions, 0 deletions
diff --git a/modules/pcp/listener.c b/modules/pcp/listener.c
new file mode 100644
index 0000000..7747672
--- /dev/null
+++ b/modules/pcp/listener.c
@@ -0,0 +1,124 @@
+/**
+ * @file listener.c Port Control Protocol module -- multicast listener
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+#include <re.h>
+#include <rew.h>
+#include <baresip.h>
+#include "pcp.h"
+
+
+/*
+ * Listen for incoming notifications on unicast/multicast port 5350
+ */
+
+
+struct pcp_listener {
+ struct udp_sock *us;
+ struct sa srv;
+ struct sa group;
+ pcp_msg_h *msgh;
+ void *arg;
+};
+
+
+static void destructor(void *arg)
+{
+ struct pcp_listener *pl = arg;
+
+ if (sa_isset(&pl->group, SA_ADDR))
+ (void)udp_multicast_leave(pl->us, &pl->group);
+
+ mem_deref(pl->us);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+ struct pcp_listener *pl = arg;
+ struct pcp_msg *msg;
+ int err;
+
+#if 0
+ if (!sa_cmp(src, &pl->srv, SA_ADDR)) {
+ debug("pcp: listener: ignore %zu bytes from non-server %J\n",
+ mb->end, src);
+ return;
+ }
+#endif
+
+ err = pcp_msg_decode(&msg, mb);
+ if (err)
+ return;
+
+ /* Validate PCP request */
+ if (!msg->hdr.resp) {
+ info("pcp: listener: ignore request from %J\n", src);
+ goto out;
+ }
+
+ if (pl->msgh)
+ pl->msgh(msg, pl->arg);
+
+ out:
+ mem_deref(msg);
+}
+
+
+int pcp_listen(struct pcp_listener **plp, const struct sa *srv,
+ pcp_msg_h *msgh, void *arg)
+{
+ struct pcp_listener *pl;
+ struct sa laddr;
+ int err;
+
+ if (!plp || !srv || !msgh)
+ return EINVAL;
+
+ pl = mem_zalloc(sizeof(*pl), destructor);
+ if (!pl)
+ return ENOMEM;
+
+ pl->srv = *srv;
+ pl->msgh = msgh;
+ pl->arg = arg;
+
+ /* note: must listen on ANY to get multicast working */
+ sa_init(&laddr, sa_af(srv));
+ sa_set_port(&laddr, PCP_PORT_CLI);
+
+ err = udp_listen(&pl->us, &laddr, udp_recv, pl);
+ if (err)
+ goto out;
+
+ switch (sa_af(&laddr)) {
+
+ case AF_INET:
+ err = sa_set_str(&pl->group, "224.0.0.1", 0);
+ break;
+
+ case AF_INET6:
+ err = sa_set_str(&pl->group, "ff02::1", 0);
+ break;
+
+ default:
+ err = EAFNOSUPPORT;
+ break;
+ }
+ if (err)
+ goto out;
+
+ err = udp_multicast_join(pl->us, &pl->group);
+ if (err)
+ goto out;
+
+ out:
+ if (err)
+ mem_deref(pl);
+ else
+ *plp = pl;
+
+ return err;
+}
diff --git a/modules/pcp/module.mk b/modules/pcp/module.mk
new file mode 100644
index 0000000..99a7c34
--- /dev/null
+++ b/modules/pcp/module.mk
@@ -0,0 +1,12 @@
+#
+# module.mk
+#
+# Copyright (C) 2010 Creytiv.com
+#
+
+MOD := pcp
+$(MOD)_SRCS += pcp.c listener.c
+$(MOD)_CFLAGS += -I$(SYSROOT)/local/include/rew
+$(MOD)_LFLAGS += -lrew
+
+include mk/mod.mk
diff --git a/modules/pcp/pcp.c b/modules/pcp/pcp.c
new file mode 100644
index 0000000..bafeab0
--- /dev/null
+++ b/modules/pcp/pcp.c
@@ -0,0 +1,368 @@
+/**
+ * @file pcp.c Port Control Protocol for Media NAT-traversal
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re.h>
+#include <rew.h>
+#include <baresip.h>
+#include "pcp.h"
+
+
+/**
+ * @defgroup pcp pcp
+ *
+ * Port Control Protocol (PCP)
+ *
+ * This module implements the medianat interface with PCP, which is
+ * the successor of the NAT-PMP protocol.
+ */
+
+
+enum {
+ LIFETIME = 120 /* seconds */
+};
+
+struct mnat_sess {
+ struct le le;
+ struct list medial;
+ mnat_estab_h *estabh;
+ void *arg;
+};
+
+struct mnat_media {
+
+ struct comp {
+ struct pcp_request *pcp;
+ struct mnat_media *media; /* pointer to parent */
+ unsigned id;
+ bool granted;
+ } compv[2];
+ unsigned compc;
+
+ struct le le;
+ struct mnat_sess *sess;
+ struct sdp_media *sdpm;
+ uint32_t srv_epoch;
+};
+
+
+static struct mnat *mnat;
+static struct sa pcp_srv;
+static struct list sessl;
+static struct pcp_listener *lsnr;
+
+
+static void session_destructor(void *arg)
+{
+ struct mnat_sess *sess = arg;
+
+ list_unlink(&sess->le);
+ list_flush(&sess->medial);
+}
+
+
+static void media_destructor(void *arg)
+{
+ struct mnat_media *m = arg;
+ unsigned i;
+
+ list_unlink(&m->le);
+
+ for (i=0; i<m->compc; i++) {
+ struct comp *comp = &m->compv[i];
+
+ mem_deref(comp->pcp);
+ }
+
+ mem_deref(m->sdpm);
+}
+
+
+static void complete(struct mnat_sess *sess, int err, const char *reason)
+{
+ mnat_estab_h *estabh = sess->estabh;
+ void *arg = sess->arg;
+
+ sess->estabh = NULL;
+
+ if (estabh) {
+ estabh(err, 0, reason, arg);
+ }
+}
+
+
+static bool all_components_granted(const struct mnat_media *m)
+{
+ unsigned i;
+
+ if (!m || !m->compc)
+ return false;
+
+ for (i=0; i<m->compc; i++) {
+ const struct comp *comp = &m->compv[i];
+ if (!comp->granted)
+ return false;
+ }
+
+ return true;
+}
+
+
+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 (!all_components_granted(m))
+ return;
+ }
+
+ complete(sess, 0, "done");
+}
+
+
+/* todo: detect multiple responses */
+static void pcp_resp_handler(int err, struct pcp_msg *msg, void *arg)
+{
+ struct comp *comp = arg;
+ struct mnat_media *m = comp->media;
+ const struct pcp_map *map;
+
+ if (err) {
+ warning("pcp: mapping error: %m\n", err);
+
+ complete(m->sess, err, NULL);
+ return;
+ }
+ else if (msg->hdr.result != PCP_SUCCESS) {
+ warning("pcp: mapping error: %s\n",
+ pcp_result_name(msg->hdr.result));
+
+ re_printf("%H\n", pcp_msg_print, msg);
+
+ complete(m->sess, EPROTO, "pcp error");
+ return;
+ }
+
+ map = pcp_msg_payload(msg);
+
+ info("pcp: %s: mapping granted for %s:"
+ " internal_port=%u, external_addr=%J (lifetime %u seconds)\n",
+ sdp_media_name(m->sdpm),
+ comp->id==1 ? "RTP" : "RTCP",
+ map->int_port, &map->ext_addr,
+ msg->hdr.lifetime);
+
+ /* Update SDP media with external IP-address mapping */
+ if (comp->id == 1)
+ sdp_media_set_laddr(m->sdpm, &map->ext_addr);
+ else
+ sdp_media_set_laddr_rtcp(m->sdpm, &map->ext_addr);
+
+ comp->granted = true;
+ m->srv_epoch = msg->hdr.epoch;
+
+ 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;
+
+ list_append(&sessl, &sess->le, sess);
+
+ 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;
+ struct pcp_map map;
+ unsigned i;
+ int err = 0;
+
+ if (!mp || !sess || !sdpm || proto != IPPROTO_UDP)
+ return EINVAL;
+
+ m = mem_zalloc(sizeof(*m), media_destructor);
+ if (!m)
+ return ENOMEM;
+
+ m->compc = sock2 ? 2 : 1;
+
+ list_append(&sess->medial, &m->le, m);
+ m->sess = sess;
+ m->sdpm = mem_ref(sdpm);
+
+ for (i=0; i<m->compc; i++) {
+ struct comp *comp = &m->compv[i];
+
+ comp->id = i+1;
+ comp->media = m;
+
+ err = udp_local_get(i==0 ? sock1 : sock2, &laddr);
+ if (err)
+ goto out;
+
+ rand_bytes(map.nonce, sizeof(map.nonce));
+ map.proto = proto;
+ map.int_port = sa_port(&laddr);
+ /* note: using same address-family as the PCP server */
+ sa_init(&map.ext_addr, sa_af(&pcp_srv));
+
+ info("pcp: %s: internal port for %s is %u\n",
+ sdp_media_name(sdpm),
+ i==0 ? "RTP" : "RTCP",
+ map.int_port);
+
+ err = pcp_request(&comp->pcp, NULL, &pcp_srv, PCP_MAP,
+ LIFETIME, &map, pcp_resp_handler, comp, 0);
+ if (err)
+ goto out;
+ }
+
+ out:
+ if (err)
+ mem_deref(m);
+ else if (mp) {
+ *mp = m;
+ }
+
+ return err;
+}
+
+
+static void media_refresh(struct mnat_media *media)
+{
+ unsigned i;
+
+ for (i=0; i<media->compc; i++) {
+ struct comp *comp = &media->compv[i];
+
+ pcp_force_refresh(comp->pcp);
+ }
+}
+
+
+static void refresh_session(struct mnat_sess *sess, uint32_t epoch_time)
+{
+ struct le *le;
+
+ for (le = sess->medial.head; le; le = le->next) {
+
+ struct mnat_media *m = le->data;
+
+ if (epoch_time < m->srv_epoch) {
+ info("pcp: detected PCP Server reboot!\n");
+ media_refresh(m);
+ }
+
+ m->srv_epoch = epoch_time;
+ }
+}
+
+
+static void pcp_msg_handler(const struct pcp_msg *msg, void *arg)
+{
+ struct le *le;
+
+ (void)arg;
+
+ info("pcp: received notification: %H\n", pcp_msg_print, msg);
+
+ if (msg->hdr.opcode == PCP_ANNOUNCE) {
+
+ for (le = sessl.head; le; le = le->next) {
+
+ struct mnat_sess *sess = le->data;
+
+ refresh_session(sess, msg->hdr.epoch);
+ }
+ }
+}
+
+
+static int module_init(void)
+{
+ struct pl pl;
+ int err;
+
+ if (0 == conf_get(conf_cur(), "pcp_server", &pl)) {
+ err = sa_decode(&pcp_srv, pl.p, pl.l);
+ if (err)
+ return err;
+ }
+ else {
+ err = net_default_gateway_get(net_af(baresip_network()),
+ &pcp_srv);
+ if (err)
+ return err;
+ sa_set_port(&pcp_srv, PCP_PORT_SRV);
+ }
+
+ info("pcp: using PCP server at %J\n", &pcp_srv);
+
+#if 1
+ /* todo: if multiple applications are listening on port 5350
+ then this will not work */
+ err = pcp_listen(&lsnr, &pcp_srv, pcp_msg_handler, 0);
+ if (err) {
+ info("pcp: could not enable listener: %m\n", err);
+ err = 0;
+ }
+#endif
+
+ return mnat_register(&mnat, "pcp", NULL,
+ session_alloc, media_alloc, NULL);
+}
+
+
+static int module_close(void)
+{
+ lsnr = mem_deref(lsnr);
+ mnat = mem_deref(mnat);
+ return 0;
+}
+
+
+EXPORT_SYM const struct mod_export DECL_EXPORTS(pcp) = {
+ "pcp",
+ "mnat",
+ module_init,
+ module_close,
+};
diff --git a/modules/pcp/pcp.h b/modules/pcp/pcp.h
new file mode 100644
index 0000000..6f9e8a9
--- /dev/null
+++ b/modules/pcp/pcp.h
@@ -0,0 +1,15 @@
+/**
+ * @file pcp.h Port Control Protocol module -- internal interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+/* listener */
+
+struct pcp_listener;
+
+typedef void (pcp_msg_h)(const struct pcp_msg *msg, void *arg);
+
+int pcp_listen(struct pcp_listener **plp, const struct sa *srv,
+ pcp_msg_h *msgh, void *arg);