add simplistic IuUP FSM and strip/add IuUP headers

This should really be using the FSM in libosmocore/laforge/iu_up: take the best
of both sides and integate in the libosmocore FSM implementation, then use it
here.
- in libosmocore, the FSM definition is nicer.
- here, we have correct header checksums.

This patch here also adds RTP header stripping/adding functionality, after
introducing using msgb to pass data around.

Change-Id: Ibc70e0aa00476926dd1f4ea8139c34f31f9cdfa3
diff --git a/configure.ac b/configure.ac
index 6f6ad4b..1278816 100644
--- a/configure.ac
+++ b/configure.ac
@@ -206,6 +206,7 @@
     tests/atlocal
     tests/mgcp_client/Makefile
     tests/mgcp/Makefile
+    tests/iuup/Makefile
     doc/Makefile
     doc/examples/Makefile
     doc/manuals/Makefile
diff --git a/include/osmocom/mgcp/debug.h b/include/osmocom/mgcp/debug.h
index ddeb0dc..1eed769 100644
--- a/include/osmocom/mgcp/debug.h
+++ b/include/osmocom/mgcp/debug.h
@@ -29,6 +29,7 @@
 /* Debug Areas of the code */
 enum {
 	DRTP,
+	DIUUP,
 	Debug_LastEntry,
 };
 
diff --git a/include/osmocom/mgcp/iuup_cn_node.h b/include/osmocom/mgcp/iuup_cn_node.h
new file mode 100644
index 0000000..f792469
--- /dev/null
+++ b/include/osmocom/mgcp/iuup_cn_node.h
@@ -0,0 +1,41 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* IuUP CN node, minimal implementation */
+
+/*                                            _____IuUP_CN_____
+ *                                            |               |
+ * UE <--> RNC --PDU-> osmo_iuup_cn_rx_pdu() -+->           ---+-> rx_payload()
+ *          |                                 |               |
+ *          |  <-PDU-- tx_msg() <-------------+--           <-+--- osmo_iuup_cn_tx_payload()
+ *                                            |               |
+ *                                            -----------------
+ */
+
+#pragma once
+
+struct osmo_iuup_cn;
+struct msgb;
+
+typedef int (*osmo_iuup_data_cb_t)(struct msgb *msg, void *node_priv);
+
+struct osmo_iuup_cn_cfg {
+	void *node_priv;
+
+	/* When the IuUP peer sent a voice packet, the clean RTP without the IuUP header is fed to this
+	 * callback. */
+	osmo_iuup_data_cb_t rx_payload;
+
+	/* IuUP handler requests that a PDU shall be sent to the IuUP peer (e.g. the RNC).
+	 * It is guaranteed that the msgb->dst pointer is preserved or copied from the msgb that
+	 * originated the request. */
+	osmo_iuup_data_cb_t tx_msg;
+};
+
+bool osmo_iuup_cn_is_iuup_init(struct msgb *msg);
+
+struct osmo_iuup_cn *osmo_iuup_cn_init(void *ctx, struct osmo_iuup_cn_cfg *cfg,
+				       const char *name_fmt, ...);
+void osmo_iuup_cn_free(struct osmo_iuup_cn *cn);
+
+int osmo_iuup_cn_tx_payload(struct osmo_iuup_cn *cn, struct msgb *payload);
+
+int osmo_iuup_cn_rx_pdu(struct osmo_iuup_cn *cn, struct msgb *pdu);
diff --git a/include/osmocom/mgcp/iuup_protocol.h b/include/osmocom/mgcp/iuup_protocol.h
new file mode 100644
index 0000000..f4aec1f
--- /dev/null
+++ b/include/osmocom/mgcp/iuup_protocol.h
@@ -0,0 +1,117 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* IuUP protocol handling, minimal implementation */
+
+#pragma once
+
+#include <osmocom/core/endian.h>
+#include <osmocom/core/msgb.h>
+
+#define OSMO_IUUP_HEADROOM 32
+
+enum osmo_iuup_pdu_type {
+	OSMO_IUUP_PDU_DATA_WITH_CRC = 0,
+	OSMO_IUUP_PDU_CONTROL_PROCEDURE = 14,
+};
+
+enum osmo_iuup_acknack {
+	OSMO_IUUP_ACKNACK_PROCEDURE = 0,
+	OSMO_IUUP_ACKNACK_ACK = 1,
+	OSMO_IUUP_ACKNACK_NACK = 2,
+};
+
+enum osmo_iuup_procedure {
+	OSMO_IUUP_PROC_INITIALIZATION = 0,
+	OSMO_IUUP_PROC_RATE_CONTROL = 1,
+	OSMO_IUUP_PROC_TIME_ALIGNMENT = 2,
+	OSMO_IUUP_PROC_ERROR_EVENT = 3,
+};
+
+enum osmo_iuup_frame_good {
+	OSMO_IUUP_FRAME_GOOD = 0,
+	OSMO_IUUP_FRAME_BAD = 1,
+	OSMO_IUUP_FRAME_BAD_DUE_TO_RADIO = 2,
+};
+
+struct osmo_iuup_hdr_ctrl {
+#if OSMO_IS_BIG_ENDIAN
+	uint8_t pdu_type:4,
+		ack_nack:2,
+		frame_nr:2;
+	uint8_t mode_version:4,
+		procedure:4;
+	uint8_t header_crc:6,
+		payload_crc_hi:2;
+	uint8_t payload_crc_lo;
+	uint8_t payload[0];
+#elif OSMO_IS_LITTLE_ENDIAN
+	uint8_t frame_nr:2,
+		ack_nack:2,
+		pdu_type:4;
+	uint8_t procedure:4,
+		mode_version:4;
+	uint8_t payload_crc_hi:2,
+		header_crc:6;
+	uint8_t payload_crc_lo;
+	uint8_t payload[0];
+#endif
+} __attribute__((packed));
+
+union osmo_iuup_hdr_ctrl_payload {
+	struct {
+#if OSMO_IS_BIG_ENDIAN
+	uint8_t spare:3,
+		iptis_present:1,
+		subflows:3,
+		chain:1;
+#elif OSMO_IS_LITTLE_ENDIAN
+	uint8_t spare:3,
+		iptis_present:1,
+		subflows:3,
+		chain:1;
+#endif
+	} initialization;
+
+	struct {
+#if OSMO_IS_BIG_ENDIAN
+	uint8_t error_distance:2,
+		error_cause:6;
+#elif OSMO_IS_LITTLE_ENDIAN
+	uint8_t error_cause:6,
+		error_distance:2;
+#endif
+	} error_event;
+};
+
+extern const struct value_string osmo_iuup_error_cause_names[];
+static inline const char *osmo_iuup_error_cause_name(uint8_t val)
+{ return get_value_string(osmo_iuup_error_cause_names, val); }
+
+struct osmo_iuup_hdr_data {
+#if OSMO_IS_BIG_ENDIAN
+	uint8_t pdu_type:4,
+		frame_nr:4;
+	uint8_t frame_good:2,
+		rfci:6;
+	uint8_t header_crc:6,
+		payload_crc_hi:2;
+	uint8_t payload_crc_lo;
+#elif OSMO_IS_LITTLE_ENDIAN
+	uint8_t frame_nr:4,
+		pdu_type:4;
+	uint8_t rfci:6,
+		frame_good:2;
+	uint8_t payload_crc_hi:2,
+		header_crc:6;
+	uint8_t payload_crc_lo;
+#endif
+	uint8_t payload[0];
+} __attribute__((packed));
+
+int osmo_iuup_classify(bool log_errors,
+		       const char *log_label,
+		       struct msgb *pdu,
+		       struct osmo_iuup_hdr_ctrl **is_ctrl,
+		       struct osmo_iuup_hdr_data **is_data);
+bool osmo_iuup_is_init(struct msgb *pdu);
+void osmo_iuup_make_init_ack(struct msgb *ack);
+void osmo_iuup_set_checksums(uint8_t *iuup_header_and_payload, unsigned int header_and_payload_len);
diff --git a/include/osmocom/mgcp/mgcp_endp.h b/include/osmocom/mgcp/mgcp_endp.h
index a23e192..04c3d18 100644
--- a/include/osmocom/mgcp/mgcp_endp.h
+++ b/include/osmocom/mgcp/mgcp_endp.h
@@ -23,15 +23,28 @@
 
 #pragma once
 
+#include <osmocom/core/msgb.h>
+
 struct sockaddr_in;
 struct mgcp_conn;
+struct mgcp_conn_rtp;
 struct mgcp_endpoint;
 
