Implement IuCS (large refactoring and addition)

osmo-nitb becomes osmo-msc
add DIUCS debug log constant
add iucs.[hc]
add msc vty, remove nitb vty
add libiudummy, to avoid linking Iu deps in tests
Use new msc_tx_dtap() instead of gsm0808_submit_dtap()
libmgcp: add mgcpgw client API
bridge calls via mgcpgw

Enable MSC specific CTRL commands, bsc_base_ctrl_cmds_install() still needs to
be split up.

Change-Id: I5b5b6a9678b458affa86800afb1ec726e66eed88
diff --git a/src/libmgcp/Makefile.am b/src/libmgcp/Makefile.am
index 5faf602..5d7844d 100644
--- a/src/libmgcp/Makefile.am
+++ b/src/libmgcp/Makefile.am
@@ -30,11 +30,14 @@
 	$(NULL)
 
 libmgcp_a_SOURCES = \
+	mgcp_common.c \
 	mgcp_protocol.c \
 	mgcp_network.c \
 	mgcp_vty.c \
 	mgcp_osmux.c \
 	mgcp_sdp.c \
+	mgcpgw_client.c \
+	mgcpgw_client_vty.c \
 	$(NULL)
 if BUILD_MGCP_TRANSCODING
 libmgcp_a_SOURCES += \
diff --git a/src/libmgcp/mgcp_common.c b/src/libmgcp/mgcp_common.c
new file mode 100644
index 0000000..43c8667
--- /dev/null
+++ b/src/libmgcp/mgcp_common.c
@@ -0,0 +1,54 @@
+/* Media Gateway Control Protocol Media Gateway: RFC 3435 */
+/* Implementations useful both for the MGCP GW as well as MGCP GW clients */
+
+/*
+ * (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * 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/core/utils.h>
+#include <openbsc/mgcp.h>
+
+const struct value_string mgcp_connection_mode_strs[] = {
+	{ MGCP_CONN_NONE, "none" },
+	{ MGCP_CONN_RECV_SEND, "sendrecv" },
+	{ MGCP_CONN_SEND_ONLY, "sendonly" },
+	{ MGCP_CONN_RECV_ONLY, "recvonly" },
+	{ MGCP_CONN_LOOPBACK, "loopback" },
+	{ 0, NULL }
+};
+
+/* Ensure that the msg->l2h is NUL terminated. */
+int mgcp_msg_terminate_nul(struct msgb *msg)
+{
+	unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */
+	if (tail[-1] == '\0')
+		/* nothing to do */;
+	else if (msgb_tailroom(msg) > 0)
+		tail[0] = '\0';
+	else if (tail[-1] == '\r' || tail[-1] == '\n')
+		tail[-1] = '\0';
+	else {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: "
+		     "Length: %d, Buffer size: %d\n",
+		     msgb_l2len(msg), msg->data_len);
+		return -ENOTSUP;
+	}
+	return 0;
+}
diff --git a/src/libmgcp/mgcp_network.c b/src/libmgcp/mgcp_network.c
index abce6e4..c9fe179 100644
--- a/src/libmgcp/mgcp_network.c
+++ b/src/libmgcp/mgcp_network.c
@@ -537,7 +537,11 @@
 	if (payload < 0)
 		return;
 
+#if 0
+	DEBUGP(DMGCP, "Payload hdr payload %u -> endp payload %u\n",
+	       rtp_hdr->payload_type, payload);
 	rtp_hdr->payload_type = payload;
+#endif
 }
 
 /*
@@ -588,6 +592,14 @@
 	struct mgcp_rtp_state *rtp_state;
 	int tap_idx;
 
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n",
+	     ENDPOINT_NUMBER(endp),
+	     dest == MGCP_DEST_NET? "net" : "bts",
+	     tcfg->audio_loop,
+	     endp->conn_mode,
+	     endp->conn_mode == MGCP_CONN_LOOPBACK);
+
 	/* For loop toggle the destination and then dispatch. */
 	if (tcfg->audio_loop)
 		dest = !dest;
@@ -605,10 +617,35 @@
 		rtp_state = &endp->net_state;
 		tap_idx = MGCP_TAP_BTS_OUT;
 	}
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n",
+	     ENDPOINT_NUMBER(endp),
+	     dest == MGCP_DEST_NET? "net" : "bts",
 
