Squashed 'third_party/rawrtc/rew/' content from commit 24c91fd83

Change-Id: Ica2fcc790472ecd5b195d20da982c4e84139cbdd
git-subtree-dir: third_party/rawrtc/rew
git-subtree-split: 24c91fd839b40b11f727c902fa46d20874da33fb
diff --git a/src/pcp/README b/src/pcp/README
new file mode 100644
index 0000000..620a614
--- /dev/null
+++ b/src/pcp/README
@@ -0,0 +1,13 @@
+PCP README:
+----------
+
+Port Control Protocol (PCP) as of RFC 6887
+
+
+
+
+other PCP implementations:
+
+  https://github.com/libpcp/pcp
+
+
diff --git a/src/pcp/mod.mk b/src/pcp/mod.mk
new file mode 100644
index 0000000..2a1dc95
--- /dev/null
+++ b/src/pcp/mod.mk
@@ -0,0 +1,12 @@
+#
+# mod.mk
+#
+# Copyright (C) 2010 - 2016 Creytiv.com
+#
+
+SRCS	+= pcp/msg.c
+SRCS	+= pcp/option.c
+SRCS	+= pcp/payload.c
+SRCS	+= pcp/pcp.c
+SRCS	+= pcp/reply.c
+SRCS	+= pcp/request.c
diff --git a/src/pcp/msg.c b/src/pcp/msg.c
new file mode 100644
index 0000000..327f254
--- /dev/null
+++ b/src/pcp/msg.c
@@ -0,0 +1,378 @@
+/**
+ * @file pcp/msg.c  PCP messages
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_map_decode(struct pcp_map *map, struct mbuf *mb)
+{
+	uint16_t port;
+	int err;
+
+	if (!map || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_MAP_SZ)
+		return EBADMSG;
+
+	(void)mbuf_read_mem(mb, map->nonce, sizeof(map->nonce));
+	map->proto = mbuf_read_u8(mb); mbuf_advance(mb, 3);
+	map->int_port = ntohs(mbuf_read_u16(mb));
+
+	port = ntohs(mbuf_read_u16(mb));
+	err = pcp_ipaddr_decode(mb, &map->ext_addr);
+	sa_set_port(&map->ext_addr, port);
+
+	return err;
+}
+
+
+static int pcp_peer_decode(struct pcp_peer *peer, struct mbuf *mb)
+{
+	uint16_t port;
+	int err = 0;
+
+	if (!peer || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_PEER_SZ)
+		return EBADMSG;
+
+	/* note: the MAP and PEER opcodes are quite similar */
+	err = pcp_map_decode(&peer->map, mb);
+	if (err)
+		return err;
+
+	port = ntohs(mbuf_read_u16(mb)); mbuf_advance(mb, 2);
+	err |= pcp_ipaddr_decode(mb, &peer->remote_addr);
+	sa_set_port(&peer->remote_addr, port);
+
+	return err;
+}
+
+
+static void destructor(void *arg)
+{
+	struct pcp_msg *msg = arg;
+
+	list_flush(&msg->optionl);
+}
+
+
+static int pcp_header_encode_request(struct mbuf *mb, enum pcp_opcode opcode,
+			      uint32_t req_lifetime, const struct sa *int_addr)
+{
+	int err = 0;
+
+	if (!mb || !int_addr)
+		return EINVAL;
+
+	err |= mbuf_write_u8(mb, PCP_VERSION);
+	err |= mbuf_write_u8(mb, opcode);
+	err |= mbuf_write_u16(mb, 0x0000);
+	err |= mbuf_write_u32(mb, htonl(req_lifetime));
+	err |= pcp_ipaddr_encode(mb, int_addr);
+
+	return err;
+}
+
+
+static int pcp_header_decode(struct pcp_hdr *hdr, struct mbuf *mb)
+{
+	uint8_t b;
+
+	if (!hdr || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < PCP_HDR_SZ)
+		return EBADMSG;
+
+	hdr->version = mbuf_read_u8(mb);
+
+	if (hdr->version != PCP_VERSION) {
+		(void)re_fprintf(stderr, "pcp: unknown version %u\n",
+				 hdr->version);
+		return EPROTO;
+	}
+
+	b            = mbuf_read_u8(mb);
+	hdr->resp    = b>>7;
+	hdr->opcode  = b & 0x7f;
+
+	(void)mbuf_read_u8(mb);
+	b = mbuf_read_u8(mb);
+
+	if (hdr->resp)
+		hdr->result = b;
+
+	hdr->lifetime = ntohl(mbuf_read_u32(mb));
+
+	if (hdr->resp) {
+		hdr->epoch = ntohl(mbuf_read_u32(mb));
+		mbuf_advance(mb, 12);
+	}
+	else { /* Request */
+		(void)pcp_ipaddr_decode(mb, &hdr->cli_addr);
+	}
+
+	return 0;
+}
+
+
+int pcp_msg_req_vencode(struct mbuf *mb, enum pcp_opcode opcode,
+			uint32_t lifetime, const struct sa *cli_addr,
+			const void *payload, uint32_t optionc, va_list ap)
+{
+	uint32_t i;
+	int err;
+
+	if (!mb || !cli_addr)
+		return EINVAL;
+
+	err = pcp_header_encode_request(mb, opcode, lifetime, cli_addr);
+	if (err)
+		return err;
+
+	if (payload) {
+		err = pcp_payload_encode(mb, opcode, payload);
+		if (err)
+			return err;
+	}
+
+	/* encode options */
+	for (i=0; i<optionc; i++) {
+
+		enum pcp_option_code code = va_arg(ap, int);
+		const void *v = va_arg(ap, const void *);
+
+		if (!v)
+			continue;
+
+		err |= pcp_option_encode(mb, code, v);
+	}
+
+	return err;
+}
+
+
+int pcp_msg_req_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       uint32_t lifetime, const struct sa *cli_addr,
+		       const void *payload, uint32_t optionc, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, optionc);
+	err = pcp_msg_req_vencode(mb, opcode, lifetime, cli_addr,
+				  payload, optionc, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+int pcp_msg_decode(struct pcp_msg **msgp, struct mbuf *mb)
+{
+	struct pcp_msg *msg;
+	size_t len, pos;
+	int err;
+
+	if (!msgp || !mb)
+		return EINVAL;
+
+	len = mbuf_get_left(mb);
+	if (len < PCP_MIN_PACKET || len > PCP_MAX_PACKET || len&3)
+		return EBADMSG;
+
+	msg = mem_zalloc(sizeof(*msg), destructor);
+	if (!msg)
+		return ENOMEM;
+
+	pos = mb->pos;
+	err = pcp_header_decode(&msg->hdr, mb);
+	if (err)
+		goto out;
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+		err = pcp_map_decode(&msg->pld.map, mb);
+		break;
+
+	case PCP_PEER:
+		err = pcp_peer_decode(&msg->pld.peer, mb);
+		break;
+
+	default:
+		break;
+	}
+	if (err)
+		goto out;
+
+	/* Decode PCP Options */
+	while (mbuf_get_left(mb) >= 4) {
+
+		struct pcp_option *opt;
+
+		err = pcp_option_decode(&opt, mb);
+		if (err)
+			goto out;
+
+		list_append(&msg->optionl, &opt->le, opt);
+	}
+
+ out:
+	if (err) {
+		mb->pos = pos;
+		mem_deref(msg);
+	}
+	else
+		*msgp = msg;
+
+	return err;
+}
+
+
+struct pcp_option *pcp_msg_option(const struct pcp_msg *msg,
+				  enum pcp_option_code code)
+{
+	struct le *le = msg ? list_head(&msg->optionl) : NULL;
+
+	while (le) {
+		struct pcp_option *opt = le->data;
+
+		le = le->next;
+
+		if (opt->code == code)
+			return opt;
+	}
+
+	return NULL;
+}
+
+
+struct pcp_option *pcp_msg_option_apply(const struct pcp_msg *msg,
+					pcp_option_h *h, void *arg)
+{
+	struct le *le = msg ? list_head(&msg->optionl) : NULL;
+
+	while (le) {
+		struct pcp_option *opt = le->data;
+
+		le = le->next;
+
+		if (h && h(opt, arg))
+			return opt;
+	}
+
+	return NULL;
+}
+
+
+static bool option_print(const struct pcp_option *opt, void *arg)
+{
+	return 0 != pcp_option_print(arg, opt);
+}
+
+
+int pcp_msg_printhdr(struct re_printf *pf, const struct pcp_msg *msg)
+{
+	int err;
+
+	if (!msg)
+		return 0;
+
+	err = re_hprintf(pf, "%s %s %usec",
+			 msg->hdr.resp ? "Response" : "Request",
+			 pcp_opcode_name(msg->hdr.opcode),
+			 msg->hdr.lifetime);
+
+	if (msg->hdr.resp) {
+		err |= re_hprintf(pf, " result=%s, epoch_time=%u sec",
+				  pcp_result_name(msg->hdr.result),
+				  msg->hdr.epoch);
+	}
+	else {
+		err |= re_hprintf(pf, " client_addr=%j", &msg->hdr.cli_addr);
+	}
+
+	return err;
+}
+
+
+static int pcp_map_print(struct re_printf *pf, const struct pcp_map *map)
+{
+	if (!map)
+		return 0;
+
+	return re_hprintf(pf,
+			  " nonce    = %w\n protocol = %s\n"
+			  " int_port = %u\n ext_addr = %J\n",
+			  map->nonce, sizeof(map->nonce),
+			  pcp_proto_name(map->proto),
+			  map->int_port,
+			  &map->ext_addr);
+}
+
+
+int pcp_msg_print(struct re_printf *pf, const struct pcp_msg *msg)
+{
+	int err;
+
+	if (!msg)
+		return 0;
+
+	err  = pcp_msg_printhdr(pf, msg);
+	err |= re_hprintf(pf, "\n");
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+		err |= pcp_map_print(pf, &msg->pld.map);
+		break;
+
+	case PCP_PEER:
+		err |= pcp_map_print(pf, &msg->pld.peer.map);
+		err |= re_hprintf(pf, " remote_peer = %J\n",
+				  &msg->pld.peer.remote_addr);
+		break;
+	}
+
+	if (err)
+		return err;
+
+	if (pcp_msg_option_apply(msg, option_print, pf))
+		return ENOMEM;
+
+	return 0;
+}
+
+
+/**
+ * Get the payload from a PCP message
+ *
+ * @param msg PCP message
+ *
+ * @return either "struct pcp_map" or "struct pcp_peer"
+ */
+const void *pcp_msg_payload(const struct pcp_msg *msg)
+{
+	if (!msg)
+		return NULL;
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:  return &msg->pld.map;
+	case PCP_PEER: return &msg->pld.peer;
+	default:       return NULL;
+	}
+}
diff --git a/src/pcp/option.c b/src/pcp/option.c
new file mode 100644
index 0000000..5ca9e0b
--- /dev/null
+++ b/src/pcp/option.c
@@ -0,0 +1,228 @@
+/**
+ * @file option.c  PCP options
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <re_types.h>
+#include <re_fmt.h>
+#include <re_mem.h>
+#include <re_mbuf.h>
+#include <re_list.h>
+#include <re_sa.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static void destructor(void *arg)
+{
+	struct pcp_option *opt = arg;
+
+	list_unlink(&opt->le);
+
+	switch (opt->code) {
+
+	case PCP_OPTION_DESCRIPTION:
+		mem_deref(opt->u.description);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+int pcp_option_encode(struct mbuf *mb, enum pcp_option_code code,
+		      const void *v)
+{
+	const struct sa *sa = v;
+	const struct pcp_option_filter *filt = v;
+	size_t start, len;
+	int err = 0;
+
+	if (!mb)
+		return EINVAL;
+
+	mb->pos += 4;
+	start = mb->pos;
+
+	switch (code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		if (!sa)
+			return EINVAL;
+		err |= pcp_ipaddr_encode(mb, sa);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		/* no payload */
+		break;
+
+	case PCP_OPTION_FILTER:
+		if (!filt)
+			return EINVAL;
+		err |= mbuf_write_u8(mb, 0x00);
+		err |= mbuf_write_u8(mb, filt->prefix_length);
+		err |= mbuf_write_u16(mb, htons(sa_port(&filt->remote_peer)));
+		err |= pcp_ipaddr_encode(mb, &filt->remote_peer);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		if (!v)
+			return EINVAL;
+		err |= mbuf_write_str(mb, v);
+		break;
+
+	default:
+		(void)re_fprintf(stderr,
+				 "pcp: unsupported option %d\n", code);
+		return EINVAL;
+	}
+
+	/* header */
+	len = mb->pos - start;
+
+	mb->pos = start - 4;
+	err |= mbuf_write_u8(mb, code);
+	err |= mbuf_write_u8(mb, 0x00);
+	err |= mbuf_write_u16(mb, htons(len));
+	mb->pos += len;
+
+	/* padding */
+	while ((mb->pos - start) & 0x03)
+		err |= mbuf_write_u8(mb, 0x00);
+
+	return err;
+}
+
+
+int pcp_option_decode(struct pcp_option **optp, struct mbuf *mb)
+{
+	struct pcp_option *opt;
+	size_t start, len;
+	uint16_t port;
+	int err = 0;
+
+	if (!optp || !mb)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 4)
+		return EBADMSG;
+
+	opt = mem_zalloc(sizeof(*opt), destructor);
+	if (!opt)
+		return ENOMEM;
+
+	opt->code = mbuf_read_u8(mb);
+	(void)mbuf_read_u8(mb);
+	len = ntohs(mbuf_read_u16(mb));
+
+	if (mbuf_get_left(mb) < len)
+		goto badmsg;
+
+	start = mb->pos;
+
+	switch (opt->code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		if (len < 16)
+			goto badmsg;
+		err = pcp_ipaddr_decode(mb, &opt->u.third_party);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		/* no payload */
+		break;
+
+	case PCP_OPTION_FILTER:
+		if (len < 20)
+			goto badmsg;
+		(void)mbuf_read_u8(mb);
+		opt->u.filter.prefix_length = mbuf_read_u8(mb);
+		port = ntohs(mbuf_read_u16(mb));
+		err = pcp_ipaddr_decode(mb, &opt->u.filter.remote_peer);
+		sa_set_port(&opt->u.filter.remote_peer, port);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		err = mbuf_strdup(mb, &opt->u.description, len);
+		break;
+
+	default:
+		mb->pos += len;
+
+		(void)re_printf("pcp: ignore option code %d (len=%zu)\n",
+				opt->code, len);
+		break;
+	}
+
+	if (err)
+		goto error;
+
+	/* padding */
+	while (((mb->pos - start) & 0x03) && mbuf_get_left(mb))
+		++mb->pos;
+
+	*optp = opt;
+
+	return 0;
+
+ badmsg:
+	err = EBADMSG;
+ error:
+	mem_deref(opt);
+
+	return err;
+}
+
+
+static const char *pcp_option_name(enum pcp_option_code code)
+{
+	switch (code) {
+
+	case PCP_OPTION_THIRD_PARTY:    return "THIRD_PARTY";
+	case PCP_OPTION_PREFER_FAILURE: return "PREFER_FAILURE";
+	case PCP_OPTION_FILTER:         return "FILTER";
+	case PCP_OPTION_DESCRIPTION:    return "DESCRIPTION";
+	default: return "?";
+	}
+}
+
+
+int pcp_option_print(struct re_printf *pf, const struct pcp_option *opt)
+{
+	int err;
+
+	if (!opt)
+		return 0;
+
+	err = re_hprintf(pf, " %-25s", pcp_option_name(opt->code));
+
+	switch (opt->code) {
+
+	case PCP_OPTION_THIRD_PARTY:
+		err |= re_hprintf(pf, "address=%j",
+				  &opt->u.third_party);
+		break;
+
+	case PCP_OPTION_PREFER_FAILURE:
+		break;
+
+	case PCP_OPTION_FILTER:
+		err |= re_hprintf(pf, "prefix_length=%u, remote_peer=%J",
+				  opt->u.filter.prefix_length,
+				  &opt->u.filter.remote_peer);
+		break;
+
+	case PCP_OPTION_DESCRIPTION:
+		err |= re_hprintf(pf, "'%s'", opt->u.description);
+		break;
+
+	default:
+		err |= re_hprintf(pf, "???");
+		break;
+	}
+
+	err |= re_hprintf(pf, "\n");
+
+	return err;
+}
diff --git a/src/pcp/payload.c b/src/pcp/payload.c
new file mode 100644
index 0000000..c9d6033
--- /dev/null
+++ b/src/pcp/payload.c
@@ -0,0 +1,112 @@
+/**
+ * @file payload.c  PCP payload encoding and decoding
+ *
+ * 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_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_write_port(struct mbuf *mb, const struct sa *sa)
+{
+	uint16_t port_be;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	switch (sa->u.sa.sa_family) {
+
+	case AF_INET:
+		port_be = sa->u.in.sin_port;
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		port_be = sa->u.in6.sin6_port;
+		break;
+#endif
+
+	default:
+		return EAFNOSUPPORT;
+	}
+
+	return mbuf_write_u16(mb, port_be);
+}
+
+
+static int pcp_map_encode(struct mbuf *mb, const struct pcp_map *map)
+{
+	int err = 0;
+
+	if (!mb || !map)
+		return EINVAL;
+
+	err |= mbuf_write_mem(mb, map->nonce, sizeof(map->nonce));
+	err |= mbuf_write_u8(mb, map->proto);
+	err |= mbuf_fill(mb, 0x00, 3);
+	err |= mbuf_write_u16(mb, htons(map->int_port));
+	err |= pcp_write_port(mb, &map->ext_addr);
+	err |= pcp_ipaddr_encode(mb, &map->ext_addr);
+
+	return err;
+}
+
+
+static int pcp_peer_encode(struct mbuf *mb, const struct pcp_peer *peer)
+{
+	int err;
+
+	if (!mb || !peer)
+		return EINVAL;
+
+	/* Protocol MUST NOT be zero.
+	 * Internal port MUST NOT be zero.
+	 */
+	if (!peer->map.proto || !peer->map.int_port)
+		return EPROTO;
+
+	/* note: the MAP and PEER opcodes are quite similar */
+	err = pcp_map_encode(mb, &peer->map);
+	if (err)
+		return err;
+
+	err  = pcp_write_port(mb, &peer->remote_addr);
+	err |= mbuf_write_u16(mb, 0x0000);
+	err |= pcp_ipaddr_encode(mb, &peer->remote_addr);
+
+	return err;
+}
+
+
+int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       const union pcp_payload *pld)
+{
+	int err;
+
+	if (!mb || !pld)
+		return EINVAL;
+
+	switch (opcode) {
+
+	case PCP_MAP:
+		err = pcp_map_encode(mb, &pld->map);
+		break;
+
+	case PCP_PEER:
+		err = pcp_peer_encode(mb, &pld->peer);
+		break;
+
+	default:
+		re_fprintf(stderr, "pcp: dont know how to encode payload"
+			   " for opcode %d\n", opcode);
+		err = EPROTO;
+		break;
+	}
+
+	return err;
+}
diff --git a/src/pcp/pcp.c b/src/pcp/pcp.c
new file mode 100644
index 0000000..28961fb
--- /dev/null
+++ b/src/pcp/pcp.c
@@ -0,0 +1,122 @@
+/**
+ * @file pcp/pcp.c  PCP protocol details
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+#include <string.h>
+#include <re_types.h>
+#include <re_mbuf.h>
+#include <re_sa.h>
+#include <re_list.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static const uint8_t pattern[12] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff};
+
+
+int pcp_ipaddr_encode(struct mbuf *mb, const struct sa *sa)
+{
+	int err = 0;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	switch (sa_af(sa)) {
+
+	case AF_INET:
+		err |= mbuf_write_mem(mb, pattern, sizeof(pattern));
+		err |= mbuf_write_mem(mb, (void *)&sa->u.in.sin_addr.s_addr,
+				      4);
+		break;
+
+#ifdef HAVE_INET6
+	case AF_INET6:
+		err |= mbuf_write_mem(mb, sa->u.in6.sin6_addr.s6_addr, 16);
+		break;
+#endif
+
+	default:
+		err = EAFNOSUPPORT;
+		break;
+	}
+
+	return err;
+}
+
+
+int pcp_ipaddr_decode(struct mbuf *mb, struct sa *sa)
+{
+	uint8_t *p;
+
+	if (!mb || !sa)
+		return EINVAL;
+
+	if (mbuf_get_left(mb) < 16)
+		return EBADMSG;
+
+	p = mbuf_buf(mb);
+
+	if (0 == memcmp(p, pattern, sizeof(pattern))) {
+
+		sa_init(sa, AF_INET);
+		memcpy(&sa->u.in.sin_addr, p + 12, 4);
+	}
+#ifdef HAVE_INET6
+	else {
+		sa_init(sa, AF_INET6);
+		memcpy(sa->u.in6.sin6_addr.s6_addr, p, 16);
+	}
+#endif
+
+	mb->pos += 16;
+
+	return 0;
+}
+
+
+const char *pcp_result_name(enum pcp_result result)
+{
+	switch (result) {
+
+	case PCP_SUCCESS:                 return "SUCCESS";
+	case PCP_UNSUPP_VERSION:          return "UNSUPP_VERSION";
+	case PCP_NOT_AUTHORIZED:          return "NOT_AUTHORIZED";
+	case PCP_MALFORMED_REQUEST:       return "MALFORMED_REQUEST";
+	case PCP_UNSUPP_OPCODE:           return "UNSUPP_OPCODE";
+	case PCP_UNSUPP_OPTION:           return "UNSUPP_OPTION";
+	case PCP_MALFORMED_OPTION:        return "MALFORMED_OPTION";
+	case PCP_NETWORK_FAILURE:         return "NETWORK_FAILURE";
+	case PCP_NO_RESOURCES:            return "NO_RESOURCES";
+	case PCP_UNSUPP_PROTOCOL:         return "UNSUPP_PROTOCOL";
+	case PCP_USER_EX_QUOTA:           return "USER_EX_QUOTA";
+	case PCP_CANNOT_PROVIDE_EXTERNAL: return "CANNOT_PROVIDE_EXTERNAL";
+	case PCP_ADDRESS_MISMATCH:        return "ADDRESS_MISMATCH";
+	case PCP_EXCESSIVE_REMOTE_PEERS:  return "EXCESSIVE_REMOTE_PEERS";
+	case PCP_UNSUPP_FAMILY:           return "UNSUPP_FAMILY";
+	default: return "?";
+	}
+}
+
+
+const char *pcp_opcode_name(enum pcp_opcode opcode)
+{
+	switch (opcode) {
+
+	case PCP_ANNOUNCE: return "ANNOUNCE";
+	case PCP_MAP:      return "MAP";
+	case PCP_PEER:     return "PEER";
+	default:           return "?";
+	}
+}
+
+
+const char *pcp_proto_name(int proto)
+{
+	switch (proto) {
+
+	case IPPROTO_UDP: return "UDP";
+	case IPPROTO_TCP: return "TCP";
+	default: return "?";
+	}
+}
diff --git a/src/pcp/pcp.h b/src/pcp/pcp.h
new file mode 100644
index 0000000..5c85ecc
--- /dev/null
+++ b/src/pcp/pcp.h
@@ -0,0 +1,9 @@
+/**
+ * @file pcp/pcp.h  PCP protocol -- Internal interface
+ *
+ * Copyright (C) 2010 - 2016 Creytiv.com
+ */
+
+
+int pcp_payload_encode(struct mbuf *mb, enum pcp_opcode opcode,
+		       const union pcp_payload *pld);
diff --git a/src/pcp/reply.c b/src/pcp/reply.c
new file mode 100644
index 0000000..264dcbb
--- /dev/null
+++ b/src/pcp/reply.c
@@ -0,0 +1,93 @@
+/**
+ * @file pcp/reply.c  PCP reply
+ *
+ * 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_sa.h>
+#include <re_udp.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+static int pcp_header_encode_response(struct mbuf *mb, enum pcp_opcode opcode,
+				      enum pcp_result result,
+				      uint32_t lifetime, uint32_t epoch_time)
+{
+	int err = 0;
+
+	if (!mb)
+		return EINVAL;
+
+	err |= mbuf_write_u8(mb, PCP_VERSION);
+	err |= mbuf_write_u8(mb, 1<<7 | opcode);
+	err |= mbuf_write_u8(mb, 0x00);
+	err |= mbuf_write_u8(mb, result);
+	err |= mbuf_write_u32(mb, htonl(lifetime));
+	err |= mbuf_write_u32(mb, htonl(epoch_time));
+	err |= mbuf_fill(mb, 0x00, 12);
+
+	return err;
+}
+
+
+/**
+ * Send a PCP response message
+ *
+ * @param us         UDP Socket
+ * @param dst        Destination network address
+ * @param req        Buffer containing original PCP request (optional)
+ * @param opcode     PCP opcode
+ * @param result     PCP result for the response
+ * @param lifetime   Lifetime in [seconds]
+ * @param epoch_time Server Epoch-time
+ * @param payload    PCP payload, e.g. struct pcp_map (optional)
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int pcp_reply(struct udp_sock *us, const struct sa *dst, struct mbuf *req,
+	      enum pcp_opcode opcode, enum pcp_result result,
+	      uint32_t lifetime, uint32_t epoch_time, const void *payload)
+{
+	struct mbuf *mb;
+	size_t start;
+	int err;
+
+	if (!us || !dst)
+		return EINVAL;
+
+	if (req) {
+		/* the complete Request must be included in the Response */
+		mb = mem_ref(req);
+	}
+	else {
+		mb = mbuf_alloc(128);
+		if (!mb)
+			return ENOMEM;
+	}
+
+	start = mb->pos;
+
+	/* encode the response packet */
+	err = pcp_header_encode_response(mb, opcode, result,
+					 lifetime, epoch_time);
+	if (err)
+		goto out;
+
+	if (payload) {
+		err = pcp_payload_encode(mb, opcode, payload);
+		if (err)
+			goto out;
+	}
+
+	mb->pos = start;
+	err = udp_send(us, dst, mb);
+
+ out:
+	mem_deref(mb);
+	return err;
+}
diff --git a/src/pcp/request.c b/src/pcp/request.c
new file mode 100644
index 0000000..60d58b4
--- /dev/null
+++ b/src/pcp/request.c
@@ -0,0 +1,346 @@
+/**
+ * @file pcp/request.c  PCP request
+ *
+ * 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_sys.h>
+#include <re_sa.h>
+#include <re_tmr.h>
+#include <re_udp.h>
+#include <re_pcp.h>
+#include "pcp.h"
+
+
+/*
+ * Defines a PCP client request
+ *
+ * the application must keep a reference to this object and the
+ * object must be deleted by the application. the response handler
+ * might be called multiple times.
+ */
+struct pcp_request {
+	struct pcp_conf conf;
+	struct sa srv;
+	struct udp_sock *us;
+	struct mbuf *mb;
+	struct tmr tmr;
+	struct tmr tmr_dur;
+	struct tmr tmr_refresh;
+	enum pcp_opcode opcode;
+	union pcp_payload payload;
+	uint32_t lifetime;
+	bool granted;
+	unsigned txc;
+	double RT;
+	pcp_resp_h *resph;
+	void *arg;
+};
+
+
+/*
+ * RT:   Retransmission timeout
+ * IRT:  Initial retransmission time, SHOULD be 3 seconds
+ * MRC:  Maximum retransmission count, SHOULD be 0 (no maximum)
+ * MRT:  Maximum retransmission time, SHOULD be 1024 seconds
+ * MRD:  Maximum retransmission duration, SHOULD be 0 (no maximum)
+ * RAND: Randomization factor
+ */
+static const struct pcp_conf default_conf = {
+	3,
+	0,
+	1024,
+	0
+};
+
+
+static int start_sending(struct pcp_request *req);
+
+
+/* random number between -0.1 and +0.1 */
+static inline double RAND(void)
+{
+	return (1.0 * rand_u16() / 32768 - 1.0) / 10.0;
+}
+
+static double RT_init(const struct pcp_conf *conf)
+{
+	return (1.0 + RAND()) * conf->irt;
+}
+
+static double RT_next(const struct pcp_conf *conf, double RTprev)
+{
+	return (1.0 + RAND()) * min (2 * RTprev, conf->mrt);
+}
+
+
+static void destructor(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	/* Destroy the mapping if it was granted */
+	if (req->granted && req->lifetime && req->mb) {
+
+		/* set the lifetime to zero */
+		req->mb->pos = 4;
+		mbuf_write_u32(req->mb, 0);
+
+		req->mb->pos = 0;
+		(void)udp_send(req->us, &req->srv, req->mb);
+	}
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+	tmr_cancel(&req->tmr_refresh);
+	mem_deref(req->us);
+	mem_deref(req->mb);
+}
+
+
+static void completed(struct pcp_request *req, int err, struct pcp_msg *msg)
+{
+	pcp_resp_h *resph = req->resph;
+	void *arg = req->arg;
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+
+	/* if the request failed, we only called the
+	   response handler once and never again */
+	if (err || msg->hdr.result != PCP_SUCCESS ) {
+		req->resph = NULL;
+	}
+
+	if (resph)
+		resph(err, msg, arg);
+}
+
+
+static void refresh_timeout(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	/* todo: update request with new EXT-ADDR from server */
+	(void)start_sending(req);
+}
+
+
+static void timeout(void *arg)
+{
+	struct pcp_request *req = arg;
+	int err;
+
+	req->txc++;
+
+	if (req->conf.mrc > 0 && req->txc > req->conf.mrc) {
+		completed(req, ETIMEDOUT, NULL);
+		return;
+	}
+
+	req->mb->pos = 0;
+	err = udp_send(req->us, &req->srv, req->mb);
+	if (err) {
+		completed(req, err, NULL);
+		return;
+	}
+
+	req->RT = RT_next(&req->conf, req->RT);
+	tmr_start(&req->tmr, req->RT * 1000, timeout, req);
+}
+
+
+static void timeout_duration(void *arg)
+{
+	struct pcp_request *req = arg;
+
+	completed(req, ETIMEDOUT, NULL);
+}
+
+
+static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
+{
+	struct pcp_request *req = arg;
+	struct pcp_msg *msg;
+	int err;
+
+	if (!sa_cmp(src, &req->srv, SA_ALL))
+		return;
+
+	err = pcp_msg_decode(&msg, mb);
+	if (err)
+		return;
+
+	if (!msg->hdr.resp) {
+		(void)re_fprintf(stderr, "pcp: ignoring PCP request\n");
+		goto out;
+	}
+
+	if (msg->hdr.opcode != req->opcode)
+		goto out;
+
+	/* compare opcode-specific data */
+
+	switch (msg->hdr.opcode) {
+
+	case PCP_MAP:
+	case PCP_PEER:
+		if (0 != memcmp(msg->pld.map.nonce, req->payload.map.nonce,
+				PCP_NONCE_SZ)) {
+			(void)re_fprintf(stderr, "ignoring unknown nonce\n");
+			goto out;
+		}
+		req->payload.map.ext_addr = msg->pld.map.ext_addr;
+		break;
+
+	default:
+		break;
+	}
+
+	req->lifetime = msg->hdr.lifetime;
+	req->granted = (msg->hdr.result == PCP_SUCCESS);
+
+	/* todo:
+	 *
+	 * Once a PCP client has successfully received a response from a PCP
+	 * server on that interface, it resets RT to a value randomly selected
+	 * in the range 1/2 to 5/8 of the mapping lifetime, as described in
+	 * Section 11.2.1, "Renewing a Mapping", and sends subsequent PCP
+	 * requests for that mapping to that same server.
+	 */
+	if (req->granted && req->lifetime) {
+
+		uint32_t v = req->lifetime * 3/4;
+
+		tmr_start(&req->tmr_refresh, v * 1000, refresh_timeout, req);
+	}
+
+	completed(req, 0, msg);
+
+ out:
+	mem_deref(msg);
+}
+
+
+static int start_sending(struct pcp_request *req)
+{
+	int err;
+
+	req->txc = 1;
+
+	req->mb->pos = 0;
+	err = udp_send(req->us, &req->srv, req->mb);
+	if (err)
+		return err;
+
+	req->RT = RT_init(&req->conf);
+	tmr_start(&req->tmr, req->RT * 1000, timeout, req);
+
+	if (req->conf.mrd) {
+		tmr_start(&req->tmr_dur, req->conf.mrd * 1000,
+			  timeout_duration, req);
+	}
+
+	return err;
+}
+
+
+static int pcp_vrequest(struct pcp_request **reqp, const struct pcp_conf *conf,
+			const struct sa *srv, enum pcp_opcode opcode,
+			uint32_t lifetime, const void *payload,
+			pcp_resp_h *resph, void *arg,
+			uint32_t optionc, va_list ap)
+{
+	const union pcp_payload *up = payload;
+	struct pcp_request *req;
+	struct sa laddr;
+	int err;
+
+	if (!reqp || !srv)
+		return EINVAL;
+
+	sa_init(&laddr, sa_af(srv));
+
+	req = mem_zalloc(sizeof(*req), destructor);
+	if (!req)
+		return ENOMEM;
+
+	req->conf   = conf ? *conf : default_conf;
+	req->opcode = opcode;
+	req->srv    = *srv;
+	req->resph  = resph;
+	req->arg    = arg;
+
+	req->lifetime = lifetime;
+
+	if (up)
+		req->payload = *up;
+
+	err = udp_listen(&req->us, &laddr, udp_recv, req);
+	if (err)
+		goto out;
+
+	/*
+	 * see RFC 6887 section 16.4
+	 */
+	err = udp_connect(req->us, srv);
+	if (err)
+		goto out;
+	err = udp_local_get(req->us, &laddr);
+	if (err)
+		goto out;
+
+	req->mb = mbuf_alloc(128);
+	if (!req->mb) {
+		err = ENOMEM;
+		goto out;
+	}
+
+	err = pcp_msg_req_vencode(req->mb, opcode, lifetime,
+				  &laddr, up, optionc, ap);
+	if (err)
+		goto out;
+
+	err = start_sending(req);
+
+ out:
+	if (err)
+		mem_deref(req);
+	else
+		*reqp = req;
+
+	return err;
+}
+
+
+int pcp_request(struct pcp_request **reqp, const struct pcp_conf *conf,
+		const struct sa *srv, enum pcp_opcode opcode,
+		uint32_t lifetime, const void *payload,
+		pcp_resp_h *resph, void *arg, uint32_t optionc, ...)
+{
+	va_list ap;
+	int err;
+
+	va_start(ap, optionc);
+	err = pcp_vrequest(reqp, conf, srv, opcode, lifetime, payload,
+			   resph, arg, optionc, ap);
+	va_end(ap);
+
+	return err;
+}
+
+
+void pcp_force_refresh(struct pcp_request *req)
+{
+	if (!req)
+		return;
+
+	tmr_cancel(&req->tmr);
+	tmr_cancel(&req->tmr_dur);
+
+	tmr_start(&req->tmr_refresh, rand_u16() % 2000, refresh_timeout, req);
+}