-/* Callback type for RTP dispatcher functions
-   (e.g mgcp_dispatch_rtp_bridge_cb, see below) */
-typedef int (*mgcp_dispatch_rtp_cb) (int proto, struct sockaddr_in *addr,
-				     char *buf, unsigned int buf_size,
-				     struct mgcp_conn *conn);
+struct osmo_rtp_msg_ctx {
+	int proto;
+	struct mgcp_conn_rtp *conn_src;
+	struct sockaddr_in *from_addr;
+};
+
+#define OSMO_RTP_MSG_CTX(MSGB) (*(struct osmo_rtp_msg_ctx**)&((MSGB)->dst))
+
+#define LOG_ENDP(endp, level, fmt, args...) \
+	LOGP(DRTP, level, "%x@ " fmt, ENDPOINT_NUMBER(endp), ## args)
+
+/* Callback type for RTP dispatcher functions (e.g mgcp_dispatch_rtp_bridge_cb, see below).
+ * The fields OSMO_RTP_MSG_PROTO, OSMO_RTP_MSG_CONN_SRC, OSMO_RTP_MSG_FROM_ADDR should be set
+ * appropriately on the msg. */
+typedef int (*mgcp_dispatch_rtp_cb) (struct msgb *msg);
 
 /* Callback type for endpoint specific cleanup actions. This function
  * is automatically executed when a connection is freed (see mgcp_conn_free()
diff --git a/include/osmocom/mgcp/mgcp_internal.h b/include/osmocom/mgcp/mgcp_internal.h
index f75ae8b..51d3b9d 100644
--- a/include/osmocom/mgcp/mgcp_internal.h
+++ b/include/osmocom/mgcp/mgcp_internal.h
@@ -37,6 +37,13 @@
 #define CONN_ID_BTS "0"
 #define CONN_ID_NET "1"
 
+#define LOG_CONN(conn, level, fmt, args...) \
+	LOGP(DRTP, level, "(%d@ I:%s) " fmt, \
+	     ENDPOINT_NUMBER((conn)->endp), (conn)->id, ## args)
+
+#define LOG_CONN_RTP(conn_rtp, level, fmt, args...) \
+	LOG_CONN(conn_rtp->conn, level, fmt, ## args)
+
 enum mgcp_trunk_type {
 	MGCP_TRUNK_VIRTUAL,
 	MGCP_TRUNK_E1,
@@ -200,6 +207,8 @@
 	} osmux;
 
 	struct rate_ctr_group *rate_ctr_group;
+
+	struct osmo_iuup_cn *iuup;
 };
 
 /*! Connection type, specifies which member of the union "u" in mgcp_conn
@@ -259,11 +268,10 @@
 };
 
 int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
-	      char *buf, int rc, struct mgcp_conn_rtp *conn_src,
+	      struct msgb *msg, struct mgcp_conn_rtp *conn_src,
 	      struct mgcp_conn_rtp *conn_dst);
 int mgcp_send_dummy(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn);
-int mgcp_dispatch_rtp_bridge_cb(int proto, struct sockaddr_in *addr, char *buf,
-				unsigned int buf_size, struct mgcp_conn *conn);
+int mgcp_dispatch_rtp_bridge_cb(struct msgb *msg);
 void mgcp_cleanup_rtp_bridge_cb(struct mgcp_endpoint *endp, struct mgcp_conn *conn);
 int mgcp_bind_net_rtp_port(struct mgcp_endpoint *endp, int rtp_port,
 			   struct mgcp_conn_rtp *conn);
@@ -328,3 +336,8 @@
 #define PTYPE_UNDEFINED (-1)
 
 void mgcp_get_local_addr(char *addr, struct mgcp_conn_rtp *conn);
+
+void mgcp_patch_and_count(struct mgcp_endpoint *endp,
+			  struct mgcp_rtp_state *state,
+			  struct mgcp_rtp_end *rtp_end,
+			  struct sockaddr_in *addr, struct msgb *msg);
diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am
index 587bdd4..7314b22 100644
--- a/src/libosmo-mgcp/Makefile.am
+++ b/src/libosmo-mgcp/Makefile.am
@@ -40,4 +40,6 @@
 	mgcp_conn.c \
 	mgcp_stat.c \
 	mgcp_endp.c \
+	iuup_protocol.c \
+	iuup_cn_node.c \
 	$(NULL)
diff --git a/src/libosmo-mgcp/iuup_cn_node.c b/src/libosmo-mgcp/iuup_cn_node.c
new file mode 100644
index 0000000..f555e36
--- /dev/null
+++ b/src/libosmo-mgcp/iuup_cn_node.c
@@ -0,0 +1,213 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* IuUP Core Network side protocol handling, minimal implementation */
+
+/*
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <talloc.h>
+#include <errno.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/netif/rtp.h>
+
+#include <osmocom/mgcp/iuup_cn_node.h>
+#include <osmocom/mgcp/iuup_protocol.h>
+
+#include <osmocom/mgcp/debug.h>
+
+#define LOG_IUUP_CN(cn, level, fmt, args...) \
+		LOGP(DIUUP, level, "(%s) " fmt, (cn)->name, ## args)
+
+struct osmo_iuup_cn {
+	struct osmo_iuup_cn_cfg cfg;
+	char *name;
+	uint8_t next_frame_nr;
+	int rtp_payload_type;
+};
+
+struct osmo_iuup_cn *osmo_iuup_cn_init(void *ctx, struct osmo_iuup_cn_cfg *cfg,
+				       const char *name_fmt, ...)
+{
+	va_list ap;
+	struct osmo_iuup_cn *cn = talloc_zero(ctx, struct osmo_iuup_cn);
+	OSMO_ASSERT(cn);
+
+	cn->cfg = *cfg;
+
+	if (!name_fmt)
+		name_fmt = "-";
+
+	va_start(ap, name_fmt);
+	cn->name = talloc_vasprintf(cn, name_fmt, ap);
+	va_end(ap);
+
+	LOGP(DIUUP, LOGL_INFO, "(%s) Initializing IuUP node\n", cn->name);
+
+	if (!osmo_identifier_valid(cn->name)) {
+		LOGP(DIUUP, LOGL_ERROR, "Attempting to set illegal id for IuUP CN instance: %s\n",
+		     osmo_quote_str(cn->name, -1));
+		talloc_free(cn);
+		return NULL;
+	}
+
+	return cn;
+}
+
+void osmo_iuup_cn_free(struct osmo_iuup_cn *cn)
+{
+	talloc_free(cn);
+}
+
+static int rx_data(struct osmo_iuup_cn *cn, struct msgb *pdu,
+		   struct osmo_iuup_hdr_data *hdr)
+{
+	/* Remove the IuUP bit from the middle of the buffer by writing the RTP header forward. */
+	unsigned int pre_hdr_len = ((uint8_t*)hdr) - pdu->data;
+	memmove(pdu->data + sizeof(*hdr), pdu->data, pre_hdr_len);
+
+	msgb_pull(pdu, sizeof(*hdr));
+
+	LOGP(DIUUP, LOGL_DEBUG, "(%s) IuUP stripping IuUP header from RTP data\n", cn->name);
+	cn->cfg.rx_payload(pdu, cn->cfg.node_priv);
+
+	return 0;
+}
+
+static int tx_init_ack(struct osmo_iuup_cn *cn, struct msgb *src_pdu)
+{
+	/* Send Initialization Ack PDU back to the sender */
+	int rc;
+	struct msgb *ack = msgb_alloc(4096, "IuUP Initialization Ack");
+	OSMO_ASSERT(ack);
+	ack->dst = src_pdu->dst;
+
+	/* Just copy the RTP header that was sent... TODO: tweak some RTP values?? */
+	memcpy(msgb_put(ack, sizeof(struct rtp_hdr)), src_pdu->data, sizeof(struct rtp_hdr));
+
+	osmo_iuup_make_init_ack(ack);
+
+	LOGP(DIUUP, LOGL_DEBUG, "(%s) Sending Initialization ACK %p\n", cn->name, cn->cfg.node_priv);
+	rc = cn->cfg.tx_msg(ack, cn->cfg.node_priv);
+	msgb_free(ack);
+	return rc;
+}
+
+static int rx_control(struct osmo_iuup_cn *cn, struct msgb *pdu,
+		      struct osmo_iuup_hdr_ctrl *hdr)
+{
+	switch (hdr->procedure) {
+	case OSMO_IUUP_PROC_INITIALIZATION:
+		switch (hdr->ack_nack) {
+		case OSMO_IUUP_ACKNACK_PROCEDURE:
+			LOGP(DIUUP, LOGL_INFO, "(%s) Rx IuUP Initialization, sending ACK\n", cn->name);
+			cn->rtp_payload_type = ((struct rtp_hdr*)pdu->data)->payload_type;
+			return tx_init_ack(cn, pdu);
+
+		default:
+			LOGP(DIUUP, LOGL_DEBUG, "(%s) Rx IuUP Initialization, unhandled ack_nack = %d\n",
+			     cn->name, hdr->ack_nack);
+			break;
+		}
+		/* Continue to log "unexpected procedure" below. */
+		break;
+
+	case OSMO_IUUP_PROC_ERROR_EVENT:
+		{
+			union osmo_iuup_hdr_ctrl_payload *p = (void*)hdr->payload;
+			LOGP(DIUUP, LOGL_ERROR,
+			     "(%s) Rx IuUP Error Event: distance=%u, cause=%u=\"%s\"\n",
+			     cn->name, p->error_event.error_distance, p->error_event.error_cause,
+			     osmo_iuup_error_cause_name(p->error_event.error_cause));
+			return 0;
+		}
+
+	default:
+		break;
+	}
+	LOG_IUUP_CN(cn, LOGL_ERROR,
+		    "Rx control PDU with unexpected procedure: 0x%x acknack=0x%x\n",
+		    hdr->procedure, hdr->ack_nack);
+	return -EINVAL;
+}
+
+/* Feed a received PDU to the IuUP CN node. This function takes ownership of the msgb, it must not be
+ * freed by the caller. */
+int osmo_iuup_cn_rx_pdu(struct osmo_iuup_cn *cn, struct msgb *pdu)
+{
+	struct osmo_iuup_hdr_ctrl *is_ctrl;
+	struct osmo_iuup_hdr_data *is_data;
+	int rc;
+
+	rc = osmo_iuup_classify(true, cn->name, pdu, &is_ctrl, &is_data);
+	if (rc)
+		return rc;
+
+	if (is_ctrl)
+		return rx_control(cn, pdu, is_ctrl);
+	if (is_data)
+		return rx_data(cn, pdu, is_data);
+	return rc;
+}
+
+static uint8_t next_frame_nr(struct osmo_iuup_cn *cn)
+{
+	uint8_t frame_nr = cn->next_frame_nr;
+	cn->next_frame_nr = (frame_nr + 1) & 0x0f;
+	return frame_nr;
+}
+
+/* Send this RTP packet to the IuUP peer: add IuUP header and call the tx_msg() to transmit the resulting
+ * message to the IuUP peer.
+ * Returns 0 on success, negative on error. */
+int osmo_iuup_cn_tx_payload(struct osmo_iuup_cn *cn, struct msgb *pdu)
+{
+	struct rtp_hdr *rtp_was, *rtp;
+	struct osmo_iuup_hdr_data *iuup_hdr;
+
+	/* Splice an IuUP header in between RTP header and payload data */
+	rtp_was = (void*)pdu->data;
+
+	/* copy the RTP header part backwards by the size needed for the IuUP header */
+	rtp = (void*)msgb_push(pdu, sizeof(*iuup_hdr));
+	memmove(rtp, rtp_was, sizeof(*rtp));
+
+	/* Send the same payload type to the peer (erm...) */
+	rtp->payload_type = cn->rtp_payload_type;
+
+	iuup_hdr = (void*)rtp->data;
+
+	*iuup_hdr = (struct osmo_iuup_hdr_data){
+		.pdu_type = OSMO_IUUP_PDU_DATA_WITH_CRC,
+		.frame_nr = next_frame_nr(cn),
+		.frame_good = OSMO_IUUP_FRAME_GOOD,
+	};
+
+	osmo_iuup_set_checksums((uint8_t*)iuup_hdr, pdu->tail - (uint8_t*)iuup_hdr);
+	LOGP(DIUUP, LOGL_DEBUG, "(%s) IuUP inserting IuUP header in RTP data (frame nr %u)\n",
+	     cn->name, iuup_hdr->frame_nr);
+
+	return cn->cfg.tx_msg(pdu, cn->cfg.node_priv);
+}
diff --git a/src/libosmo-mgcp/iuup_protocol.c b/src/libosmo-mgcp/iuup_protocol.c
new file mode 100644
index 0000000..bfb009d
--- /dev/null
+++ b/src/libosmo-mgcp/iuup_protocol.c
@@ -0,0 +1,286 @@
+/* A Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* IuUP Core Network side protocol, minimal implementation */
+
+/*
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <osmocom/mgcp/iuup_protocol.h>
+#include <osmocom/mgcp/debug.h>
+#include <osmocom/netif/rtp.h>
+
+/* Calculating two bytes of CRC is ok to do by a loop */
+static uint8_t header_crc6(const uint8_t *hdr)
+{
+    int bit;
+    /* Polynomial: D^6 + D^5 + D^3 + D^2 + D^1 + 1
+     * that's 1101111 or 0x6f;
+     * align its lowest bit with a uint16_t's highest bit: */
+    uint32_t polynomial = 0x6f << 15; // 00110111 10000000 00000000
+    uint32_t remainder = ( ((uint32_t)hdr[0]) << 8 | hdr[1] ) << 6;
+
+    for (bit = 15; bit >= 0; bit--)
+    {
+        if (remainder & (0x40 << bit))
+            remainder ^= polynomial;
+        polynomial >>= 1;
+    }
+
+    return remainder;
+}
+
+/*
+ * Charles Michael Heard's CRC-10 code, from
+ *
+ *      http://web.archive.org/web/20061005231950/http://cell-relay.indiana.edu/cell-relay/publications/software/CRC/crc10.html
+ *
+ * with the CRC table initialized with values computed by
+ * his "gen_byte_crc10_table()" routine, rather than by calling that
+ * routine at run time, and with various data type cleanups.
+ */
+static const uint16_t byte_crc10_table[256] = {
+	0x0000, 0x0233, 0x0255, 0x0066, 0x0299, 0x00aa, 0x00cc, 0x02ff,
+	0x0301, 0x0132, 0x0154, 0x0367, 0x0198, 0x03ab, 0x03cd, 0x01fe,
+	0x0031, 0x0202, 0x0264, 0x0057, 0x02a8, 0x009b, 0x00fd, 0x02ce,
+	0x0330, 0x0103, 0x0165, 0x0356, 0x01a9, 0x039a, 0x03fc, 0x01cf,
+	0x0062, 0x0251, 0x0237, 0x0004, 0x02fb, 0x00c8, 0x00ae, 0x029d,
+	0x0363, 0x0150, 0x0136, 0x0305, 0x01fa, 0x03c9, 0x03af, 0x019c,
+	0x0053, 0x0260, 0x0206, 0x0035, 0x02ca, 0x00f9, 0x009f, 0x02ac,
+	0x0352, 0x0161, 0x0107, 0x0334, 0x01cb, 0x03f8, 0x039e, 0x01ad,
+	0x00c4, 0x02f7, 0x0291, 0x00a2, 0x025d, 0x006e, 0x0008, 0x023b,
+	0x03c5, 0x01f6, 0x0190, 0x03a3, 0x015c, 0x036f, 0x0309, 0x013a,
+	0x00f5, 0x02c6, 0x02a0, 0x0093, 0x026c, 0x005f, 0x0039, 0x020a,
+	0x03f4, 0x01c7, 0x01a1, 0x0392, 0x016d, 0x035e, 0x0338, 0x010b,
+	0x00a6, 0x0295, 0x02f3, 0x00c0, 0x023f, 0x000c, 0x006a, 0x0259,
+	0x03a7, 0x0194, 0x01f2, 0x03c1, 0x013e, 0x030d, 0x036b, 0x0158,
+	0x0097, 0x02a4, 0x02c2, 0x00f1, 0x020e, 0x003d, 0x005b, 0x0268,
+	0x0396, 0x01a5, 0x01c3, 0x03f0, 0x010f, 0x033c, 0x035a, 0x0169,
+	0x0188, 0x03bb, 0x03dd, 0x01ee, 0x0311, 0x0122, 0x0144, 0x0377,
+	0x0289, 0x00ba, 0x00dc, 0x02ef, 0x0010, 0x0223, 0x0245, 0x0076,
+	0x01b9, 0x038a, 0x03ec, 0x01df, 0x0320, 0x0113, 0x0175, 0x0346,
+	0x02b8, 0x008b, 0x00ed, 0x02de, 0x0021, 0x0212, 0x0274, 0x0047,
+	0x01ea, 0x03d9, 0x03bf, 0x018c, 0x0373, 0x0140, 0x0126, 0x0315,
+	0x02eb, 0x00d8, 0x00be, 0x028d, 0x0072, 0x0241, 0x0227, 0x0014,
+	0x01db, 0x03e8, 0x038e, 0x01bd, 0x0342, 0x0171, 0x0117, 0x0324,
+	0x02da, 0x00e9, 0x008f, 0x02bc, 0x0043, 0x0270, 0x0216, 0x0025,
+	0x014c, 0x037f, 0x0319, 0x012a, 0x03d5, 0x01e6, 0x0180, 0x03b3,
+	0x024d, 0x007e, 0x0018, 0x022b, 0x00d4, 0x02e7, 0x0281, 0x00b2,
+	0x017d, 0x034e, 0x0328, 0x011b, 0x03e4, 0x01d7, 0x01b1, 0x0382,
+	0x027c, 0x004f, 0x0029, 0x021a, 0x00e5, 0x02d6, 0x02b0, 0x0083,
+	0x012e, 0x031d, 0x037b, 0x0148, 0x03b7, 0x0184, 0x01e2, 0x03d1,
+	0x022f, 0x001c, 0x007a, 0x0249, 0x00b6, 0x0285, 0x02e3, 0x00d0,
+	0x011f, 0x032c, 0x034a, 0x0179, 0x0386, 0x01b5, 0x01d3, 0x03e0,
+	0x021e, 0x002d, 0x004b, 0x0278, 0x0087, 0x02b4, 0x02d2, 0x00e1
+};
+
+static uint16_t crc10(uint16_t crc10_accum, const uint8_t *payload, unsigned int payload_len)
+{
+	int i;
+
+	for (i = 0; i < payload_len; i++) {
+		crc10_accum = ((crc10_accum << 8) & 0x300)
+			^ byte_crc10_table[(crc10_accum >> 2) & 0xff]
+			^ payload[i];
+	}
+	return crc10_accum;
+}
+
+/* When a payload of a multiple of bytes has run through, we need to still feed 10 bits of zeros into the
+ * CRC10 to get the payload's checksum result that we can send to a peer. That can't be done with above
+ * table, because it acts as if full 16 bits are fed. This stops after 10 bits. */
+static uint16_t crc10_remainder(uint16_t crc10_accum)
+{
+    int bit;
+    /* Polynomial: D^10 + D^9 + D^5 + D^4 + D^1 + 1
+     * that's 11000110011 or 0x633;
+     * align its lowest bit with a 10bit value's highest bit: */
+    uint32_t polynomial = 0x633 << 9; // 1100 01100110 00000000
+    uint32_t remainder = ((uint32_t)crc10_accum) << 10;
+
+    /* Run on 10 bits */
+    for (bit = 9; bit >= 0; bit--)
+    {
+        if (remainder & ((1 << 10) << bit))
+            remainder ^= polynomial;
+        polynomial >>= 1;
+    }
+
+    return remainder & 0x3ff;
+}
+
+static uint16_t payload_crc10(const uint8_t *payload, unsigned int payload_len)
+{
+	uint16_t crc10_accum = crc10(0, payload, payload_len);
+	return crc10_remainder(crc10_accum);
+}
+
+/* Given an IuUP PDU data block, write the correct header and payload CRC checksums at the right places.
+ */
+void osmo_iuup_set_checksums(uint8_t *iuup_header_and_payload, unsigned int header_and_payload_len)
+{
+	/* For both data and ctrl, the checksums and payload are at the same offset */
+	struct osmo_iuup_hdr_data *hdr = (void*)iuup_header_and_payload;
+	uint16_t crc;
+	unsigned int payload_len;
+
+	hdr->header_crc = header_crc6(iuup_header_and_payload);
+
+	payload_len = iuup_header_and_payload + header_and_payload_len - hdr->payload;
+	crc = payload_crc10(hdr->payload, payload_len);
+	hdr->payload_crc_hi = (crc >> 8) & 0x3;
+	hdr->payload_crc_lo = crc & 0xff;
+
+}
+
+/* Validate minimum message sizes, IuUP PDU type, header- and payload checksums. If it is a Control
+ * Procedure PDU, return the header position in is_ctrl, if it is a Data PDU, return the header position
+ * in is_data. If log_errors is true, log on DIUUP with the given log label for context. Return NULL in
+ * both is_ctrl and is_data, and return a negative error code if the PDU could not be identified as a
+ * valid RTP PDU containing an IuUP part. */
+int osmo_iuup_classify(bool log_errors,
+		       const char *log_label,
+		       struct msgb *pdu,
+		       struct osmo_iuup_hdr_ctrl **is_ctrl,
+		       struct osmo_iuup_hdr_data **is_data)
+{
+	struct rtp_hdr *rtp = (void*)pdu->data;
+	struct osmo_iuup_hdr_ctrl *hdr = (void*)rtp->data;
+	unsigned int payload_len;
+	uint16_t crc_calculated;
+	uint16_t crc_from_peer;
+
+#define ERR(fmt, args...) do { \
+			if (log_errors) \
+				LOGP(DIUUP, LOGL_ERROR, "(%s) " fmt, log_label? : "-", ## args); \
+			return -EINVAL; \
+		} while (0)
+
+	if (is_ctrl)
+		*is_ctrl = NULL;
+	if (is_data)
+		*is_data = NULL;
+
+	/* We need at least a header of 4 bytes. The osmo_iuup_hdr_ctrl already includes a byte of
+	 * payload, so use osmo_iuup_hdr_data to check the minimum here. */
+	if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_data)))
+		ERR("IuUP PDU too short: %u\n", pdu->len);
+
+	/* Let's not validate checksums if the header type isn't sane */
+	switch (hdr->pdu_type) {
+	case OSMO_IUUP_PDU_DATA_WITH_CRC:
+		/* If the caller isn't interested in data PDUs, cut short here. */
+		if (!is_data)
+			return 0;
+		break;
+	case OSMO_IUUP_PDU_CONTROL_PROCEDURE:
+		/* If the caller isn't interested in control PDUs, cut short here. */
+		if (!is_ctrl)
+			return 0;
+		if (pdu->len < (sizeof(*rtp) + sizeof(struct osmo_iuup_hdr_ctrl)))
+			ERR("IuUP control PDU too short: %u\n", pdu->len);
+		break;
+	default:
+		ERR("IuUP with invalid type: %u\n", hdr->pdu_type);
+	}
+
+	/* For both data and ctrl, the checksums and payload are at the same offset */
+
+	crc_calculated = header_crc6((uint8_t*)hdr);
+	if (crc_calculated != hdr->header_crc)
+		ERR("IuUP PDU with invalid header CRC (peer sent 0x%x, calculated 0x%x)\n",
+		    hdr->header_crc, crc_calculated);
+
+	payload_len = pdu->tail - hdr->payload;
+	crc_calculated = payload_crc10(hdr->payload, payload_len);
+	crc_from_peer = (((uint16_t)hdr->payload_crc_hi) << 8) | hdr->payload_crc_lo;
+	if (crc_from_peer != crc_calculated)
+		ERR("IuUP PDU with invalid payload CRC (peer sent 0x%x, calculated 0x%x)\n",
+		    crc_from_peer, crc_calculated);
+
+	switch (hdr->pdu_type) {
+	case OSMO_IUUP_PDU_DATA_WITH_CRC:
+		if (is_data)
+			*is_data = (void*)hdr;
+		return 0;
+	case OSMO_IUUP_PDU_CONTROL_PROCEDURE:
+		if (is_ctrl)
+			*is_ctrl = hdr;
+		return 0;
+	default:
+		ERR("IuUP with invalid type: %u\n", hdr->pdu_type);
+	}
+#undef ERR
+}
+
+/* Return true if this RTP packet contains an IuUP Initialization header (detect IuUP peer). */
+bool osmo_iuup_is_init(struct msgb *pdu)
+{
+ 	struct osmo_iuup_hdr_ctrl *is_ctrl;
+	osmo_iuup_classify(false, NULL, pdu, &is_ctrl, NULL);
+	return is_ctrl
+		&& is_ctrl->procedure == OSMO_IUUP_PROC_INITIALIZATION
+		&& is_ctrl->ack_nack == OSMO_IUUP_ACKNACK_PROCEDURE;
+}
+
+/* Append an IuUP Initialization ACK message */
+void osmo_iuup_make_init_ack(struct msgb *ack)
+{
+	/* Send Initialization Ack PDU back to the sender */
+ 	struct osmo_iuup_hdr_ctrl *hdr;
+	OSMO_ASSERT(ack);
+
+	hdr = (void*)msgb_put(ack, sizeof(*hdr));
+
+	*hdr = (struct osmo_iuup_hdr_ctrl){
+		.pdu_type = OSMO_IUUP_PDU_CONTROL_PROCEDURE,
+		.ack_nack = OSMO_IUUP_ACKNACK_ACK,
+		.procedure = OSMO_IUUP_PROC_INITIALIZATION,
+	};
+
+	osmo_iuup_set_checksums((uint8_t*)hdr, sizeof(*hdr));
+}
+
+const struct value_string osmo_iuup_error_cause_names[] = {
+	{ 0, "CRC error of frame header" },
+	{ 1, "CRC error of frame payload" },
+	{ 2, "Unexpected frame number" },
+	{ 3, "Frame loss" },
+	{ 4, "PDU type unknown" },
+	{ 5, "Unknown procedure" },
+	{ 6, "Unknown reserved value" },
+	{ 7, "Unknown field" },
+	{ 8, "Frame too short" },
+	{ 9, "Missing fields" },
+	{ 16, "Unexpected PDU type" },
+	{ 17, "spare" },
+	{ 18, "Unexpected procedure" },
+	{ 19, "Unexpected RFCI" },
+	{ 20, "Unexpected value" },
+	{ 42, "Initialisation failure" },
+	{ 43, "Initialisation failure (network error, timer expiry)" },
+	{ 44, "Initialisation failure (Iu UP function error, repeated NACK)" },
+	{ 45, "Rate control failure" },
+	{ 46, "Error event failure" },
+	{ 47, "Time Alignment not supported" },
+	{ 48, "Requested Time Alignment not possible" },
+	{ 49, "Iu UP Mode version not supported" },
+	{}
+};
diff --git a/src/libosmo-mgcp/mgcp_network.c b/src/libosmo-mgcp/mgcp_network.c
index 7af8e71..813f088 100644
--- a/src/libosmo-mgcp/mgcp_network.c
+++ b/src/libosmo-mgcp/mgcp_network.c
@@ -43,6 +43,8 @@
 #include <osmocom/mgcp/mgcp_endp.h>
 #include <osmocom/mgcp/mgcp_codec.h>
 #include <osmocom/mgcp/debug.h>