-	if (!rtp_end->output_enabled)
+	     inet_ntoa(endp->net_end.addr),
+	     ntohs(endp->net_end.rtp_port),
+	     ntohs(endp->net_end.rtcp_port),
+
+	     inet_ntoa(endp->bts_end.addr),
+	     ntohs(endp->bts_end.rtp_port),
+	     ntohs(endp->bts_end.rtcp_port),
+
+	     inet_ntoa(rtp_end->addr),
+	     ntohs(rtp_end->rtp_port),
+	     ntohs(rtp_end->rtcp_port)
+	    );
+
+	if (!rtp_end->output_enabled) {
 		rtp_end->dropped_packets += 1;
-	else if (is_rtp) {
+		LOGP(DMGCP, LOGL_DEBUG,
+		     "endpoint %x output disabled, drop to %s %s %d %d\n",
+		     ENDPOINT_NUMBER(endp),
+		     dest == MGCP_DEST_NET? "net" : "bts",
+		     inet_ntoa(rtp_end->addr),
+		     ntohs(rtp_end->rtp_port),
+		     ntohs(rtp_end->rtcp_port)
+		    );
+	} else if (is_rtp) {
 		int cont;
 		int nbytes = 0;
 		int len = rc;
@@ -619,8 +656,17 @@
 				break;
 
 			mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len);
+		LOGP(DMGCP, LOGL_DEBUG,
+		     "endpoint %x process/send to %s %s %d %d\n",
+		     ENDPOINT_NUMBER(endp),
+		     (dest == MGCP_DEST_NET)? "net" : "bts",
+		     inet_ntoa(rtp_end->addr),
+		     ntohs(rtp_end->rtp_port),
+		     ntohs(rtp_end->rtcp_port)
+		    );
 			forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx],
 				     buf, len);
+
 			rc = mgcp_udp_send(rtp_end->rtp.fd,
 					   &rtp_end->addr,
 					   rtp_end->rtp_port, buf, len);
@@ -632,6 +678,15 @@
 		} while (len > 0);
 		return nbytes;
 	} else if (!tcfg->omit_rtcp) {
+		LOGP(DMGCP, LOGL_DEBUG,
+		     "endpoint %x send to %s %s %d %d\n",
+		     ENDPOINT_NUMBER(endp),
+		     dest == MGCP_DEST_NET? "net" : "bts",
+		     inet_ntoa(rtp_end->addr),
+		     ntohs(rtp_end->rtp_port),
+		     ntohs(rtp_end->rtcp_port)
+		    );
+
 		return mgcp_udp_send(rtp_end->rtcp.fd,
 				     &rtp_end->addr,
 				     rtp_end->rtcp_port, buf, rc);
@@ -676,9 +731,28 @@
 	if (rc <= 0)
 		return -1;
 
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "endpoint %x",
+	     ENDPOINT_NUMBER(endp));
+	LOGPC(DMGCP, LOGL_DEBUG,
+	      " from net %s %d",
+	      inet_ntoa(addr.sin_addr),
+	      ntohs(addr.sin_port));
+	LOGPC(DMGCP, LOGL_DEBUG,
+	      " net_end %s %d %d",
+	      inet_ntoa(endp->net_end.addr),
+	      ntohs(endp->net_end.rtp_port),
+	      ntohs(endp->net_end.rtcp_port));
+	LOGPC(DMGCP, LOGL_DEBUG,
+	      " bts_end %s %d %d\n",
+	      inet_ntoa(endp->bts_end.addr),
+	      ntohs(endp->bts_end.rtp_port),
+	      ntohs(endp->bts_end.rtcp_port)
+	     );
+
 	if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) {
 		LOGP(DMGCP, LOGL_ERROR,
-			"Endpoint 0x%x data from wrong address %s vs. ",
+			"rtp_data_net: Endpoint 0x%x data from wrong address %s vs. ",
 			ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr));
 		LOGPC(DMGCP, LOGL_ERROR,
 			"%s\n", inet_ntoa(endp->net_end.addr));