+#include <osmocom/mgcp/iuup_cn_node.h>
+#include <osmocom/mgcp/iuup_protocol.h>
 
 
 #define RTP_SEQ_MOD		(1 << 16)
@@ -50,11 +52,13 @@
 #define RTP_MAX_MISORDER	100
 #define RTP_BUF_SIZE		4096
 
-enum {
+enum rtp_proto {
 	MGCP_PROTO_RTP,
 	MGCP_PROTO_RTCP,
 };
 
+static int rx_rtp(struct msgb *msg);
+
 /*! Determine the local rtp bind IP-address.
  *  \param[out] addr caller provided memory to store the resulting IP-Address
  *  \param[in] endp mgcp endpoint, that holds a copy of the VTY parameters
@@ -481,14 +485,14 @@
  * Patch the payload type of an RTP packet so that it uses the payload type
  * that is valid for the destination connection (conn_dst) */
 static int mgcp_patch_pt(struct mgcp_conn_rtp *conn_src,
-			 struct mgcp_conn_rtp *conn_dst, char *data, int len)
+			 struct mgcp_conn_rtp *conn_dst, struct msgb *msg)
 {
 	struct rtp_hdr *rtp_hdr;
 	uint8_t pt_in;
 	int pt_out;
 
-	OSMO_ASSERT(len >= sizeof(struct rtp_hdr));
-	rtp_hdr = (struct rtp_hdr *)data;
+	OSMO_ASSERT(msg->len >= sizeof(struct rtp_hdr));
+	rtp_hdr = (struct rtp_hdr *)msg->data;
 
 	pt_in = rtp_hdr->payload_type;
 	pt_out = mgcp_codec_pt_translate(conn_src, conn_dst, pt_in);
@@ -508,7 +512,7 @@
 void mgcp_patch_and_count(struct mgcp_endpoint *endp,
 			  struct mgcp_rtp_state *state,
 			  struct mgcp_rtp_end *rtp_end,
-			  struct sockaddr_in *addr, char *data, int len)
+			  struct sockaddr_in *addr, struct msgb *msg)
 {
 	uint32_t arrival_time;
 	int32_t transit;
@@ -516,11 +520,12 @@
 	uint32_t timestamp, ssrc;
 	struct rtp_hdr *rtp_hdr;
 	int payload = rtp_end->codec->payload_type;
+	unsigned int len = msg->len;
 
 	if (len < sizeof(*rtp_hdr))
 		return;
 
-	rtp_hdr = (struct rtp_hdr *)data;
+	rtp_hdr = (struct rtp_hdr *)msg->data;
 	seq = ntohs(rtp_hdr->sequence);
 	timestamp = ntohl(rtp_hdr->timestamp);
 	arrival_time = get_current_ts(rtp_end->codec->rate);
@@ -653,15 +658,14 @@
 
 /* Forward data to a debug tap. This is debug function that is intended for
  * debugging the voice traffic with tools like gstreamer */
-static void forward_data(int fd, struct mgcp_rtp_tap *tap, const char *buf,
-			 int len)
+static void forward_data(int fd, struct mgcp_rtp_tap *tap, struct msgb *msg)
 {
 	int rc;
 
 	if (!tap->enabled)
 		return;
 
-	rc = sendto(fd, buf, len, 0, (struct sockaddr *)&tap->forward,
+	rc = sendto(fd, msg->data, msg->len, 0, (struct sockaddr *)&tap->forward,
 		    sizeof(tap->forward));
 
 	if (rc < 0)
@@ -679,7 +683,7 @@
  *  \param[in] conn_dst associated destination connection
  *  \returns 0 on success, -1 on ERROR */
 int mgcp_send(struct mgcp_endpoint *endp, int is_rtp, struct sockaddr_in *addr,
-	      char *buf, int len, struct mgcp_conn_rtp *conn_src,
+	      struct msgb *msg, struct mgcp_conn_rtp *conn_src,
 	      struct mgcp_conn_rtp *conn_dst)
 {
 	/*! When no destination connection is available (e.g. when only one
@@ -691,6 +695,7 @@
 	struct mgcp_rtp_state *rtp_state;
 	char *dest_name;
 	int rc;
+	int len;
 
 	OSMO_ASSERT(conn_src);
 	OSMO_ASSERT(conn_dst);
@@ -719,7 +724,7 @@
 	 * should not occur if transcoding is consequently avoided. Until
 	 * we have transcoding support in osmo-mgw we can not resolve this. */
 	if (is_rtp) {
-		rc = mgcp_patch_pt(conn_src, conn_dst, buf, len);
+		rc = mgcp_patch_pt(conn_src, conn_dst, msg);
 		if (rc < 0) {
 			LOGP(DRTP, LOGL_ERROR,
 			     "endpoint:0x%x can not patch PT because no suitable egress codec was found.\n",
@@ -746,18 +751,18 @@
 	} else if (is_rtp) {
 		int cont;
 		int nbytes = 0;
-		int buflen = len;
+		int buflen = msg->len;
 		do {
 			/* Run transcoder */
 			cont = endp->cfg->rtp_processing_cb(endp, rtp_end,
-							    buf, &buflen,
+							    (char*)msg->data, &buflen,
 							    RTP_BUF_SIZE);
 			if (cont < 0)
 				break;
 
 			if (addr)
 				mgcp_patch_and_count(endp, rtp_state, rtp_end,
-						     addr, buf, buflen);
+						     addr, msg);
 			LOGP(DRTP, LOGL_DEBUG,
 			     "endpoint:0x%x process/send to %s %s "
 			     "rtp_port:%u rtcp_port:%u\n",
@@ -768,8 +773,9 @@
 
 			/* Forward a copy of the RTP data to a debug ip/port */
 			forward_data(rtp_end->rtp.fd, &conn_src->tap_out,
-				     buf, buflen);
+				     msg);
 
+#if 0
 			/* FIXME: HACK HACK HACK. See OS#2459.
 			 * The ip.access nano3G needs the first RTP payload's first two bytes to read hex
 			 * 'e400', or it will reject the RAB assignment. It seems to not harm other femto
@@ -777,7 +783,7 @@
 			 */
 			if (!rtp_state->patched_first_rtp_payload
 			    && conn_src->conn->mode == MGCP_CONN_LOOPBACK) {
-				uint8_t *data = (uint8_t *) & buf[12];
+				uint8_t *data = msg->data + 12;
 				if (data[0] == 0xe0) {
 					data[0] = 0xe4;
 					data[1] = 0x00;
@@ -788,10 +794,13 @@
 					     ENDPOINT_NUMBER(endp));
 				}
 			}
+#endif
 
-			len = mgcp_udp_send(rtp_end->rtp.fd,
-					    &rtp_end->addr,
-					    rtp_end->rtp_port, buf, buflen);
+			if (conn_dst->iuup)
+				len = osmo_iuup_cn_tx_payload(conn_dst->iuup, msg);
+			else
+				len = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, rtp_end->rtp_port,
+						    (char*)msg->data, msg->len);
 
 			if (len <= 0)
 				return len;
@@ -814,7 +823,7 @@
 
 		len = mgcp_udp_send(rtp_end->rtcp.fd,
 				    &rtp_end->addr,
-				    rtp_end->rtcp_port, buf, len);
+				    rtp_end->rtcp_port, (char*)msg->data, msg->len);
 
 		rate_ctr_inc(&conn_dst->rate_ctr_group->ctr[RTP_PACKETS_TX_CTR]);
 		rate_ctr_add(&conn_dst->rate_ctr_group->ctr[RTP_OCTETS_TX_CTR], len);
@@ -825,46 +834,6 @@
 	return 0;
 }
 
-/* Helper function for mgcp_recv(),
-   Receive one RTP Packet + Originating address from file descriptor */
-static int receive_from(struct mgcp_endpoint *endp, int fd,
-			struct sockaddr_in *addr, char *buf, int bufsize)
-{
-	int rc;
-	socklen_t slen = sizeof(*addr);
-	struct sockaddr_in addr_sink;
-	char buf_sink[RTP_BUF_SIZE];
-	bool tossed = false;
-
-	if (!addr)
-		addr = &addr_sink;
-	if (!buf) {
-		tossed = true;
-		buf = buf_sink;
-		bufsize = sizeof(buf_sink);
-	}
-
-	rc = recvfrom(fd, buf, bufsize, 0, (struct sockaddr *)addr, &slen);
-
-	LOGP(DRTP, LOGL_DEBUG,
-	     "receiving %u bytes length packet from %s:%u ...\n",
-	     rc, inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
-
-	if (rc < 0) {
-		LOGP(DRTP, LOGL_ERROR,
-		     "endpoint:0x%x failed to receive packet, errno: %d/%s\n",
-		     ENDPOINT_NUMBER(endp), errno, strerror(errno));
-		return -1;
-	}
-
-	if (tossed) {
-		LOGP(DRTP, LOGL_ERROR, "endpoint:0x%x packet tossed\n",
-		     ENDPOINT_NUMBER(endp));
-	}
-
-	return rc;
-}
-
 /* Check if the origin (addr) matches the address/port data of the RTP
  * connections. */
 static int check_rtp_origin(struct mgcp_conn_rtp *conn,
@@ -969,7 +938,7 @@
 
 /* Do some basic checks to make sure that the RTCP packets we are going to
  * process are not complete garbage */
-static int check_rtcp(char *buf, unsigned int buf_size)
+static int check_rtcp(struct mgcp_conn_rtp *conn_src, struct msgb *msg)
 {
 	struct rtcp_hdr *hdr;
 	unsigned int len;
@@ -977,33 +946,45 @@
 
 	/* RTPC packets that are just a header without data do not make
 	 * any sense. */
-	if (buf_size < sizeof(struct rtcp_hdr))
+	if (msg->len < sizeof(struct rtcp_hdr)) {
+		LOG_CONN_RTP(conn_src, LOGL_ERROR, "RTCP packet too short (%u < %zu)\n",
+			     msg->len, sizeof(struct rtcp_hdr));
 		return -EINVAL;
+	}
 
 	/* Make sure that the length of the received packet does not exceed
 	 * the available buffer size */
-	hdr = (struct rtcp_hdr *)buf;
+	hdr = (struct rtcp_hdr *)msg->data;
 	len = (osmo_ntohs(hdr->length) + 1) * 4;
-	if (len > buf_size)
+	if (len > msg->len) {
+		LOG_CONN_RTP(conn_src, LOGL_ERROR, "RTCP header length exceeds packet size (%u > %u)\n",
+			     len, msg->len);
 		return -EINVAL;
+	}
 
 	/* Make sure we accept only packets that have a proper packet type set
 	 * See also: http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
 	type = hdr->type;
-	if ((type < 192 || type > 195) && (type < 200 || type > 213))
+	if ((type < 192 || type > 195) && (type < 200 || type > 213)) {
+		LOG_CONN_RTP(conn_src, LOGL_ERROR, "RTCP header: invalid type: %u\n", type);
 		return -EINVAL;
+	}
 
 	return 0;
 }
 
 /* Do some basic checks to make sure that the RTP packets we are going to
  * process are not complete garbage */
-static int check_rtp(char *buf, unsigned int buf_size)
+static int check_rtp(struct mgcp_conn_rtp *conn_src, struct msgb *msg)
 {
-	/* RTP packets that are just a header without data do not make
-	 * any sense. */
-	if (buf_size < sizeof(struct rtp_hdr))
-		return -EINVAL;
+	size_t min_size = sizeof(struct rtp_hdr);
+	if (conn_src->iuup)
+		min_size += sizeof(struct osmo_iuup_hdr_data);
+	if (msg->len < min_size) {
+		LOG_CONN_RTP(conn_src, LOGL_ERROR, "RTP packet too short (%u < %zu)\n",
+			     msg->len, min_size);
+		return -1;
+	}
 
 	/* FIXME: Add more checks, the reason why we do not check more than
 	 * the length is because we currently handle IUUP packets as RTP
@@ -1014,91 +995,14 @@
 	return 0;
 }
 
-/* Receive RTP data from a specified source connection and dispatch it to a
- * destination connection. */
-static int mgcp_recv(int *proto, struct sockaddr_in *addr, char *buf,
-		     unsigned int buf_size, struct osmo_fd *fd)
-{
-	struct mgcp_endpoint *endp;
-	struct mgcp_conn_rtp *conn;
-	struct mgcp_trunk_config *tcfg;
-	int rc;
-
-	conn = (struct mgcp_conn_rtp*) fd->data;
-	endp = conn->conn->endp;
-	tcfg = endp->tcfg;
-
-	LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x receiving RTP/RTCP packet...\n",
-	     ENDPOINT_NUMBER(endp));
-
-	rc = receive_from(endp, fd->fd, addr, buf, buf_size);
-	if (rc <= 0)
-		return -1;
-
-	/* FIXME: The way how we detect the protocol looks odd. We should look
-	 * into the packet header. Also we should introduce a packet type
-	 * MGCP_PROTO_IUUP because currently we handle IUUP packets like RTP
-	 * packets which is problematic. */
-	*proto = fd == &conn->end.rtp ? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
-
-	if (*proto == MGCP_PROTO_RTP) {
-		if (check_rtp(buf, rc) < 0) {
-			LOGP(DRTP, LOGL_ERROR,
-			     "endpoint:0x%x invalid RTP packet received -- packet tossed\n",
-			     ENDPOINT_NUMBER(endp));
-			return -1;
-		}
-	} else if (*proto == MGCP_PROTO_RTCP) {
-		if (check_rtcp(buf, rc) < 0) {
-			LOGP(DRTP, LOGL_ERROR,
-			     "endpoint:0x%x invalid RTCP packet received -- packet tossed\n",
-			     ENDPOINT_NUMBER(endp));
-			return -1;
-		}
-	}
-
-	LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x ", ENDPOINT_NUMBER(endp));
-	LOGPC(DRTP, LOGL_DEBUG, "receiving from %s %s %d\n",
-	      conn->conn->name, inet_ntoa(addr->sin_addr),
-	      ntohs(addr->sin_port));
-	LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x conn:%s\n", ENDPOINT_NUMBER(endp),
-	     mgcp_conn_dump(conn->conn));
-
-	/* Check if the origin of the RTP packet seems plausible */
-	if (tcfg->rtp_accept_all == 0) {
-		if (check_rtp_origin(conn, addr) != 0)
-			return -1;
-	}
-
-	/* Filter out dummy message */
-	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
-		LOGP(DRTP, LOGL_NOTICE,
-		     "endpoint:0x%x dummy message received\n",
-		     ENDPOINT_NUMBER(endp));
-		LOGP(DRTP, LOGL_ERROR,
-		     "endpoint:0x%x packet tossed\n", ENDPOINT_NUMBER(endp));
-		return 0;
-	}
-
-	/* Increment RX statistics */
-	rate_ctr_inc(&conn->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]);
-	rate_ctr_add(&conn->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR], rc);
-
-	/* Forward a copy of the RTP data to a debug ip/port */
-	forward_data(fd->fd, &conn->tap_in, buf, rc);
-
-	return rc;
-}
-
 /* Send RTP data. Possible options are standard RTP packet
  * transmission or trsmission via an osmux connection */
-static int mgcp_send_rtp(int proto, struct sockaddr_in *addr, char *buf,
-			 unsigned int buf_size,
-			 struct mgcp_conn_rtp *conn_src,
-			 struct mgcp_conn_rtp *conn_dst)
+static int mgcp_send_rtp(struct mgcp_conn_rtp *conn_dst, struct msgb *msg)
 {
-	struct mgcp_endpoint *endp;
-	endp = conn_src->conn->endp;
+	enum rtp_proto proto = OSMO_RTP_MSG_CTX(msg)->proto;
+	struct mgcp_conn_rtp *conn_src = OSMO_RTP_MSG_CTX(msg)->conn_src;
+	struct sockaddr_in *from_addr = OSMO_RTP_MSG_CTX(msg)->from_addr;
+	struct mgcp_endpoint *endp = conn_src->conn->endp;
 
 	LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x destin conn:%s\n",
 	     ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_dst->conn));
@@ -1118,14 +1022,14 @@
 		     "using mgcp_send() to forward data directly\n",
 		     ENDPOINT_NUMBER(endp));
 		return mgcp_send(endp, proto == MGCP_PROTO_RTP,
-				 addr, buf, buf_size, conn_src, conn_dst);
+				 from_addr, msg, conn_src, conn_dst);
 	case MGCP_OSMUX_BSC_NAT:
 	case MGCP_OSMUX_BSC:
 		LOGP(DRTP, LOGL_DEBUG,
 		     "endpoint:0x%x endpoint type is MGCP_OSMUX_BSC_NAT, "
 		     "using osmux_xfrm_to_osmux() to forward data through OSMUX\n",
 		     ENDPOINT_NUMBER(endp));