@@ -691,7 +765,7 @@
 		if (endp->net_end.rtp_port != addr.sin_port &&
 		    endp->net_end.rtcp_port != addr.sin_port) {
 			LOGP(DMGCP, LOGL_ERROR,
-				"Data from wrong source port %d on 0x%x\n",
+				"rtp_data_net: Data from wrong source port %d on 0x%x\n",
 				ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
 			return -1;
 		}
@@ -701,6 +775,12 @@
 		break;
 	}
 
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "rtp_data_net: Endpoint %x data from %s %d\n",
+	     ENDPOINT_NUMBER(endp),
+	     inet_ntoa(addr.sin_addr),
+	     ntohs(addr.sin_port));
+
 	/* throw away the dummy message */
 	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
 		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n",
@@ -780,7 +860,7 @@
 
 	if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) {
 		LOGP(DMGCP, LOGL_ERROR,
-			"Data from wrong bts %s on 0x%x\n",
+			"rtp_data_bts: Data from wrong bts %s on 0x%x\n",
 			inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp));
 		return -1;
 	}
@@ -788,11 +868,17 @@
 	if (endp->bts_end.rtp_port != addr.sin_port &&
 	    endp->bts_end.rtcp_port != addr.sin_port) {
 		LOGP(DMGCP, LOGL_ERROR,
-			"Data from wrong bts source port %d on 0x%x\n",
+			"rtp_data_bts: ata from wrong bts source port %d on 0x%x\n",
 			ntohs(addr.sin_port), ENDPOINT_NUMBER(endp));
 		return -1;
 	}
 
+	LOGP(DMGCP, LOGL_DEBUG,
+	     "rtp_data_bts: Endpoint %x data from %s %d\n",
+	     ENDPOINT_NUMBER(endp),
+	     inet_ntoa(addr.sin_addr),
+	     ntohs(addr.sin_port));
+
 	/* throw away the dummy message */
 	if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) {
 		LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n",
@@ -808,6 +894,9 @@
 
 	switch (endp->type) {
 	case MGCP_RTP_DEFAULT:
+		LOGP(DMGCP, LOGL_DEBUG,
+		     "rtp_data_bts: Endpoint %x MGCP_RTP_DEFAULT\n",
+		     ENDPOINT_NUMBER(endp));
 		return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP,
 				 &addr, buf, rc);
 	case MGCP_RTP_TRANSCODED:
diff --git a/src/libmgcp/mgcp_protocol.c b/src/libmgcp/mgcp_protocol.c
index 4fcadd9..78e41f1 100644
--- a/src/libmgcp/mgcp_protocol.c
+++ b/src/libmgcp/mgcp_protocol.c
@@ -318,26 +318,14 @@
 	int i, code, handled = 0;
 	struct msgb *resp = NULL;
 	char *data;
-	unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */
 
 	if (msgb_l2len(msg) < 4) {
 		LOGP(DMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len);
 		return NULL;
 	}
 
-	/* Ensure that the msg->l2h is NUL terminated. */
-	if (tail[-1] == '\0')
-		/* nothing to do */;
-	else if (msgb_tailroom(msg) > 0)
-		tail[0] = '\0';
-	else if (tail[-1] == '\r' || tail[-1] == '\n')
-		tail[-1] = '\0';
-	else {
-		LOGP(DMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: "
-		     "Length: %d, Buffer size: %d\n",
-		     msgb_l2len(msg), msg->data_len);
+	if (mgcp_msg_terminate_nul(msg))
 		return NULL;
-	}
 
         /* attempt to treat it as a response */
         if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
@@ -547,6 +535,11 @@
 	endp->bts_end.output_enabled =
 		endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0;
 
+	LOGP(DMGCP, LOGL_DEBUG, "endpoint %x connection mode '%s' %d output_enabled net %d bts %d\n",
+	     ENDPOINT_NUMBER(endp),
+	     msg, endp->conn_mode, endp->net_end.output_enabled,
+	     endp->bts_end.output_enabled);
+
 	return ret;
 }
 
@@ -972,6 +965,8 @@
 			break;
 		case MGCP_POLICY_DEFER:
 			/* stop processing */
+			LOGP(DMGCP, LOGL_DEBUG, "endp %x MDCX defer\n",
+			     ENDPOINT_NUMBER(endp));
 			return NULL;
 			break;
 		case MGCP_POLICY_CONT:
@@ -1003,6 +998,8 @@
 
 
 out_silent:
+	LOGP(DMGCP, LOGL_DEBUG, "endp %x Modify endpoint: silent exit\n",
+	     ENDPOINT_NUMBER(endp));
 	return NULL;
 }
 
diff --git a/src/libmgcp/mgcpgw_client.c b/src/libmgcp/mgcpgw_client.c
new file mode 100644
index 0000000..9f0c84d
--- /dev/null
+++ b/src/libmgcp/mgcpgw_client.c
@@ -0,0 +1,549 @@
+/* mgcp_utils - common functions to setup an MGCP connection
+ */
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * 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 <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/mgcpgw_client.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/debug.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+struct mgcpgw_client {
+	struct mgcpgw_client_conf actual;
+	uint32_t remote_addr;
+	struct osmo_wqueue wq;
+	mgcp_trans_id_t next_trans_id;
+	uint16_t next_endpoint;
+	struct llist_head responses_pending;
+};
+
+void mgcpgw_client_conf_init(struct mgcpgw_client_conf *conf)
+{
+	/* NULL and -1 default to MGCPGW_CLIENT_*_DEFAULT values */
+	*conf = (struct mgcpgw_client_conf){
+		.local_addr = NULL,
+		.local_port = -1,
+		.remote_addr = NULL,
+		.remote_port = -1,
+	};
+}
+
+unsigned int mgcpgw_client_next_endpoint(struct mgcpgw_client *client)
+{
+	return client->next_endpoint ++;
+}
+
+static void mgcpgw_client_handle_response(struct mgcpgw_client *mgcp,
+					  struct mgcp_response_pending *pending,
+					  struct mgcp_response *response)
+{
+	if (!pending) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot handle NULL response\n");
+		return;
+	}
+	if (pending->response_cb)
+		pending->response_cb(response, pending->priv);
+	else
+		LOGP(DMGCP, LOGL_INFO, "MGCP response ignored (NULL cb)\n");
+	talloc_free(pending);
+}
+
+static int mgcp_response_parse_head(struct mgcp_response *r, struct msgb *msg)
+{
+	int comment_pos;
+	char *end;
+
+	if (mgcp_msg_terminate_nul(msg))
+		goto response_parse_failure;
+
+	r->body = (char *)msg->data;
+
+        if (sscanf(r->body, "%3d %u %n",
+		   &r->head.response_code, &r->head.trans_id,
+		   &comment_pos) != 2)
+		goto response_parse_failure;
+
+	r->head.comment = r->body + comment_pos;
+	end = strchr(r->head.comment, '\r');
+	if (!end)
+		goto response_parse_failure;
+	/* Mark the end of the comment */
+	*end = '\0';
+	r->body = end + 1;
+	if (r->body[0] == '\n')
+		r->body ++;
+	return 0;
+
+response_parse_failure:
+	LOGP(DMGCP, LOGL_ERROR,
+	     "Failed to parse MGCP response header\n");
+	return -EINVAL;
+}
+
+/* TODO undup against mgcp_protocol.c:mgcp_check_param() */
+static bool mgcp_line_is_valid(const char *line)
+{
+	const size_t line_len = strlen(line);
+	if (line[0] == '\0')
+		return true;
+
+	if (line_len < 2
+	    || line[1] != '=') {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Wrong MGCP option format: '%s'\n",
+		     line);
+		return false;
+	}
+
+	return true;
+}
+
+/* Parse a line like "m=audio 16002 RTP/AVP 98" */
+static int mgcp_parse_audio(struct mgcp_response *r, const char *line)
+{
+        if (sscanf(line, "m=audio %hu",
+		   &r->audio_port) != 1)
+		goto response_parse_failure;
+
+	return 0;
+
+response_parse_failure:
+	LOGP(DMGCP, LOGL_ERROR,
+	     "Failed to parse MGCP response header\n");
+	return -EINVAL;
+}
+
+int mgcp_response_parse_params(struct mgcp_response *r)
+{
+	char *line;
+	int rc;
+	OSMO_ASSERT(r->body);
+	char *data = strstr(r->body, "\n\n");
+
+	if (!data) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "MGCP response: cannot find start of parameters\n");
+		return -EINVAL;
+	}
+
+	/* Advance to after the \n\n, replace the second \n with \0. That's
+	 * where the parameters start. */
+	data ++;
+	*data = '\0';
+	data ++;
+
+	for_each_line(line, data) {
+		if (!mgcp_line_is_valid(line))
+			return -EINVAL;
+
+		switch (line[0]) {
+		case 'm':
+			rc = mgcp_parse_audio(r, line);
+			if (rc)
+				return rc;
+			break;
+		default:
+			/* skip unhandled parameters */
+			break;
+		}
+	}
+	return 0;
+}
+
+static struct mgcp_response_pending *mgcpgw_client_response_pending_get(
+					 struct mgcpgw_client *mgcp,
+					 struct mgcp_response *r)
+{
+	struct mgcp_response_pending *pending;
+	if (!r)
+		return NULL;
+	llist_for_each_entry(pending, &mgcp->responses_pending, entry) {
+		if (pending->trans_id == r->head.trans_id) {
+			llist_del(&pending->entry);
+			return pending;
+		}
+	}
+	return NULL;
+}
+
+/* Feed an MGCP message into the receive processing.
+ * Parse the head and call any callback registered for the transaction id found
+ * in the MGCP message. This is normally called directly from the internal
+ * mgcp_do_read that reads from the socket connected to the MGCP gateway. This
+ * function is published mainly to be able to feed data from the test suite.
+ */
+int mgcpgw_client_rx(struct mgcpgw_client *mgcp, struct msgb *msg)
+{
+	struct mgcp_response r = { 0 };
+	struct mgcp_response_pending *pending;
+	int rc;
+
+	rc = mgcp_response_parse_head(&r, msg);
+	if (rc) {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot parse MGCP response\n");
+		return -1;
+	}
+
+	pending = mgcpgw_client_response_pending_get(mgcp, &r);
+	if (!pending) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot find matching MGCP transaction for trans_id %d\n",
+		     r.head.trans_id);
+		return -1;
+	}
+
+	mgcpgw_client_handle_response(mgcp, pending, &r);
+	return 0;
+}
+
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+	struct mgcpgw_client *mgcp = fd->data;
+	struct msgb *msg;
+	int ret;
+
+	msg = msgb_alloc_headroom(4096, 128, "mgcp_from_gw");
+	if (!msg) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n");
+		return -1;
+	}
+
+	ret = read(fd->fd, msg->data, 4096 - 128);
+	if (ret <= 0) {
+		LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno));
+		msgb_free(msg);
+		return -1;
+	} else if (ret > 4096 - 128) {
+		LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret);
+		msgb_free(msg);
+		return -1;
+        }
+
+	msg->l2h = msgb_put(msg, ret);
+	ret = mgcpgw_client_rx(mgcp, msg);
+	talloc_free(msg);
+	return ret;
+}
+
+static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg)
+{
+	int ret;
+	static char strbuf[4096];
+	unsigned int l = msg->len < sizeof(strbuf)-1 ? msg->len : sizeof(strbuf)-1;
+	strncpy(strbuf, (const char*)msg->data, l);
+	strbuf[l] = '\0';
+	DEBUGP(DMGCP, "Tx MGCP msg to MGCP GW: '%s'\n", strbuf);
+
+	LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len);
+
+	ret = write(fd->fd, msg->data, msg->len);
+	if (ret != msg->len)
+		LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP"
+		     " GW: %s\n", strerror(errno));
+
+	return ret;
+}
+
+struct mgcpgw_client *mgcpgw_client_init(void *ctx,
+					 struct mgcpgw_client_conf *conf)
+{
+	struct mgcpgw_client *mgcp;
+
+	mgcp = talloc_zero(ctx, struct mgcpgw_client);
+
+	INIT_LLIST_HEAD(&mgcp->responses_pending);
+
+	mgcp->next_trans_id = 1;
+	mgcp->next_endpoint = 1;
+
+	mgcp->actual.local_addr = conf->local_addr ? conf->local_addr :
+		MGCPGW_CLIENT_LOCAL_ADDR_DEFAULT;
+	mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port :
+		MGCPGW_CLIENT_LOCAL_PORT_DEFAULT;
+
+	mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr :
+		MGCPGW_CLIENT_REMOTE_ADDR_DEFAULT;
+	mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port :
+		MGCPGW_CLIENT_REMOTE_PORT_DEFAULT;
+
+	return mgcp;
+}
+
+int mgcpgw_client_connect(struct mgcpgw_client *mgcp)
+{
+	int on;
+	struct sockaddr_in addr;
+	struct osmo_wqueue *wq;
+	int rc;
+
+	if (!mgcp) {
+		LOGP(DMGCP, LOGL_FATAL, "MGCPGW client not initialized properly\n");
+		return -EINVAL;
+	}
+
+	wq = &mgcp->wq;
+
+	wq->bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+	if (wq->bfd.fd < 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno);
+		return -errno;
+	}
+
+	on = 1;
+	if (setsockopt(wq->bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL,
+		     "Failed to initialize socket for MGCP GW: %s\n",
+		     strerror(errno));
+		rc = -errno;
+		goto error_close_fd;
+	}
+
+	/* bind socket */
+	memset(&addr, 0, sizeof(addr));
+	addr.sin_family = AF_INET;
+	inet_aton(mgcp->actual.local_addr, &addr.sin_addr);
+	addr.sin_port = htons(mgcp->actual.local_port);
+	if (bind(wq->bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL,
+		     "Failed to bind for MGCP GW to %s %u\n",
+		     mgcp->actual.local_addr, mgcp->actual.local_port);
+		rc = -errno;
+		goto error_close_fd;
+	}
+
+	/* connect to the remote */
+	inet_aton(mgcp->actual.remote_addr, &addr.sin_addr);
+	addr.sin_port = htons(mgcp->actual.remote_port);
+	if (connect(wq->bfd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		LOGP(DMGCP, LOGL_FATAL,
+		     "Failed to connect to MGCP GW at %s %u: %s\n",
+		     mgcp->actual.remote_addr, mgcp->actual.remote_port,
+		     strerror(errno));
+		rc = -errno;
+		goto error_close_fd;
+	}
+
+	mgcp->remote_addr = htonl(addr.sin_addr.s_addr);
+
+	osmo_wqueue_init(wq, 10);
+	wq->bfd.when = BSC_FD_READ;
+	wq->bfd.data = mgcp;
+	wq->read_cb = mgcp_do_read;
+	wq->write_cb = mgcp_do_write;
+
+	if (osmo_fd_register(&wq->bfd) != 0) {
+		LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n");
+		rc = -EIO;
+		goto error_close_fd;
+	}
+	LOGP(DMGCP, LOGL_INFO, "MGCP GW connection: %s:%u -> %s:%u\n",
+	     mgcp->actual.local_addr, mgcp->actual.local_port,
+	     mgcp->actual.remote_addr, mgcp->actual.remote_port);
+
+	return 0;
+error_close_fd:
+	close(wq->bfd.fd);
+	wq->bfd.fd = -1;
+	return rc;
+}
+
+const char *mgcpgw_client_remote_addr_str(struct mgcpgw_client *mgcp)
+{
+	return mgcp->actual.remote_addr;
+}
+
+uint16_t mgcpgw_client_remote_port(struct mgcpgw_client *mgcp)
+{
+	return mgcp->actual.remote_port;
+}
+
+/* Return the MGCP GW binary IPv4 address in network byte order. */
+uint32_t mgcpgw_client_remote_addr_n(struct mgcpgw_client *mgcp)
+{
+	return mgcp->remote_addr;
+}
+
+struct mgcp_response_pending * mgcpgw_client_pending_add(
+					struct mgcpgw_client *mgcp,
+					mgcp_trans_id_t trans_id,
+					mgcp_response_cb_t response_cb,
+					void *priv)
+{
+	struct mgcp_response_pending *pending;
+
+	pending = talloc_zero(mgcp, struct mgcp_response_pending);
+	pending->trans_id = trans_id;
+	pending->response_cb = response_cb;
+	pending->priv = priv;
+	llist_add_tail(&pending->entry, &mgcp->responses_pending);
+
+	return pending;
+}
+
+/* Send the MGCP message in msg to the MGCP GW and handle a response with
+ * response_cb. NOTE: the response_cb still needs to call
+ * mgcp_response_parse_params(response) to get the parsed parameters -- to
+ * potentially save some CPU cycles, only the head line has been parsed when
+ * the response_cb is invoked. */
+int mgcpgw_client_tx(struct mgcpgw_client *mgcp, struct msgb *msg,
+		     mgcp_response_cb_t response_cb, void *priv)
+{
+	struct mgcp_response_pending *pending;
+	mgcp_trans_id_t trans_id;
+	int rc;
+
+	trans_id = msg->cb[MSGB_CB_MGCP_TRANS_ID];
+	if (!trans_id) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Unset transaction id in mgcp send request\n");
+		talloc_free(msg);
+		return -EINVAL;
+	}
+
+	pending = mgcpgw_client_pending_add(mgcp, trans_id, response_cb, priv);
+
+	if (msgb_l2len(msg) > 4096) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Cannot send, MGCP message too large: %u\n",
+		     msgb_l2len(msg));
+		msgb_free(msg);
+		rc = -EINVAL;
+		goto mgcp_tx_error;
+	}
+
+	rc = osmo_wqueue_enqueue(&mgcp->wq, msg);
+	if (rc) {
+		LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW\n");
+		msgb_free(msg);
+		goto mgcp_tx_error;
+	} else
+		LOGP(DMGCP, LOGL_INFO, "Queued %u bytes for MGCP GW\n",
+		     msgb_l2len(msg));
+	return 0;
+
+mgcp_tx_error:
+	/* Pass NULL to response cb to indicate an error */
+	mgcpgw_client_handle_response(mgcp, pending, NULL);
+	return -1;
+}
+
+static struct msgb *mgcp_msg_from_buf(mgcp_trans_id_t trans_id,
+				      const char *buf, int len)
+{
+	struct msgb *msg;
+
+	if (len > (4096 - 128)) {
+		LOGP(DMGCP, LOGL_ERROR, "Cannot send to MGCP GW:"
+		     " message too large: %d\n", len);
+		return NULL;
+	}
+
+	msg = msgb_alloc_headroom(4096, 128, "MGCP tx");
+	OSMO_ASSERT(msg);
+
+	char *dst = (char*)msgb_put(msg, len);
+	memcpy(dst, buf, len);
+	msg->l2h = msg->data;
+	msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id;
+
+	return msg;
+}
+
+static struct msgb *mgcp_msg_from_str(mgcp_trans_id_t trans_id,
+				      const char *fmt, ...)
+{
+	static char compose[4096 - 128];
+	va_list ap;
+	int len;
+	OSMO_ASSERT(fmt);
+
+	va_start(ap, fmt);
+	len = vsnprintf(compose, sizeof(compose), fmt, ap);
+	va_end(ap);
+	if (len >= sizeof(compose)) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Message too large: trans_id=%u len=%d\n",
+		     trans_id, len);
+		return NULL;
+	}
+	if (len < 1) {
+		LOGP(DMGCP, LOGL_ERROR,
+		     "Failed to compose message: trans_id=%u len=%d\n",
+		     trans_id, len);
+		return NULL;
+	}
+	return mgcp_msg_from_buf(trans_id, compose, len);
+}
+
+static mgcp_trans_id_t mgcpgw_client_next_trans_id(struct mgcpgw_client *mgcp)
+{
+	/* avoid zero trans_id to distinguish from unset trans_id */
+	if (!mgcp->next_trans_id)
+		mgcp->next_trans_id ++;
+	return mgcp->next_trans_id ++;
+}
+
+struct msgb *mgcp_msg_crcx(struct mgcpgw_client *mgcp,
+			   uint16_t rtp_endpoint, unsigned int call_id,
+			   enum mgcp_connection_mode mode)
+{
+	mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp);
+	return mgcp_msg_from_str(trans_id,
+		 "CRCX %u %x@mgw MGCP 1.0\r\n"
+		 "C: %x\r\n"
+		 "L: p:20, a:AMR, nt:IN\r\n"
+		 "M: %s\r\n"
+		 ,
+		 trans_id,
+		 rtp_endpoint,
+		 call_id,
+		 mgcp_cmode_name(mode));
+}
+
+struct msgb *mgcp_msg_mdcx(struct mgcpgw_client *mgcp,
+			   uint16_t rtp_endpoint, const char *rtp_conn_addr,
+			   uint16_t rtp_port, enum mgcp_connection_mode mode)
+
+{
+	mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp);
+	return mgcp_msg_from_str(trans_id,
+		 "MDCX %u %x@mgw MGCP 1.0\r\n"
+		 "M: %s\r\n"
+		 "\r\n"
+		 "c=IN IP4 %s\r\n"
+		 "m=audio %u RTP/AVP 255\r\n"
+		 ,
+		 trans_id,
+		 rtp_endpoint,
+		 mgcp_cmode_name(mode),
+		 rtp_conn_addr,
+		 rtp_port);
+}
diff --git a/src/libmgcp/mgcpgw_client_vty.c b/src/libmgcp/mgcpgw_client_vty.c
new file mode 100644
index 0000000..a42ee4e
--- /dev/null
+++ b/src/libmgcp/mgcpgw_client_vty.c
@@ -0,0 +1,116 @@
+/* MGCPGW client interface to quagga VTY */
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c)
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * 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 <inttypes.h>
+#include <stdlib.h>
+#include <talloc.h>
+
+#include <osmocom/vty/command.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/mgcpgw_client.h>
+
+#define MGCPGW_STR "MGCP gateway configuration for RTP streams\n"
+
+struct mgcpgw_client_conf *global_mgcpgw_client_conf = NULL;
+
+DEFUN(cfg_mgcpgw_local_ip, cfg_mgcpgw_local_ip_cmd,
+      "mgcpgw local-ip A.B.C.D",
+      MGCPGW_STR "local bind to connect to MGCP gateway with\n"
+      "local bind IP address\n")
+{
+	if (!global_mgcpgw_client_conf)
+		return CMD_ERR_NOTHING_TODO;
+	global_mgcpgw_client_conf->local_addr =
+		talloc_strdup(gsmnet_from_vty(vty), argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcpgw_local_port, cfg_mgcpgw_local_port_cmd,
+      "mgcpgw local-port <0-65535>",
+      MGCPGW_STR "local bind to connect to MGCP gateway with\n"
+      "local bind port\n")
+{
+	if (!global_mgcpgw_client_conf)
+		return CMD_ERR_NOTHING_TODO;
+	global_mgcpgw_client_conf->local_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcpgw_remote_ip, cfg_mgcpgw_remote_ip_cmd,
+      "mgcpgw remote-ip A.B.C.D",
+      MGCPGW_STR "remote bind to connect to MGCP gateway with\n"
+      "remote bind IP address\n")
+{
+	if (!global_mgcpgw_client_conf)
+		return CMD_ERR_NOTHING_TODO;
+	global_mgcpgw_client_conf->remote_addr =
+		talloc_strdup(gsmnet_from_vty(vty), argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcpgw_remote_port, cfg_mgcpgw_remote_port_cmd,
+      "mgcpgw remote-port <0-65535>",
+      MGCPGW_STR "remote bind to connect to MGCP gateway with\n"
+      "remote bind port\n")
+{
+	if (!global_mgcpgw_client_conf)
+		return CMD_ERR_NOTHING_TODO;
+	global_mgcpgw_client_conf->remote_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+int mgcpgw_client_config_write(struct vty *vty, const char *indent)
+{
+	const char *addr;
+	int port;
+
+	addr = global_mgcpgw_client_conf->local_addr;
+	if (addr)
+		vty_out(vty, "%smgcpgw local-ip %s%s", indent, addr,
+			VTY_NEWLINE);
+	port = global_mgcpgw_client_conf->local_port;
+	if (port >= 0)
+		vty_out(vty, "%smgcpgw local-port %u%s", indent,
+			(uint16_t)port, VTY_NEWLINE);
+
+	addr = global_mgcpgw_client_conf->remote_addr;
+	if (addr)
+		vty_out(vty, "%smgcpgw remote-ip %s%s", indent, addr,
+			VTY_NEWLINE);
+	port = global_mgcpgw_client_conf->remote_port;
+	if (port >= 0)
+		vty_out(vty, "%smgcpgw remote-port %u%s", indent,
+			(uint16_t)port, VTY_NEWLINE);
+
+	return CMD_SUCCESS;
+}
+
+void mgcpgw_client_vty_init(int node, struct mgcpgw_client_conf *conf)
+{
+	global_mgcpgw_client_conf = conf;
+
+	install_element(node, &cfg_mgcpgw_local_ip_cmd);
+	install_element(node, &cfg_mgcpgw_local_port_cmd);
+	install_element(node, &cfg_mgcpgw_remote_ip_cmd);
+	install_element(node, &cfg_mgcpgw_remote_port_cmd);
+}