-		return osmux_xfrm_to_osmux(buf, buf_size, conn_dst);
+		return osmux_xfrm_to_osmux((char*)msg->data, msg->len, conn_dst);
 	}
 
 	/* If the data has not been handled/forwarded until here, it will
@@ -1145,9 +1049,10 @@
  *  \param[in] buf_size size data length of buf
  *  \param[in] conn originating connection
  *  \returns 0 on success, -1 on ERROR */
-int mgcp_dispatch_rtp_bridge_cb(int proto, struct sockaddr_in *addr, char *buf,
-				unsigned int buf_size, struct mgcp_conn *conn)
+int mgcp_dispatch_rtp_bridge_cb(struct msgb *msg)
 {
+	struct mgcp_conn_rtp *conn_src = OSMO_RTP_MSG_CTX(msg)->conn_src;
+	struct mgcp_conn *conn = conn_src->conn;
 	struct mgcp_conn *conn_dst;
 	struct mgcp_endpoint *endp;
 	endp = conn->endp;
@@ -1191,9 +1096,7 @@
 	}
 
 	/* Dispatch RTP packet to destination RTP connection */
-	return mgcp_send_rtp(proto, addr, buf,
-			     buf_size, &conn->u.rtp, &conn_dst->u.rtp);
-
+	return mgcp_send_rtp(&conn_dst->u.rtp, msg);
 }
 
 /*! cleanup an endpoint when a connection on an RTP bridge endpoint is removed.
@@ -1215,6 +1118,76 @@
 	}
 }
 
+static bool is_dummy_msg(enum rtp_proto proto, struct msgb *msg)
+{
+	return msg->len == 1 && msg->data[0] == MGCP_DUMMY_LOAD;
+}
+
+struct pdu_ctx {
+	struct sockaddr_in *from_addr;
+	struct mgcp_conn_rtp *conn_src;
+};
+
+/* IuUP CN node has stripped an IuUP header and forwards RTP data to distribute to the peers. */
+int iuup_rx_payload(struct msgb *msg, void *node_priv)
+{
+	struct mgcp_conn_rtp *conn_src = OSMO_RTP_MSG_CTX(msg)->conn_src;
+	LOG_CONN_RTP(conn_src, LOGL_DEBUG, "iuup_rx_payload(%u bytes)\n", msg->len);
+	return rx_rtp(msg);
+}
+
+/* IuUP CN node has composed a message that contains an IuUP header and asks us to send to the IuUP peer.
+ */
+int iuup_tx_msg(struct msgb *msg, void *node_priv)
+{
+	const struct in_addr zero_addr = {};
+	struct mgcp_conn_rtp *conn_src = OSMO_RTP_MSG_CTX(msg)->conn_src;
+	struct mgcp_conn_rtp *conn_dst = node_priv;
+	struct sockaddr_in *from_addr = OSMO_RTP_MSG_CTX(msg)->from_addr;
+	struct mgcp_rtp_end *rtp_end = &conn_dst->end;
+	struct in_addr to_addr = rtp_end->addr;
+	uint16_t to_port = rtp_end->rtp_port;
+
+	if (conn_src == conn_dst
+	    && !memcmp(&zero_addr, &to_addr, sizeof(zero_addr)) && !to_port) {
+		LOG_CONN_RTP(conn_dst, LOGL_DEBUG, "iuup_tx_msg(): direct IuUP reply\n");
+		/* IuUP wants to send a message back to the same peer that sent an RTP package, but there
+		 * is no address configured for that peer yet. It is probably an IuUP Initialization ACK
+		 * reply. Use the sender address to send the reply.
+		 *
+		 * During 3G RAB Assignment, a 3G cell might first probe the MGW and expect an IuUP
+		 * Initialization ACK before it replies to the MSC with a successful RAB Assignment; only
+		 * after that reply does MSC officially know which RTP address+port the 3G cell wants to
+		 * use and can tell this MGW about it, so this "loopback" is, for some 3G cells, the only
+		 * chance we have to get a successful RAB Assignment done (particularly the nano3G does
+		 * this). */
+		to_addr = from_addr->sin_addr;
+		to_port = from_addr->sin_port;
+	}
+
+	LOG_CONN_RTP(conn_dst, LOGL_DEBUG, "iuup_tx_msg(%u bytes) to %s:%u\n", msg->len,
+		     inet_ntoa(to_addr), ntohs(to_port));
+
+	return mgcp_udp_send(rtp_end->rtp.fd, &to_addr, to_port, (char*)msg->data, msg->len);
+}
+
+static void iuup_init(struct mgcp_conn_rtp *conn_src)
+{
+	struct osmo_iuup_cn_cfg cfg = {
+		.node_priv = conn_src,
+		.rx_payload = iuup_rx_payload,
+		.tx_msg = iuup_tx_msg,
+	};
+
+	if (conn_src->iuup) {
+		LOG_CONN_RTP(conn_src, LOGL_NOTICE, "Rx IuUP init, but already initialized. Ignoring.\n");
+		return;
+	}
+
+	conn_src->iuup = osmo_iuup_cn_init(conn_src->conn, &cfg, "endp_%d_conn_%s",
+					   ENDPOINT_NUMBER(conn_src->conn->endp), conn_src->conn->id);
+}
+
 /* Handle incoming RTP data from NET */
 static int rtp_data_net(struct osmo_fd *fd, unsigned int what)
 {
@@ -1228,43 +1201,104 @@
 	struct mgcp_conn_rtp *conn_src;
 	struct mgcp_endpoint *endp;
 	struct sockaddr_in addr;
-
-	char buf[RTP_BUF_SIZE];
-	int proto;
-	int len;
+	socklen_t slen = sizeof(addr);
+	int ret;
+	enum rtp_proto proto;
+	struct osmo_rtp_msg_ctx mc;
+	struct msgb *msg = msgb_alloc_headroom(RTP_BUF_SIZE + OSMO_IUUP_HEADROOM,
+					       OSMO_IUUP_HEADROOM, "RTP-rx");
 
 	conn_src = (struct mgcp_conn_rtp *)fd->data;
 	OSMO_ASSERT(conn_src);
 	endp = conn_src->conn->endp;
 	OSMO_ASSERT(endp);
 
-	LOGP(DRTP, LOGL_DEBUG, "endpoint:0x%x source conn:%s\n",
-	     ENDPOINT_NUMBER(endp), mgcp_conn_dump(conn_src->conn));
+	proto = (fd == &conn_src->end.rtp)? MGCP_PROTO_RTP : MGCP_PROTO_RTCP;
 
-	/* Receive packet */
-	len = mgcp_recv(&proto, &addr, buf, sizeof(buf), fd);
-	if (len < 0)
+	ret = recvfrom(fd->fd, msg->data, msg->data_len, 0, (struct sockaddr *)&addr, &slen);
+
+	if (ret <= 0) {
+		LOG_CONN_RTP(conn_src, LOGL_ERROR, "recvfrom error: %s\n", strerror(errno));
+		msgb_free(msg);
 		return -1;
+	}
+
+	msgb_put(msg, ret);
+
+	LOG_CONN_RTP(conn_src, LOGL_DEBUG, "%s: rx %u bytes from %s:%u\n",
+		     proto == MGCP_PROTO_RTP ? "RTP" : "RTPC",
+		     msg->len, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+	if ((proto == MGCP_PROTO_RTP && check_rtp(conn_src, msg))
+	    || (proto == MGCP_PROTO_RTCP && check_rtcp(conn_src, msg))) {
+		/* Logging happened in the two check_ functions */
+		return -1;
+	}
+
+	if (is_dummy_msg(proto, msg)) {
+		LOG_CONN_RTP(conn_src, LOGL_DEBUG, "rx dummy packet (dropped)\n");
+		return 0;
+	}
+
+	mc = (struct osmo_rtp_msg_ctx){
+		.proto = proto,
+		.conn_src = conn_src,
+		.from_addr = &addr,
+	};
+	OSMO_RTP_MSG_CTX(msg) = &mc;
+	LOG_CONN_RTP(conn_src, LOGL_DEBUG, "msg ctx: %d %p %s\n",
+		     OSMO_RTP_MSG_CTX(msg)->proto,
+		     OSMO_RTP_MSG_CTX(msg)->conn_src,
+		     osmo_hexdump((void*)OSMO_RTP_MSG_CTX(msg)->from_addr, sizeof(struct sockaddr_in)));
+
+	/* Increment RX statistics */
+	rate_ctr_inc(&conn_src->rate_ctr_group->ctr[RTP_PACKETS_RX_CTR]);
+	rate_ctr_add(&conn_src->rate_ctr_group->ctr[RTP_OCTETS_RX_CTR], msg->len);
+	/* FIXME: count RTP and RTCP separately, also count IuUP payload-less separately */
+
+	/* Forward a copy of the RTP data to a debug ip/port */
+	forward_data(fd->fd, &conn_src->tap_in, msg);
+
+	if (proto == MGCP_PROTO_RTP && osmo_iuup_is_init(msg))
+		iuup_init(conn_src);
+
+	if (conn_src->iuup && proto == MGCP_PROTO_RTP)
+		return osmo_iuup_cn_rx_pdu(conn_src->iuup, msg);
+	else
+		return rx_rtp(msg);
+	msgb_free(msg);
+}
+
+static int rx_rtp(struct msgb *msg)
+{
+	struct mgcp_conn_rtp *conn_src = OSMO_RTP_MSG_CTX(msg)->conn_src;
+	struct sockaddr_in *from_addr = OSMO_RTP_MSG_CTX(msg)->from_addr;
+	struct mgcp_conn *conn = conn_src->conn;
+	struct mgcp_trunk_config *tcfg = conn->endp->tcfg;
+
+	LOG_CONN_RTP(conn_src, LOGL_DEBUG, "rx_rtp(%u bytes)\n", msg->len);
 
 	/* Check if the connection is in loopback mode, if yes, just send the
 	 * incoming data back to the origin */
-	if (conn_src->conn->mode == MGCP_CONN_LOOPBACK) {
+	if (conn->mode == MGCP_CONN_LOOPBACK) {
 		/* When we are in loopback mode, we loop back all incoming
 		 * packets back to their origin. We will use the originating
 		 * address data from the UDP packet header to patch the
 		 * outgoing address in connection on the fly */
 		if (conn_src->end.rtp_port == 0) {
-			conn_src->end.addr = addr.sin_addr;
-			conn_src->end.rtp_port = addr.sin_port;
+			conn_src->end.addr = from_addr->sin_addr;
+			conn_src->end.rtp_port = from_addr->sin_port;
 		}
-		return mgcp_send_rtp(proto, &addr, buf,
-				     len, conn_src, conn_src);
+		return mgcp_send_rtp(conn_src, msg);
 	}
 
+	/* Check if the origin of the RTP packet seems plausible */
+	if (!tcfg->rtp_accept_all && check_rtp_origin(conn_src, from_addr))
+		return -1;
+
 	/* Execute endpoint specific implementation that handles the
 	 * dispatching of the RTP data */
-	return endp->type->dispatch_rtp_cb(proto, &addr, buf, len,
-					   conn_src->conn);
+	return conn->endp->type->dispatch_rtp_cb(msg);
 }
 
 /*! set IP Type of Service parameter.
diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c
index a2c138d..2a7ec09 100644
--- a/src/libosmo-mgcp/mgcp_osmux.c
+++ b/src/libosmo-mgcp/mgcp_osmux.c
@@ -261,8 +261,7 @@
 
 	/* Send RTP data to NET */
 	/* FIXME: Get rid of conn_bts and conn_net! */
-	mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len,
-		  conn_bts, conn_net);
+	mgcp_send(endp, 1, &addr, msg, conn_bts, conn_net);
 	msgb_free(msg);
 }
 
@@ -288,8 +287,7 @@
 
 	/* Send RTP data to BTS */
 	/* FIXME: Get rid of conn_bts and conn_net! */
-	mgcp_send(endp, 1, &addr, (char *)msg->data, msg->len,
-		  conn_net, conn_bts);
+	mgcp_send(endp, 1, &addr, msg, conn_net, conn_bts);
 	msgb_free(msg);
 }
 
diff --git a/src/osmo-mgw/mgw_main.c b/src/osmo-mgw/mgw_main.c
index 583d0fb..43be54e 100644
--- a/src/osmo-mgw/mgw_main.c
+++ b/src/osmo-mgw/mgw_main.c
@@ -244,6 +244,12 @@
 		  .color = "\033[1;30m",
 		  .enabled = 1,.loglevel = LOGL_NOTICE,
 		  },
+	[DIUUP] = {
+		  .name = "DIUUP",
+		  .description = "IuUP within RTP stream handling",
+		  .color = "\033[1;31m",
+		  .enabled = 1,.loglevel = LOGL_NOTICE,
+		  },
 };
 
 const struct log_info log_info = {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 49a659f..302fa52 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,6 +1,7 @@
 SUBDIRS = \
 	mgcp_client \
 	mgcp \
+	iuup \
 	$(NULL)
 
 # The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/iuup/Makefile.am b/tests/iuup/Makefile.am
new file mode 100644
index 0000000..12806b1
--- /dev/null
+++ b/tests/iuup/Makefile.am
@@ -0,0 +1,45 @@
+AM_CPPFLAGS = \
+	$(all_includes) \
+	-I$(top_srcdir)/include \
+	-I$(top_srcdir) \
+	$(NULL)
+
+AM_CFLAGS = \
+	-Wall \
+	-ggdb3 \
+	$(LIBOSMOCORE_CFLAGS) \
+	$(LIBOSMOVTY_CFLAGS) \
+	$(LIBOSMOGSM_CFLAGS) \
+	$(LIBOSMONETIF_CFLAGS) \
+	$(COVERAGE_CFLAGS) \
+	$(NULL)
+
+AM_LDFLAGS = \
+	$(COVERAGE_LDFLAGS) \
+	$(NULL)
+
+EXTRA_DIST = \
+	iuup_test.ok \
+	iuup_test.err \
+	$(NULL)
+
+noinst_PROGRAMS = \
+	iuup_test \
+	$(NULL)
+
+iuup_test_SOURCES = \
+	iuup_test.c \
+	$(NULL)
+
+iuup_test_LDADD = \
+	$(top_builddir)/src/libosmo-mgcp/libosmo-mgcp.a \
+	$(LIBOSMOCORE_LIBS) \
+	$(LIBOSMOVTY_LIBS) \
+	$(LIBOSMOGSM_LIBS) \
+	$(LIBRARY_DL) \
+	$(LIBOSMONETIF_LIBS) \
+	-lm  \
+	$(NULL)
+
+update_exp:
+	$(builddir)/iuup_test >$(srcdir)/iuup_test.ok 2>$(srcdir)/iuup_test.err
diff --git a/tests/iuup/iuup_test.c b/tests/iuup/iuup_test.c
new file mode 100644
index 0000000..8d96f06
--- /dev/null
+++ b/tests/iuup/iuup_test.c
@@ -0,0 +1,156 @@
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/mgcp/iuup_cn_node.h>
+#include <osmocom/mgcp/iuup_protocol.h>
+
+void *ctx = NULL;
+
+static const char *dump(struct msgb *msg)
+{
+	return osmo_hexdump_nospc(msg->data, msg->len);
+}
+
+struct msgb *msgb_from_hex(const char *label, const char *hex)
+{
+	struct msgb *msg = msgb_alloc_headroom(4096 + OSMO_IUUP_HEADROOM,
+					       OSMO_IUUP_HEADROOM, label);
+	unsigned char *rc;
+	msg->l2h = msg->data;
+	rc = msgb_put(msg, osmo_hexparse(hex, msg->data, msgb_tailroom(msg)));
+	OSMO_ASSERT(rc == msg->l2h);
+	return msg;
+}
+
+const char *expect_rx_payload = NULL;
+int rx_payload(struct msgb *msg, void *node_priv)
+{
+	printf("rx_payload() invoked by iuup_cn!\n");
+	printf("        [IuUP] -RTP->\n");
+	printf("%s\n", dump(msg));
+	printf("node_priv=%p\n", node_priv);
+	if (!expect_rx_payload) {
+		printf("ERROR: did not expect rx_payload()\n");
+		exit(-1);
+	} else if (strcmp(expect_rx_payload, dump(msg))) {
+		printf("ERROR: mismatches expected msg %s\n", expect_rx_payload);
+		exit(-1);
+	} else
+		printf("ok: matches expected msg\n");
+	expect_rx_payload = NULL;
+	return 0;
+}
+
+const char *expect_tx_msg = NULL;
+int tx_msg(struct msgb *msg, void *node_priv)
+{
+	printf("tx_msg() invoked by iuup_cn!\n");
+	printf(" <-PDU- [IuUP]\n");
+	printf("%s\n", dump(msg));
+	printf("node_priv=%p\n", node_priv);
+	if (!expect_tx_msg) {
+		printf("ERROR: did not expect tx_msg()\n");
+		exit(-1);
+	} else if (strcmp(expect_tx_msg, dump(msg))) {
+		printf("ERROR: mismatches expected msg %s\n", expect_tx_msg);
+		exit(-1);
+	} else
+		printf("ok: matches expected msg\n");
+	expect_tx_msg = NULL;
+	return 0;
+}
+
+static int rx_pdu(struct osmo_iuup_cn *cn, struct msgb *msg)
+{
+	int rc;
+	printf(" -PDU-> [IuUP]\n");
+	printf("%s\n", dump(msg));
+	rc = osmo_iuup_cn_rx_pdu(cn, msg);
+	printf("rc=%d\n", rc);
+	return rc;
+}
+
+static int tx_payload(struct osmo_iuup_cn *cn, struct msgb *msg)
+{
+	int rc;
+	printf("        [IuUP] <-RTP-\n");
+	printf("%s\n", dump(msg));
+	rc = osmo_iuup_cn_tx_payload(cn, msg);
+	printf("rc=%d\n", rc);
+	return rc;
+}
+
+void test_cn_session()
+{
+	void *node_priv = (void*)0x2342;
+
+	struct osmo_iuup_cn_cfg cfg = {
+		.node_priv = node_priv,
+		.rx_payload = rx_payload,
+		.tx_msg = tx_msg,
+	};
+
+	struct osmo_iuup_cn *cn = osmo_iuup_cn_init(ctx, &cfg, __func__);
+	OSMO_ASSERT(cn);
+
+	printf("\nSend IuUP Initialization. Expecting direct tx_msg() of the Initialization Ack\n");
+	expect_tx_msg = "8060dc5219495e3f00010111" /* RTP header */
+			"e4002400"; /* IuUP Init Ack */
+	rx_pdu(cn,
+	       msgb_from_hex("IuUP-Init",
+			     "8060dc5219495e3f00010111" /* <- RTP header */
+			     "e000df99" /* <- IuUP header */
+			     "160051673c01270000820000001710000100" /* IuUP params */));
+
+#define RTP_HEADER "8060944c6256042c00010102"
+#define IUUP_HEADER "0100e2b3"
+#define RTP_PAYLOAD "6cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0"
+	printf("\nReceive payload encapsulated in IuUP. Expecting rx_payload() of just RTP packet\n");
+	printf("i.e. should strip away " IUUP_HEADER "\n");
+	expect_rx_payload = RTP_HEADER RTP_PAYLOAD;
+	rx_pdu(cn,
+	       msgb_from_hex("IuUP-Data",
+			     RTP_HEADER IUUP_HEADER RTP_PAYLOAD));
+
+	printf("\nTransmit RTP. Expecting tx_msg() with inserted IuUP header\n");
+	expect_tx_msg = RTP_HEADER "000002b3" RTP_PAYLOAD;
+	tx_payload(cn,
+		   msgb_from_hex("RTP data", RTP_HEADER RTP_PAYLOAD));
+
+	printf("\nMore RTP, each time the Frame Nr advances, causing a new header CRC.\n");
+	expect_tx_msg = RTP_HEADER "0100e2b3" RTP_PAYLOAD;
+	tx_payload(cn,
+		   msgb_from_hex("RTP data", RTP_HEADER RTP_PAYLOAD));
+	expect_tx_msg = RTP_HEADER "02007eb3" RTP_PAYLOAD;
+	tx_payload(cn,
+		   msgb_from_hex("RTP data", RTP_HEADER RTP_PAYLOAD));
+	expect_tx_msg = RTP_HEADER "03009eb3" RTP_PAYLOAD;
+	tx_payload(cn,
+		   msgb_from_hex("RTP data", RTP_HEADER RTP_PAYLOAD));
+
+	printf("All done.\n");
+}
+
+static const struct log_info_cat log_categories[] = {
+};
+
+const struct log_info log_info = {
+	.cat = log_categories,
+	.num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(void)
+{
+	ctx = talloc_named_const(NULL, 0, __FILE__);
+	void *msgb_ctx = msgb_talloc_ctx_init(ctx, 0);
+	osmo_init_logging2(ctx, &log_info);
+
+	test_cn_session();
+
+	talloc_free(msgb_ctx);
+	return 0;
+}
diff --git a/tests/iuup/iuup_test.err b/tests/iuup/iuup_test.err
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/iuup/iuup_test.err
diff --git a/tests/iuup/iuup_test.ok b/tests/iuup/iuup_test.ok
new file mode 100644
index 0000000..2b09c66
--- /dev/null
+++ b/tests/iuup/iuup_test.ok
@@ -0,0 +1,58 @@
+
+Send IuUP Initialization. Expecting direct tx_msg() of the Initialization Ack
+ -PDU-> [IuUP]
+8060dc5219495e3f00010111e000df99160051673c01270000820000001710000100
+tx_msg() invoked by iuup_cn!
+ <-PDU- [IuUP]
+8060dc5219495e3f00010111e4002400
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+
+Receive payload encapsulated in IuUP. Expecting rx_payload() of just RTP packet
+i.e. should strip away 0100e2b3
+ -PDU-> [IuUP]
+8060944c6256042c000101020100e2b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+rx_payload() invoked by iuup_cn!
+        [IuUP] -RTP->
+8060944c6256042c000101026cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+
+Transmit RTP. Expecting tx_msg() with inserted IuUP header
+        [IuUP] <-RTP-
+8060944c6256042c000101026cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+tx_msg() invoked by iuup_cn!
+ <-PDU- [IuUP]
+8060944c6256042c00010102000002b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+
+More RTP, each time the Frame Nr advances, causing a new header CRC.
+        [IuUP] <-RTP-
+8060944c6256042c000101026cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+tx_msg() invoked by iuup_cn!
+ <-PDU- [IuUP]
+8060944c6256042c000101020100e2b36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+        [IuUP] <-RTP-
+8060944c6256042c000101026cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+tx_msg() invoked by iuup_cn!
+ <-PDU- [IuUP]
+8060944c6256042c0001010202007eb36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+        [IuUP] <-RTP-
+8060944c6256042c000101026cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+tx_msg() invoked by iuup_cn!
+ <-PDU- [IuUP]
+8060944c6256042c0001010203009eb36cfb23bc46d18180c3e5ffe040045600005a7d35b625b80005fff03214ced0
+node_priv=0x2342
+ok: matches expected msg
+rc=0
+All done.
diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c
index a540056..ebcad8e 100644
--- a/tests/mgcp/mgcp_test.c
+++ b/tests/mgcp/mgcp_test.c
@@ -28,6 +28,7 @@
 #include <osmocom/mgcp/mgcp_endp.h>
 #include <osmocom/mgcp/mgcp_sdp.h>
 #include <osmocom/mgcp/mgcp_codec.h>
+#include <osmocom/mgcp/mgcp_internal.h>
 
 #include <osmocom/core/application.h>
 #include <osmocom/core/talloc.h>
@@ -1189,7 +1190,7 @@
 void mgcp_patch_and_count(struct mgcp_endpoint *endp,
 			  struct mgcp_rtp_state *state,
 			  struct mgcp_rtp_end *rtp_end,
-			  struct sockaddr_in *addr, char *data, int len);
+			  struct sockaddr_in *addr, struct msgb *msg);
 
 static void test_packet_error_detection(int patch_ssrc, int patch_ts)
 {
@@ -1200,7 +1201,6 @@
 	struct mgcp_rtp_state state;
 	struct mgcp_rtp_end *rtp;
 	struct sockaddr_in addr = { 0 };
-	char buffer[4096];
 	uint32_t last_ssrc = 0;
 	uint32_t last_timestamp = 0;
 	uint32_t last_seqno = 0;
@@ -1247,16 +1247,17 @@
 
 	for (i = 0; i < ARRAY_SIZE(test_rtp_packets1); ++i) {
 		struct rtp_packet_info *info = test_rtp_packets1 + i;
+		struct msgb *msg = msgb_alloc(4096, __func__);
 
 		force_monotonic_time_us = round(1000000.0 * info->txtime);
 
-		OSMO_ASSERT(info->len <= sizeof(buffer));
+		OSMO_ASSERT(info->len <= msgb_tailroom(msg));
 		OSMO_ASSERT(info->len >= 0);
-		memmove(buffer, info->data, info->len);
+		msg->l3h = msgb_put(msg, info->len);
+		memcpy((char*)msgb_l3(msg), info->data, info->len);
 		mgcp_rtp_end_config(&endp, 1, rtp);
 
-		mgcp_patch_and_count(&endp, &state, rtp, &addr,
-				     buffer, info->len);
+		mgcp_patch_and_count(&endp, &state, rtp, &addr, msg);
 
 		if (state.out_stream.ssrc != last_ssrc) {
 			printf("Output SSRC changed to %08x\n",
@@ -1283,6 +1284,8 @@
 		last_out_ts_err_cnt = state.out_stream.err_ts_ctr->current;
 		last_timestamp = state.out_stream.last_timestamp;
 		last_seqno = state.out_stream.last_seq;
+
+		msgb_free(msg);
 	}
 
 	force_monotonic_time_us = -1;
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 3585bf0..0c3f802 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -13,3 +13,10 @@
 cat $abs_srcdir/mgcp/mgcp_test.ok > expout
 AT_CHECK([$abs_top_builddir/tests/mgcp/mgcp_test], [], [expout], [ignore])
 AT_CLEANUP
+
+AT_SETUP([iuup])
+AT_KEYWORDS([iuup])
+cat $abs_srcdir/iuup/iuup_test.ok > expout
+cat $abs_srcdir/iuup/iuup_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/iuup/iuup_test], [], [expout], [experr])
+AT_CLEANUP