Change in osmo-mgw[master]: Add multithreading for the virtual trunk

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Hoernchen gerrit-no-reply at lists.osmocom.org
Thu Sep 9 14:06:40 UTC 2021


Hoernchen has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-mgw/+/25432 )


Change subject: Add multithreading for the virtual trunk
......................................................................

Add multithreading for the virtual trunk

The idea is rather easy: do not disturb the code too much, so each
thread operates on a mgcp_trunk sub-trunk that has
- (it is) a mgcp_trunk structure just like the parent trunk
- some thread-specific information in struct per_thread_info
* this exists in the sub-trunk, as a SINGLE pointer (this_thread_info)
to the threads own info
- a different endpoint begin offset
- and different number of endpoint
- a private copy (!) of the mgcp_config structure that allows selective
updates of config settings during runtime using the vty

The thread-trunks doen't really know that they are not a full trunk, as
far as actual "mgw-operation" is concerned

Most of the other changes deal with the (t)alloc contexts to ensure
proper parent contexts that are not mixed between threads, the only
talloc that is threadsafe is the null context with disabled null
tracking, which is plain old malloc.

A parent trunk is configured using the vty/config as usual, so it also
has all the endpoints, but those are just not being used by anything,
all of the structs just exist to allow parsing and configuring, but the
updates are then also sent to the trunk threads. It contains the
thread_info array which has one per_thread_info struct entry for every
trunkthread of this trunk.

Communication betwen the main threads and the trunk thread(s) work by
sending messages through
- the mgcp msg queue for mgcp commands, which the thread then ansers by
writing to the socket, the queue back to the main thread is currently
unused.
- the cfg/vty command queue for vty commands and vty settings, that are
parsed and filtered by the threads own functions, the return queue to
the main thread is only being used to block it during vty show commands
that print in threads.

MGCP message handling is therefore split between a quick parsing in the
main thread to determine which endpoint (-> thread) should handle the
command and wrapping the command in a interthread queue buffer before
submitting it to one (or multiple) threads, and the usual processing
within the sub-trunk threads.

Multithreading is by default disabled unless "number threads" in the
config file exists.

Multithreading is disabled for e1 trunks due to the complexity of the
code and a lack of test coverage that reliably proves the absence of
threading related issues, so e1 is still being handled by the main
thread. Multithreading is also disabled for all trunks if osmux is
configured, also due to complexity and the fact that one osmux trunk is
limited to 256 calls due to the CID anyway, which can be handled within
one (main) thread.

That being said the code is ready to enable threads for all of that,
it's just not possible to prove that it will not implode at some point..

Change-Id: I31be8253600c8af0a43c967d0d128f5ba7b16260
---
M include/osmocom/mgcp/Makefile.am
M include/osmocom/mgcp/mgcp.h
M include/osmocom/mgcp/mgcp_endp.h
A include/osmocom/mgcp/mgcp_threads.h
M include/osmocom/mgcp/mgcp_trunk.h
M include/osmocom/mgcp/osmux.h
M src/libosmo-mgcp/Makefile.am
M src/libosmo-mgcp/mgcp_endp.c
M src/libosmo-mgcp/mgcp_osmux.c
M src/libosmo-mgcp/mgcp_protocol.c
M src/libosmo-mgcp/mgcp_sdp.c
M src/libosmo-mgcp/mgcp_stat.c
A src/libosmo-mgcp/mgcp_threads.c
A src/libosmo-mgcp/mgcp_threads_vty.c
M src/libosmo-mgcp/mgcp_trunk.c
M src/libosmo-mgcp/mgcp_vty.c
M src/osmo-mgw/mgw_main.c
M tests/mgcp/mgcp_test.c
18 files changed, 1,311 insertions(+), 437 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-mgw refs/changes/32/25432/1

diff --git a/include/osmocom/mgcp/Makefile.am b/include/osmocom/mgcp/Makefile.am
index b94cdcd..9004cc8 100644
--- a/include/osmocom/mgcp/Makefile.am
+++ b/include/osmocom/mgcp/Makefile.am
@@ -13,4 +13,6 @@
 	mgcp_e1.h \
 	mgcp_network.h \
 	mgcp_protocol.h \
+	mgcp_threads.h \
+	mgcp_threads_queue.h \
 	$(NULL)
diff --git a/include/osmocom/mgcp/mgcp.h b/include/osmocom/mgcp/mgcp.h
index f1e6460..fffff96 100644
--- a/include/osmocom/mgcp/mgcp.h
+++ b/include/osmocom/mgcp/mgcp.h
@@ -151,29 +151,33 @@
 	mgcp_rqnt rqnt_cb;
 	void *data;
 
+	unsigned int num_threads_for_virttrunk;
 	/* list holding the trunks */
 	struct llist_head trunks;
 
 	enum mgcp_role role;
 
-	/* osmux translator: 0 means disabled, 1 means enabled */
-	int osmux;
-	/* addr to bind the server to */
-	char osmux_addr[INET6_ADDRSTRLEN];
-	/* The BSC-NAT may ask for enabling osmux on demand. This tells us if
-	 * the osmux socket is already initialized.
-	 */
-	int osmux_init;
-	/* osmux batch factor: from 1 to 4 maximum */
-	int osmux_batch;
-	/* osmux batch size (in bytes) */
-	int osmux_batch_size;
-	/* osmux port */
-	uint16_t osmux_port;
-	/* Pad circuit with dummy messages until we see the first voice
-	 * message.
-	 */
-	uint16_t osmux_dummy;
+	struct global_osmux_options_t {
+		/* osmux translator: 0 means disabled, 1 means enabled */
+		int osmux;
+		/* addr to bind the server to */
+		char osmux_addr[INET6_ADDRSTRLEN];
+		/* The BSC-NAT may ask for enabling osmux on demand. This tells us if
+		* the osmux socket is already initialized.
+		*/
+		int osmux_init;
+		/* osmux batch factor: from 1 to 4 maximum */
+		int osmux_batch;
+		/* osmux batch size (in bytes) */
+		int osmux_batch_size;
+		/* osmux port */
+		uint16_t osmux_port;
+		/* Pad circuit with dummy messages until we see the first voice
+		* message.
+		*/
+		uint16_t osmux_dummy;
+	} global_osmux_options;
+
 	/* domain name of the media gateway */
 	char domain[255+1];
 
@@ -198,7 +202,11 @@
 /*
  * format helper functions
  */
-struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg);
+struct to_trunkthread_mgcp_msg;
+struct per_thread_info;
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct to_trunkthread_mgcp_msg* w);
+struct msgb *mgcp_submit_message_to_trunkthread(struct mgcp_config *cfg, struct to_trunkthread_mgcp_msg* w);
+struct msgb* thread_handle_mgcp_message(struct to_trunkthread_mgcp_msg* w, struct per_thread_info *thread_info);
 
 
 int mgcp_send_reset_ep(struct mgcp_endpoint *endp);
diff --git a/include/osmocom/mgcp/mgcp_endp.h b/include/osmocom/mgcp/mgcp_endp.h
index 91c4551..47592ae 100644
--- a/include/osmocom/mgcp/mgcp_endp.h
+++ b/include/osmocom/mgcp/mgcp_endp.h
@@ -136,6 +136,7 @@
 bool mgcp_endp_avail(struct mgcp_endpoint *endp);
 void mgcp_endp_add_conn(struct mgcp_endpoint *endp, struct mgcp_conn *conn);
 void mgcp_endp_remove_conn(struct mgcp_endpoint *endp, struct mgcp_conn *conn);
+void chop_epname_prefix(char *epname, const struct mgcp_trunk *trunk);
 void mgcp_endp_strip_name(char *epname_stripped, const char *epname,
 			 const struct mgcp_trunk *trunk);
 struct mgcp_endpoint *mgcp_endp_find_specific(const char *epname,
diff --git a/include/osmocom/mgcp/mgcp_threads.h b/include/osmocom/mgcp/mgcp_threads.h
new file mode 100644
index 0000000..2ee44d7
--- /dev/null
+++ b/include/osmocom/mgcp/mgcp_threads.h
@@ -0,0 +1,127 @@
+#pragma once
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild
+ *
+ * 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 <assert.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include <osmocom/mgcp/mgcp.h>
+#include <osmocom/mgcp/mgcp_protocol.h>
+#include <osmocom/mgcp/mgcp_msg.h>
+#include <osmocom/mgcp/mgcp_trunk.h>
+#include <osmocom/mgcp/mgcp_threads_queue.h>
+
+void split_trunks_into_threads(struct mgcp_config *cfg);
+int get_trunk_thread_for_ep_name(const char *epname, struct mgcp_trunk *thread_parent_trunk);
+void send_async_vty_trunk_update_msg(struct mgcp_trunk *t);
+void send_async_vty_cfg_update_msg(struct mgcp_config *c);
+void thread_dispatch_ep_dump_msg(struct mgcp_trunk *trunk, int threadnum, struct vty *vty, const char *epname,
+				 bool show_stats, bool active_only);
+enum trunkthread_cfg_msg_t;
+struct to_trunkthread_cfg_msg;
+void thread_dispatch_ep_msg(struct mgcp_trunk *master_trunk, int threadnum, struct vty *v, enum trunkthread_cfg_msg_t,
+			    struct to_trunkthread_cfg_msg *m);
+
+/* Request data passed to the request handler */
+struct mgcp_request_data {
+	/* request name (e.g. "MDCX") */
+	char name[4 + 1];
+
+	/* parsing results from the MGCP header (trans id, endpoint name ...) */
+	struct mgcp_parse_data *pdatap;
+
+	/* pointer to endpoint resource (may be NULL for wildcarded requests) */
+	struct mgcp_endpoint *endp;
+
+	/* pointer to trunk resource */
+	struct mgcp_trunk *trunk;
+
+	/* set to true when the request has been classified as wildcarded */
+	bool wildcarded;
+
+	/* contains cause code in case of problems during endp/trunk resolution */
+	int mgcp_cause;
+};
+
+enum cfg_content_t { HAS_TRUNK = 1 << 0, HAS_CFG = 1 << 1 };
+enum trunkthread_cfg_msg_t {
+	IS_INVALID = 0 << 0,
+	IS_CFGMSG = 1 << 0,
+	IS_VTYMSG = 1 << 1,
+	IS_FREEEPMSG = 1 << 2,
+	IS_LOOPMSG = 1 << 3,
+	IS_TAPMSG = 1 << 4
+};
+struct to_trunkthread_cfg_msg {
+	enum trunkthread_cfg_msg_t type;
+	struct vty *vty;
+	union {
+		struct cfgmsg {
+			enum cfg_content_t content;
+			struct mgcp_config c;
+			struct mgcp_trunk t;
+		} c;
+		struct vtymsg {
+			char epname[MGCP_ENDPOINT_MAXLEN]; /* may be empty */
+			bool show_stats;
+			bool active_only;
+		} v;
+		struct freepmsg {
+			char endp[MGCP_ENDPOINT_MAXLEN * 4];
+		} f;
+		struct tapmsg {
+			char epname[MGCP_ENDPOINT_MAXLEN];
+			char connid[MGCP_CONN_ID_MAXLEN];
+			bool direction_is_in;
+			char addr[INET6_ADDRSTRLEN];
+			unsigned short destport;
+		} t;
+		struct loopmsg {
+			char epname[MGCP_ENDPOINT_MAXLEN];
+			bool enable_loop;
+		} l;
+	};
+};
+struct to_trunkthread_mgcp_msg {
+	struct y {
+		ssize_t msglen;
+		bool successfully_parsed;
+		struct osmo_sockaddr addr;
+		struct mgcp_parse_data pdata;
+		struct mgcp_request_data rq;
+	} x;
+	char msg[4096 - sizeof(struct y)];
+};
+static_assert(sizeof(struct to_trunkthread_mgcp_msg) == 4096, "interthread struct size mismatch!");
+
+struct per_thread_info {
+	struct mgcp_trunk *parent_trunk; /* pointer to parent trunk */
+	struct mgcp_trunk *this_trunk; /* talloced, used as ctx */
+	struct mgcp_config *this_cfg; /* talloced, used as ctx */
+	struct qchan chan_mgcp;
+	struct qchan chan_cfg;
+	int tid; /* thread number handling this subtrunk */
+	pthread_t thr;
+	struct osmo_fd master_evfd; /* registered by dispatch thread */
+	atomic_uint eps_free;
+};
+
+void thread_push_msg(struct mgcp_trunk *trunk, unsigned int threadnum, void *elem);
diff --git a/include/osmocom/mgcp/mgcp_trunk.h b/include/osmocom/mgcp/mgcp_trunk.h
index 3f14f97..db98ca7 100644
--- a/include/osmocom/mgcp/mgcp_trunk.h
+++ b/include/osmocom/mgcp/mgcp_trunk.h
@@ -22,6 +22,16 @@
 struct mgcp_trunk {
 	struct llist_head entry;
 
+	/*	master trunk struct: array of num_threads trunk info structs containing per thread data
+		slave thread trunk struct: single entry pointing to this threads info!
+	*/
+	union {
+		struct per_thread_info *thread_info;
+		struct per_thread_info *this_thread_info;
+	};
+	int num_threads;
+	bool use_threads; /* false for e1 */
+
 	struct mgcp_config *cfg;
 
 	unsigned int trunk_nr;
@@ -52,6 +62,7 @@
 	int rtp_accept_all;
 
 	unsigned int number_endpoints;
+	unsigned int number_endpoints_offset;
 	struct mgcp_endpoint **endpoints;
 
 	/* rate counters and stat items to measure the trunks overall performance and health */
diff --git a/include/osmocom/mgcp/osmux.h b/include/osmocom/mgcp/osmux.h
index 99b44d1..2230a71 100644
--- a/include/osmocom/mgcp/osmux.h
+++ b/include/osmocom/mgcp/osmux.h
@@ -12,6 +12,10 @@
 	OSMUX_ROLE_BSC_NAT,
 };
 
+struct global_osmux_options_t;
+void osmux_set_global_opt(struct global_osmux_options_t *ptr);
+struct global_osmux_options_t * osmux_get_global_opt();
+
 int osmux_init(int role, struct mgcp_config *cfg);
 int osmux_enable_conn(struct mgcp_endpoint *endp, struct mgcp_conn_rtp *conn,
 		      struct osmo_sockaddr *addr, uint16_t port);
diff --git a/src/libosmo-mgcp/Makefile.am b/src/libosmo-mgcp/Makefile.am
index 91b2bf6..e791a94 100644
--- a/src/libosmo-mgcp/Makefile.am
+++ b/src/libosmo-mgcp/Makefile.am
@@ -48,4 +48,7 @@
 	mgcp_ctrl.c \
 	mgcp_ratectr.c \
 	mgcp_e1.c \
+	mgcp_threads.c \
+	mgcp_threads_queue.c \
+	mgcp_threads_vty.c \
 	$(NULL)
diff --git a/src/libosmo-mgcp/mgcp_endp.c b/src/libosmo-mgcp/mgcp_endp.c
index 9846dfe..49e7624 100644
--- a/src/libosmo-mgcp/mgcp_endp.c
+++ b/src/libosmo-mgcp/mgcp_endp.c
@@ -26,6 +26,7 @@
 #include <osmocom/mgcp/mgcp_conn.h>
 #include <osmocom/mgcp/mgcp_endp.h>
 #include <osmocom/mgcp/mgcp_trunk.h>
+#include <osmocom/mgcp/mgcp_threads.h>
 
 #include <osmocom/abis/e1_input.h>
 #include <osmocom/mgcp/mgcp_e1.h>
@@ -124,9 +125,11 @@
 
 	/* We must only decrement the stat item when the endpoint as actually
 	 * claimed. An endpoint is claimed when a call-id is set */
-	if (endp->callid)
+	if (endp->callid) {
 		osmo_stat_item_dec(osmo_stat_item_group_get_item(endp->trunk->stats.common,
 								 TRUNK_STAT_ENDPOINTS_USED), 1);
+		endp->trunk->this_thread_info->eps_free++;
+	}
 
 	/* Reset endpoint parameters and states */
 	talloc_free(endp->callid);
@@ -144,7 +147,7 @@
  * "ds/e1-") and write the epname without the prefix back to the memory
  * pointed at by epname. (per trunk the prefix is the same for all endpoints,
  * so no ambiguity is introduced) */
-static void chop_epname_prefix(char *epname, const struct mgcp_trunk *trunk)
+void chop_epname_prefix(char *epname, const struct mgcp_trunk *trunk)
 {
 	size_t prefix_len;
 	switch (trunk->trunk_type) {
@@ -606,6 +609,7 @@
 	OSMO_ASSERT(endp->callid);
 	osmo_stat_item_inc(osmo_stat_item_group_get_item(endp->trunk->stats.common,
 							 TRUNK_STAT_ENDPOINTS_USED), 1);
+	endp->trunk->this_thread_info->eps_free--;
 
 	/* Allocate resources */
 	switch (endp->trunk->trunk_type) {
diff --git a/src/libosmo-mgcp/mgcp_osmux.c b/src/libosmo-mgcp/mgcp_osmux.c
index de19042..03aa71a 100644
--- a/src/libosmo-mgcp/mgcp_osmux.c
+++ b/src/libosmo-mgcp/mgcp_osmux.c
@@ -44,6 +44,16 @@
 
 static void *osmux;
 
+static struct global_osmux_options_t *g_osmux;
+
+void osmux_set_global_opt(struct global_osmux_options_t *ptr) {
+	g_osmux = ptr;
+}
+
+struct global_osmux_options_t * osmux_get_global_opt() {
+	return g_osmux;
+}
+
 /* Deliver OSMUX batch to the remote end */
 static void osmux_deliver_cb(struct msgb *batch_msg, void *data)
 {
@@ -135,10 +145,10 @@
 	/* sequence number to start OSMUX message from */
 	h->in->osmux_seq = 0;
 
-	h->in->batch_factor = cfg->osmux_batch;
+	h->in->batch_factor = g_osmux->osmux_batch;
 
 	/* If batch size is zero, the library defaults to 1470 bytes. */
-	h->in->batch_size = cfg->osmux_batch_size;
+	h->in->batch_size = g_osmux->osmux_batch_size;
 	h->in->deliver = osmux_deliver_cb;
 	osmux_xfrm_input_init(h->in);
 	h->in->data = h;
@@ -368,7 +378,7 @@
 	if (!msg)
 		return -1;
 
-	if (!cfg->osmux) {
+	if (!g_osmux->osmux) {
 		LOGP(DLMGCP, LOGL_ERROR,
 		     "bsc-nat wants to use Osmux but bsc did not request it\n");
 		goto out;
@@ -418,11 +428,11 @@
 
 	osmo_fd_setup(&osmux_fd, -1, OSMO_FD_READ, osmux_read_fd_cb, cfg, 0);
 
-	ret = mgcp_create_bind(cfg->osmux_addr, &osmux_fd, cfg->osmux_port,
+	ret = mgcp_create_bind(g_osmux->osmux_addr, &osmux_fd, g_osmux->osmux_port,
 				cfg->endp_dscp, cfg->endp_priority);
 	if (ret < 0) {
 		LOGP(DLMGCP, LOGL_ERROR, "cannot bind OSMUX socket to %s:%u\n",
-		     cfg->osmux_addr, cfg->osmux_port);
+		     g_osmux->osmux_addr, g_osmux->osmux_port);
 		return ret;
 	}
 
@@ -432,7 +442,7 @@
 		     osmo_sock_get_name2(osmux_fd.fd));
 		return ret;
 	}
-	cfg->osmux_init = 1;
+	g_osmux->osmux_init = 1;
 
 	LOGP(DLMGCP, LOGL_INFO, "OSMUX socket listening on %s\n",
 		 osmo_sock_get_name2(osmux_fd.fd));
@@ -460,7 +470,7 @@
 	 */
 	struct in6_addr addr_unset = {};
 	static const uint32_t rtp_ssrc_winlen = UINT32_MAX / (OSMUX_CID_MAX + 1);
-	uint16_t osmux_dummy = endp->trunk->cfg->osmux_dummy;
+	uint16_t osmux_dummy = g_osmux->osmux_dummy;
 
 	/* Check if osmux is enabled for the specified connection */
 	if (conn->osmux.state != OSMUX_STATE_ACTIVATING) {
diff --git a/src/libosmo-mgcp/mgcp_protocol.c b/src/libosmo-mgcp/mgcp_protocol.c
index f9ae2ec..1a39827 100644
--- a/src/libosmo-mgcp/mgcp_protocol.c
+++ b/src/libosmo-mgcp/mgcp_protocol.c
@@ -24,6 +24,7 @@
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <time.h>
 #include <limits.h>
 #include <unistd.h>
@@ -46,6 +47,7 @@
 #include <osmocom/mgcp/mgcp_sdp.h>
 #include <osmocom/mgcp/mgcp_codec.h>
 #include <osmocom/mgcp/mgcp_conn.h>
+#include <osmocom/mgcp/mgcp_threads.h>
 
 /* Contains the last successfully resolved endpoint name. This variable is used
  * for the unit-tests to verify that the endpoint was correctly resolved. */
@@ -67,27 +69,6 @@
 		LOGPTRUNK(trunk, cat, level, fmt, ## args); \
 } while (0)
 
-/* Request data passed to the request handler */
-struct mgcp_request_data {
-	/* request name (e.g. "MDCX") */
-	char name[4+1];
-
-	/* parsing results from the MGCP header (trans id, endpoint name ...) */
-	struct mgcp_parse_data *pdata;
-
-	/* pointer to endpoint resource (may be NULL for wildcarded requests) */
-	struct mgcp_endpoint *endp;
-
-	/* pointer to trunk resource */
-	struct mgcp_trunk *trunk;
-
-	/* set to true when the request has been classified as wildcarded */
-	bool wildcarded;
-
-	/* contains cause code in case of problems during endp/trunk resolution */
-	int mgcp_cause;
-};
-
 /* Request handler specification, here we specify an array with function
  * pointers to the various MGCP requests implemented below */
 struct mgcp_request {
@@ -202,7 +183,7 @@
 	return msg;
 }
 
-static struct msgb *create_resp(struct mgcp_endpoint *endp, int code,
+static struct msgb *create_resp(void* msgctx, struct mgcp_endpoint *endp, int code,
 				const char *txt, const char *msg,
 				const char *trans, const char *param,
 				const char *sdp)
@@ -210,7 +191,8 @@
 	int len;
 	struct msgb *res;
 
-	res = mgcp_msgb_alloc(endp->trunk);
+	OSMO_ASSERT(msgctx != 0);
+	res = mgcp_msgb_alloc(msgctx);
 	if (!res)
 		return NULL;
 
@@ -242,26 +224,26 @@
 	return res;
 }
 
-static struct msgb *create_ok_resp_with_param(struct mgcp_endpoint *endp,
+static struct msgb *create_ok_resp_with_param(void* msgctx, struct mgcp_endpoint *endp,
 					      int code, const char *msg,
 					      const char *trans,
 					      const char *param)
 {
-	return create_resp(endp, code, " OK", msg, trans, param, NULL);
+	return create_resp(msgctx, endp, code, " OK", msg, trans, param, NULL);
 }
 
-static struct msgb *create_ok_response(struct mgcp_endpoint *endp,
+static struct msgb *create_ok_response(void* msgctx, struct mgcp_endpoint *endp,
 				       int code, const char *msg,
 				       const char *trans)
 {
-	return create_ok_resp_with_param(endp, code, msg, trans, NULL);
+	return create_ok_resp_with_param(msgctx, endp, code, msg, trans, NULL);
 }
 
-static struct msgb *create_err_response(struct mgcp_endpoint *endp,
+static struct msgb *create_err_response(void* msgctx, struct mgcp_endpoint *endp,
 					int code, const char *msg,
 					const char *trans)
 {
-	return create_resp(endp, code, " FAIL", msg, trans, NULL, NULL);
+	return create_resp(msgctx, endp, code, " FAIL", msg, trans, NULL, NULL);
 }
 
 /* Format MGCP response string (with SDP attached) */
@@ -318,7 +300,7 @@
 	rc = mgcp_write_response_sdp(endp, conn, sdp, addr);
 	if (rc < 0)
 		goto error;
-	result = create_resp(endp, 200, " OK", msg, trans_id, NULL, (char*) sdp->data);
+	result = create_resp(endp->trunk, endp, 200, " OK", msg, trans_id, NULL, (char*) sdp->data);
 	msgb_free(sdp);
 	return result;
 error:
@@ -336,89 +318,147 @@
 		mgcp_send_dummy(endp, conn);
 }
 
-/* handle incoming messages:
- *   - this can be a command (four letters, space, transaction id)
- *   - or a response (three numbers, space, transaction id) */
-struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg)
+
+/* partially sanitize and parse incoming message
+ * !! only public for testing !! */
+struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct to_trunkthread_mgcp_msg* w)
 {
 	struct rate_ctr_group *rate_ctrs = cfg->ratectr.mgcp_general_ctr_group;
-	struct mgcp_parse_data pdata;
-	struct mgcp_request_data rq;
-	int rc, i, code, handled = 0;
-	struct msgb *resp = NULL;
-	char *data;
+	int code;
+	ssize_t rc = w->x.msglen;
 
-	debug_last_endpoint_name[0] = '\0';
+	#define pdata w->x.pdata
+	#define rq w->x.rq
 
 	/* Count all messages, even incorect ones */
 	rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_MSGS_TOTAL));
 
-	if (msgb_l2len(msg) < 4) {
-		LOGP(DLMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len);
+	if (rc < sizeof(rq.name)-1) {
+		LOGP(DLMGCP, LOGL_ERROR, "msg too short: %zd\n", rc);
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_MSG_PARSE));
-		return NULL;
+		return 0;
 	}
+	memcpy(rq.name, (const char *)&w->msg[0], sizeof(rq.name)-1);
 
-	if (mgcp_msg_terminate_nul(msg)) {
-		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_MSG_PARSE));
-		return NULL;
-	}
-
-	mgcp_disp_msg(msg->l2h, msgb_l2len(msg), "Received message");
+	mgcp_disp_msg(w->msg, rc, "Received message");
 
 	/* attempt to treat it as a response */
-	if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) {
+	if (sscanf((const char *)&w->msg[0], "%3d %*s", &code) == 1) {
 		LOGP(DLMGCP, LOGL_DEBUG, "Response: Code: %d\n", code);
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_MSG_PARSE));
-		return NULL;
+		return 0;
 	}
 
-
 	/* Parse message, extract endpoint name and transaction identifier and request name etc. */
-	memset(&pdata, 0, sizeof(pdata));
-	memset(&rq, 0, sizeof(rq));
-	pdata.cfg = cfg;
-	memcpy(rq.name, (const char *)&msg->l2h[0], sizeof(rq.name)-1);
-	msg->l3h = &msg->l2h[4];
-	data = mgcp_strline((char *)msg->l3h, &pdata.save);
-	rc = mgcp_parse_header(&pdata, data);
+	rc = mgcp_parse_header(&pdata, mgcp_strline((char *)&w->msg[4], &pdata.save));
 	if (rc < 0) {
 		LOGP(DLMGCP, LOGL_ERROR, "%s: failed to parse MCGP message\n", rq.name);
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_MSG_PARSE));
-		return create_err_response(NULL, -rc, rq.name, "000000");
+		return create_err_response(cfg, NULL, -rc, rq.name, "000000");
 	}
 
 	/* Locate endpoint and trunk, if no endpoint can be located try at least to identify the trunk. */
-	rq.pdata = &pdata;
+	rq.pdatap = &pdata;
 	rq.wildcarded = mgcp_endp_is_wildcarded(pdata.epname);
-	rq.endp = mgcp_endp_by_name(&rc, pdata.epname, pdata.cfg);
-	rq.mgcp_cause = rc;
-	if (!rq.endp) {
-		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_NO_ENDPOINT));
-		if (rq.wildcarded) {
-			/* If we are unable to find the endpoint we still may be able to identify the trunk. Some
-			 * request handlers will still be able to perform a useful action if the request refers to
-			 * the whole trunk (wildcarded request). */
-			LOGP(DLMGCP, LOGL_NOTICE,
-			     "%s: cannot find endpoint \"%s\", cause=%d -- trying to identify trunk...\n", rq.name,
-			     pdata.epname, -rq.mgcp_cause);
-			rq.trunk = mgcp_trunk_by_name(pdata.cfg, pdata.epname);
-			if (!rq.trunk) {
-				LOGP(DLMGCP, LOGL_ERROR, "%s: failed to identify trunk for endpoint \"%s\" -- abort\n",
-				     rq.name, pdata.epname);
-				return create_err_response(NULL, -rq.mgcp_cause, rq.name, pdata.trans);
+	rq.trunk = mgcp_trunk_by_name(pdata.cfg, pdata.epname);
+
+	if (!rq.trunk) {
+		rq.mgcp_cause = -500; /* if we can't even find the trunk we also found no EP */
+		LOGP(DLMGCP, LOGL_ERROR, "%s: failed to identify trunk for endpoint \"%s\" -- abort\n",
+				rq.name, pdata.epname);
+		return create_err_response(cfg, NULL, -rq.mgcp_cause, rq.name, pdata.trans);
+	}
+
+	w->x.successfully_parsed = true;
+	return NULL;
+}
+#undef pdata
+#undef rq
+
+/* submit partially parsed message to thunkthreads */
+struct msgb *mgcp_submit_message_to_trunkthread(struct mgcp_config *cfg, struct to_trunkthread_mgcp_msg* w)
+{
+	struct msgb * retmsg;
+	int which_thread = -1;
+
+
+	#define pdata w->x.pdata
+	#define rq w->x.rq
+
+	retmsg = mgcp_handle_message(cfg, w);
+	if (retmsg || !w->x.successfully_parsed)
+		return retmsg;
+
+	if (!rq.trunk->use_threads)
+		return thread_handle_mgcp_message(w, rq.trunk->thread_info);
+
+	/* might not be wildcarded -> look up the thread according to ep name */
+	which_thread = get_trunk_thread_for_ep_name(pdata.epname, rq.trunk);
+
+	/* update pointers to offsets for interthread q */
+	pdata.trans = (char *)(pdata.trans - &w->msg[0]);
+	pdata.epname = (char *)(pdata.epname - &w->msg[0]);
+	if (pdata.save >= &w->msg[0]) /* might be 0 in case of header-only messages, so be careful */
+		pdata.save = (char *)(pdata.save - &w->msg[0]);
+
+	if (rq.wildcarded) {
+		if (strcmp(rq.name, "CRCX") == 0){ /* crcx -> pick ONE thread that has free endpoints */
+			for (int i = 0; i < rq.trunk->num_threads; i++) {
+				if (rq.trunk->thread_info[i].eps_free) {
+					thread_push_msg(rq.trunk, i, w);
+					return NULL;
+				}
 			}
-		} else {
+			/* just bother t0 to generate a nice error response if we don't have any eps left */
+			thread_push_msg(rq.trunk, 0, w);
+			return NULL;
+		} else { /* wildcarded, and not crcx -> to all threads of a trunk */
+			for (int i = 0; i < rq.trunk->num_threads; i++)
+				thread_push_msg(rq.trunk, i, w);
+			return NULL;
+		}
+	}
+
+	/* we now know the trunk and the ep, so dispatch it */
+	thread_push_msg(rq.trunk, which_thread, w);
+
+
+	return NULL;
+}
+#undef pdata
+#undef rq
+
+struct msgb* thread_handle_mgcp_message(struct to_trunkthread_mgcp_msg* w, struct per_thread_info *thread_info) {
+	int rc = -500, handled = 0;
+	struct msgb *resp = NULL;
+	struct rate_ctr_group *rate_ctrs = w->x.rq.trunk->cfg->ratectr.mgcp_general_ctr_group;
+	#define pdata w->x.pdata
+	#define rq w->x.rq
+
+	/* finds us a free ep in case wildcarded / crcx
+		OR finds us a proper ep passed by name */
+	if (!rq.wildcarded || !strcmp(rq.name, "CRCX"))
+		rq.endp = mgcp_endp_by_name_trunk(&rc, pdata.epname, rq.trunk);
+	rq.mgcp_cause = rc;
+
+	if (rq.endp) {
+		debug_last_endpoint_name[0] = 0;
+		osmo_strlcpy(debug_last_endpoint_name, rq.endp->name, sizeof(debug_last_endpoint_name));
+	}
+
+	if (!rq.endp) {
+		if (rq.wildcarded) {
+			/* we know this is the trunk that handles this wildcarded message */
+		} else  {
+			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_FAIL_NO_ENDPOINT));
 			/* If the endpoint name suggests that the request refers to a specific endpoint, then the
-			 * request cannot be handled and we must stop early. */
+				* request cannot be handled and we must stop early. */
 			LOGP(DLMGCP, LOGL_NOTICE,
-			     "%s: cannot find endpoint \"%s\", cause=%d -- abort\n", rq.name,
-			     pdata.epname, -rq.mgcp_cause);
-			return create_err_response(NULL, -rq.mgcp_cause, rq.name, pdata.trans);
+					"%s: cannot find endpoint \"%s\", cause=%d -- abort\n", rq.name,
+					pdata.epname, -rq.mgcp_cause);
+			return create_err_response(rq.trunk, NULL, -rq.mgcp_cause, rq.name, pdata.trans);
 		}
 	} else {
-		osmo_strlcpy(debug_last_endpoint_name, rq.endp->name, sizeof(debug_last_endpoint_name));
-		rq.trunk = rq.endp->trunk;
 		rq.mgcp_cause = 0;
 
 		/* Check if we have to retransmit a response from a previous transaction */
@@ -429,16 +469,19 @@
 	}
 
 	/* Find an appropriate handler for the current request and execute it */
-	for (i = 0; i < ARRAY_SIZE(mgcp_requests); i++) {
+	for (int i = 0; i < ARRAY_SIZE(mgcp_requests); i++) {
 		if (strcmp(mgcp_requests[i].name, rq.name) == 0) {
+
+			#if 0
 			/* Check if the request requires and endpoint, if yes, check if we have it, otherwise don't
 			 * execute the request handler. */
 			if (mgcp_requests[i].require_endp && !rq.endp) {
 				LOGP(DLMGCP, LOGL_ERROR,
 				     "%s: the request handler \"%s\" requires an endpoint resource for \"%s\", which is not available -- abort\n",
 				     rq.name, mgcp_requests[i].debug_name, pdata.epname);
-				return create_err_response(NULL, -rq.mgcp_cause, rq.name, pdata.trans);
+				return create_err_response(rq.trunk, NULL, -rq.mgcp_cause, rq.name, pdata.trans);
 			}
+			#endif
 
 			/* Execute request handler */
 			if (rq.endp)
@@ -455,22 +498,40 @@
 		}
 	}
 
-	/* Check if the MGCP request was handled and increment rate counters accordingly. */
-	if (handled) {
-		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_MSGS_HANDLED));
-	} else {
-		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_MSGS_UNHANDLED));
-		LOGP(DLMGCP, LOGL_ERROR, "MSG with type: '%.4s' not handled\n", &msg->l2h[0]);
+	/* ensure we do not send back duplicate messages due to wildcarded requests */
+	if (thread_info->tid != 0 && rq.wildcarded && strcmp(rq.name, "CRCX")) {
+		if (resp)
+			msgb_free(resp);
+		resp = 0;
+	}
+
+	/* Check if the MGCP request was handled and increment rate counters accordingly - but don't count this multiple times */
+	if (!rq.wildcarded || (rq.wildcarded && !strcmp(rq.name, "CRCX"))) {
+		if (handled) {
+			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_MSGS_HANDLED));
+		} else {
+			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_GENERAL_RX_MSGS_UNHANDLED));
+			LOGP(DLMGCP, LOGL_ERROR, "MSG with type: '%.4s' not handled\n", rq.name);
+		}
 	}
 
 	return resp;
 }
+#undef pdata
+#undef rq
 
 /* AUEP command handler, processes the received command */
 static struct msgb *handle_audit_endpoint(struct mgcp_request_data *rq)
 {
 	LOGPENDP(rq->endp, DLMGCP, LOGL_NOTICE, "AUEP: auditing endpoint ...\n");
-	return create_ok_response(rq->endp, 200, "AUEP", rq->pdata->trans);
+
+	if (!rq->endp || !mgcp_endp_avail(rq->endp)) {
+		LOGPENDP(rq->endp, DLMGCP, LOGL_ERROR,
+			 "AUEP: selected endpoint not available!\n");
+		return create_err_response(rq->trunk, NULL, 501, "AUEP", rq->pdatap->trans);
+	}
+
+	return create_ok_response(rq->trunk, rq->endp, 200, "AUEP", rq->pdatap->trans);
 }
 
 /* Try to find a free port by attempting to bind on it. Also handle the
@@ -750,7 +811,7 @@
  */
 static int mgcp_osmux_setup(struct mgcp_endpoint *endp, const char *line)
 {
-	if (!endp->trunk->cfg->osmux_init) {
+	if (! osmux_get_global_opt()->osmux_init) {
 		if (osmux_init(OSMUX_ROLE_BSC, endp->trunk->cfg) < 0) {
 			LOGPENDP(endp, DLMGCP, LOGL_ERROR, "Cannot init OSMUX\n");
 			return -3;
@@ -779,7 +840,7 @@
 		/* If we have SDP, we ignore the local connection options and
 		 * use only the SDP information. */
 		mgcp_codec_reset_all(conn);
-		rc = mgcp_parse_sdp_data(endp, conn, rq->pdata);
+		rc = mgcp_parse_sdp_data(endp, conn, rq->pdatap);
 		if (rc != 0) {
 			LOGPCONN(conn->conn, DLMGCP,  LOGL_ERROR,
 				 "%s: sdp not parseable\n", cmd);
@@ -847,7 +908,7 @@
 /* CRCX command handler, processes the received command */
 static struct msgb *handle_create_con(struct mgcp_request_data *rq)
 {
-	struct mgcp_parse_data *pdata = rq->pdata;
+	struct mgcp_parse_data *pdata = rq->pdatap;
 	struct mgcp_trunk *trunk = rq->trunk;
 	struct mgcp_endpoint *endp = rq->endp;
 	struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_crcx_ctr_group;
@@ -864,11 +925,19 @@
 
 	LOGPENDP(endp, DLMGCP, LOGL_NOTICE, "CRCX: creating new connection ...\n");
 
+	/* we must have a free ep */
+	if (!endp) {
+		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_AVAIL));
+		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
+			 "CRCX: no free endpoints available!\n");
+		return create_err_response(rq->trunk, NULL, 403, "CRCX", pdata->trans);
+	}
+
 	if (!mgcp_endp_avail(endp)) {
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_AVAIL));
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "CRCX: selected endpoint not available!\n");
-		return create_err_response(NULL, 501, "CRCX", pdata->trans);
+		return create_err_response(rq->trunk, NULL, 501, "CRCX", pdata->trans);
 	}
 
 	/* parse CallID C: and LocalParameters L: */
@@ -888,7 +957,7 @@
 			 * together with a CRCX, the MGW will assign the
 			 * connection identifier by itself on CRCX */
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_BAD_ACTION));
-			return create_err_response(NULL, 523, "CRCX", pdata->trans);
+			return create_err_response(rq->trunk, NULL, 523, "CRCX", pdata->trans);
 			break;
 		case 'M':
 			mode = (const char *)line + 3;
@@ -896,7 +965,7 @@
 		case 'X':
 			if (strncasecmp("Osmux: ", line + 2, strlen("Osmux: ")) == 0) {
 				/* If osmux is disabled, just skip setting it up */
-				if (!rq->endp->trunk->cfg->osmux)
+				if (! trunk->cfg->global_osmux_options.osmux)
 					break;
 				osmux_cid = mgcp_osmux_setup(endp, line);
 				break;
@@ -914,7 +983,7 @@
 			LOGPENDP(endp, DLMGCP, LOGL_NOTICE,
 				 "CRCX: unhandled option: '%c'/%d\n", *line, *line);
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_UNHANDLED_PARAM));
-			return create_err_response(NULL, 539, "CRCX", pdata->trans);
+			return create_err_response(rq->trunk, NULL, 539, "CRCX", pdata->trans);
 			break;
 		}
 	}
@@ -925,14 +994,14 @@
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "CRCX: insufficient parameters, missing callid\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_MISSING_CALLID));
-		return create_err_response(endp, 516, "CRCX", pdata->trans);
+		return create_err_response(endp, endp, 516, "CRCX", pdata->trans);
 	}
 
 	if (!mode) {
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "CRCX: insufficient parameters, missing mode\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_INVALID_MODE));
-		return create_err_response(endp, 517, "CRCX", pdata->trans);
+		return create_err_response(endp, endp, 517, "CRCX", pdata->trans);
 	}
 
 	/* Check if we are able to accept the creation of another connection */
@@ -949,7 +1018,7 @@
 			/* There is no more room for a connection, leave
 			 * everything as it is and return with an error */
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_LIMIT_EXCEEDED));
-			return create_err_response(endp, 540, "CRCX", pdata->trans);
+			return create_err_response(endp, endp, 540, "CRCX", pdata->trans);
 		}
 	}
 
@@ -967,7 +1036,7 @@
 			/* This is not our call, leave everything as it is and
 			 * return with an error. */
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_UNKNOWN_CALLID));
-			return create_err_response(endp, 400, "CRCX", pdata->trans);
+			return create_err_response(endp, endp, 400, "CRCX", pdata->trans);
 		}
 	}
 
@@ -978,7 +1047,7 @@
 		rc = mgcp_endp_claim(endp, callid);
 		if (rc != 0) {
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_CLAIM));
-			return create_err_response(endp, 502, "CRCX", pdata->trans);
+			return create_err_response(endp, endp, 502, "CRCX", pdata->trans);
 		}
 	}
 
@@ -1010,7 +1079,7 @@
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_NO_OSMUX));
 			goto error2;
 		}
-	} else if (endp->trunk->cfg->osmux == OSMUX_USAGE_ONLY) {
+	} else if (osmux_get_global_opt()->osmux == OSMUX_USAGE_ONLY) {
 		LOGPCONN(_conn, DLMGCP, LOGL_ERROR,
 			 "CRCX: osmux only and no osmux offered\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_CRCX_FAIL_NO_OSMUX));
@@ -1097,13 +1166,13 @@
 	mgcp_endp_release(endp);
 	LOGPENDP(endp, DLMGCP, LOGL_NOTICE,
 		 "CRCX: unable to create connection\n");
-	return create_err_response(endp, error_code, "CRCX", pdata->trans);
+	return create_err_response(endp, endp, error_code, "CRCX", pdata->trans);
 }
 
 /* MDCX command handler, processes the received command */
 static struct msgb *handle_modify_con(struct mgcp_request_data *rq)
 {
-	struct mgcp_parse_data *pdata = rq->pdata;
+	struct mgcp_parse_data *pdata = rq->pdatap;
 	struct mgcp_trunk *trunk = rq->trunk;
 	struct mgcp_endpoint *endp = rq->endp;
 	struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_mdcx_ctr_group;
@@ -1121,26 +1190,26 @@
 
 	LOGPENDP(endp, DLMGCP, LOGL_NOTICE, "MDCX: modifying existing connection ...\n");
 
-	if (!mgcp_endp_avail(endp)) {
-		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_AVAIL));
-		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
-			 "MDCX: selected endpoint not available!\n");
-		return create_err_response(NULL, 501, "MDCX", pdata->trans);
-	}
-
 	/* Prohibit wildcarded requests */
 	if (rq->wildcarded) {
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "MDCX: wildcarded endpoint names not supported.\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_WILDCARD));
-		return create_err_response(endp, 507, "MDCX", pdata->trans);
+		return create_err_response(rq->trunk, endp, 507, "MDCX", pdata->trans);
+	}
+
+	if (!endp || !mgcp_endp_avail(endp)) {
+		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_AVAIL));
+		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
+			 "MDCX: selected endpoint not available!\n");
+		return create_err_response(rq->trunk, NULL, 501, "MDCX", pdata->trans);
 	}
 
 	if (llist_count(&endp->conns) <= 0) {
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "MDCX: endpoint is not holding a connection.\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_NO_CONN));
-		return create_err_response(endp, 400, "MDCX", pdata->trans);
+		return create_err_response(endp, endp, 400, "MDCX", pdata->trans);
 	}
 
 	for_each_line(line, pdata->save) {
@@ -1174,7 +1243,7 @@
 		case 'X':
 			if (strncasecmp("Osmux: ", line + 2, strlen("Osmux: ")) == 0) {
 				/* If osmux is disabled, just skip setting it up */
-				if (!endp->trunk->cfg->osmux)
+				if (!osmux_get_global_opt()->osmux)
 					break;
 				osmux_cid = mgcp_osmux_setup(endp, line);
 				break;
@@ -1190,7 +1259,7 @@
 				 "MDCX: Unhandled MGCP option: '%c'/%d\n",
 				 line[0], line[0]);
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_UNHANDLED_PARAM));
-			return create_err_response(NULL, 539, "MDCX", pdata->trans);
+			return create_err_response(rq->trunk, NULL, 539, "MDCX", pdata->trans);
 			break;
 		}
 	}
@@ -1200,13 +1269,13 @@
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "MDCX: insufficient parameters, missing ci (connectionIdentifier)\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_NO_CONNID));
-		return create_err_response(endp, 515, "MDCX", pdata->trans);
+		return create_err_response(endp, endp, 515, "MDCX", pdata->trans);
 	}
 
 	conn = mgcp_conn_get_rtp(endp, conn_id);
 	if (!conn) {
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_MDCX_FAIL_CONN_NOT_FOUND));
-		return create_err_response(endp, 400, "MDCX", pdata->trans);
+		return create_err_response(endp, endp, 400, "MDCX", pdata->trans);
 	}
 
 	mgcp_conn_watchdog_kick(conn->conn);
@@ -1313,7 +1382,7 @@
 	mgcp_endp_update(endp);
 	return create_response_with_sdp(endp, conn, "MDCX", pdata->trans, false, false);
 error3:
-	return create_err_response(endp, error_code, "MDCX", pdata->trans);
+	return create_err_response(endp, endp, error_code, "MDCX", pdata->trans);
 
 out_silent:
 	LOGPENDP(endp, DLMGCP, LOGL_DEBUG, "MDCX: silent exit\n");
@@ -1323,7 +1392,7 @@
 /* DLCX command handler, processes the received command */
 static struct msgb *handle_delete_con(struct mgcp_request_data *rq)
 {
-	struct mgcp_parse_data *pdata = rq->pdata;
+	struct mgcp_parse_data *pdata = rq->pdatap;
 	struct mgcp_trunk *trunk = rq->trunk;
 	struct mgcp_endpoint *endp = rq->endp;
 	struct rate_ctr_group *rate_ctrs = trunk->ratectr.mgcp_dlcx_ctr_group;
@@ -1344,14 +1413,27 @@
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_AVAIL));
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "DLCX: selected endpoint not available!\n");
-		return create_err_response(NULL, 501, "DLCX", pdata->trans);
+		return create_err_response(rq->trunk, NULL, 501, "DLCX", pdata->trans);
 	}
 
 	if (endp && !rq->wildcarded && llist_empty(&endp->conns)) {
 		LOGPENDP(endp, DLMGCP, LOGL_ERROR,
 			 "DLCX: endpoint is not holding a connection.\n");
 		rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_NO_CONN));
-		return create_err_response(endp, 515, "DLCX", pdata->trans);
+		return create_err_response(endp, endp, 515, "DLCX", pdata->trans);
+	}
+
+	/* Handle wildcarded DLCX that refers to the whole trunk. This means
+	 * that we walk over all endpoints on the trunk in order to drop all
+	 * connections on the trunk. (see also RFC3435 Annex F.7) */
+	if (rq->wildcarded) {
+		int num_conns = 0;
+		for (i = 0; i < trunk->number_endpoints; i++) {
+			num_conns += llist_count(&trunk->endpoints[i]->conns);
+			mgcp_endp_release(trunk->endpoints[i]);
+		}
+		rate_ctr_add(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_SUCCESS), num_conns);
+		return create_ok_response(trunk, NULL, 200, "DLCX", pdata->trans);
 	}
 
 	for_each_line(line, pdata->save) {
@@ -1364,9 +1446,9 @@
 			   then this request cannot be handled */
 			if (!endp) {
 				LOGPTRUNK(trunk, DLMGCP, LOGL_NOTICE,
-					  "cannot handle requests with call-id (C) without endpoint -- abort!");
+						"cannot handle requests with call-id (C) without endpoint -- abort!");
 				rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_UNHANDLED_PARAM));
-				return create_err_response(NULL, 539, "DLCX", pdata->trans);
+				return create_err_response(rq->trunk, NULL, 539, "DLCX", pdata->trans);
 			}
 
 			if (mgcp_verify_call_id(endp, line + 3) != 0) {
@@ -1380,9 +1462,9 @@
 			   then this request cannot be handled */
 			if (!endp) {
 				LOGPTRUNK(trunk, DLMGCP, LOGL_NOTICE,
-					  "cannot handle requests with conn-id (I) without endpoint -- abort!");
+					"cannot handle requests with conn-id (I) without endpoint -- abort!");
 				rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_UNHANDLED_PARAM));
-				return create_err_response(NULL, 539, "DLCX", pdata->trans);
+				return create_err_response(rq->trunk, NULL, 539, "DLCX", pdata->trans);
 			}
 
 			conn_id = (const char *)line + 3;
@@ -1398,24 +1480,11 @@
 			LOGPEPTR(endp, trunk, DLMGCP, LOGL_NOTICE, "DLCX: Unhandled MGCP option: '%c'/%d\n",
 				 line[0], line[0]);
 			rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_FAIL_UNHANDLED_PARAM));
-			return create_err_response(NULL, 539, "DLCX", pdata->trans);
+			return create_err_response(rq->trunk, NULL, 539, "DLCX", pdata->trans);
 			break;
 		}
 	}
 
-	/* Handle wildcarded DLCX that refers to the whole trunk. This means
-	 * that we walk over all endpoints on the trunk in order to drop all
-	 * connections on the trunk. (see also RFC3435 Annex F.7) */
-	if (rq->wildcarded) {
-		int num_conns = 0;
-		for (i = 0; i < trunk->number_endpoints; i++) {
-			num_conns += llist_count(&trunk->endpoints[i]->conns);
-			mgcp_endp_release(trunk->endpoints[i]);
-		}
-		rate_ctr_add(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_SUCCESS), num_conns);
-		return create_ok_response(NULL, 200, "DLCX", pdata->trans);
-	}
-
 	/* The logic does not permit to go past this point without having the
 	 * the endp pointer populated. */
 	OSMO_ASSERT(endp);
@@ -1438,7 +1507,7 @@
 		/* Note: In this case we do not return any statistics,
 		 * as we assume that the client is not interested in
 		 * this case. */
-		return create_ok_response(endp, 200, "DLCX", pdata->trans);
+		return create_ok_response(endp, endp, 200, "DLCX", pdata->trans);
 	}
 
 	/* Find the connection */
@@ -1467,10 +1536,10 @@
 	rate_ctr_inc(rate_ctr_group_get_ctr(rate_ctrs, MGCP_DLCX_SUCCESS));
 	if (silent)
 		goto out_silent;
-	return create_ok_resp_with_param(endp, 250, "DLCX", pdata->trans, stats);
+	return create_ok_resp_with_param(endp, endp, 250, "DLCX", pdata->trans, stats);
 
 error3:
-	return create_err_response(endp, error_code, "DLCX", pdata->trans);
+	return create_err_response(endp, endp, error_code, "DLCX", pdata->trans);
 
 out_silent:
 	LOGPENDP(endp, DLMGCP, LOGL_DEBUG, "DLCX: silent exit\n");
@@ -1488,10 +1557,15 @@
 	 * mechanism to distinguish which endpoint shall be resetted
 	 * is needed */
 
+	LOGP(DLMGCP, LOGL_NOTICE, "Asked to reset endpoints: %u/%d\n",
+			rq->trunk->trunk_nr, rq->trunk->trunk_type);
 	LOGP(DLMGCP, LOGL_NOTICE, "RSIP: resetting all endpoints ...\n");
 
-	if (rq->pdata->cfg->reset_cb)
-		rq->pdata->cfg->reset_cb(rq->endp->trunk);
+	/* Walk over all endpoints and trigger a release, this will release all
+		* endpoints, possible open connections are forcefully dropped */
+	for (int i = 0; i < rq->trunk->number_endpoints; ++i)
+		mgcp_endp_release(rq->trunk->endpoints[i]);
+
 	return NULL;
 }
 
@@ -1515,7 +1589,7 @@
 
 	LOGP(DLMGCP, LOGL_NOTICE, "RQNT: processing request for notification ...\n");
 
-	for_each_line(line, rq->pdata->save) {
+	for_each_line(line, rq->pdatap->save) {
 		switch (toupper(line[0])) {
 		case 'S':
 			tone = extract_tone(line);
@@ -1525,14 +1599,14 @@
 
 	/* we didn't see a signal request with a tone */
 	if (tone == CHAR_MAX)
-		return create_ok_response(rq->endp, 200, "RQNT", rq->pdata->trans);
+		return create_ok_response(rq->endp, rq->endp, 200, "RQNT", rq->pdatap->trans);
 
-	if (rq->pdata->cfg->rqnt_cb)
-		res = rq->pdata->cfg->rqnt_cb(rq->endp, tone);
+	if (rq->pdatap->cfg->rqnt_cb)
+		res = rq->pdatap->cfg->rqnt_cb(rq->endp, tone);
 
 	return res == 0 ?
-	    create_ok_response(rq->endp, 200, "RQNT", rq->pdata->trans) :
-	    create_err_response(rq->endp, res, "RQNT", rq->pdata->trans);
+	    create_ok_response(rq->endp, rq->endp, 200, "RQNT", rq->pdatap->trans) :
+	    create_err_response(rq->endp, rq->endp, res, "RQNT", rq->pdatap->trans);
 }
 
 /* Connection keepalive timer, will take care that dummy packets are send
@@ -1618,14 +1692,14 @@
 
 	osmo_strlcpy(cfg->domain, "mgw", sizeof(cfg->domain));
 
-	cfg->net_ports.lock = PTHREAD_MUTEX_INITIALIZER;
+	cfg->net_ports.lock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
 	cfg->net_ports.range_start = RTP_PORT_DEFAULT_RANGE_START;
 	cfg->net_ports.range_end = RTP_PORT_DEFAULT_RANGE_END;
 	cfg->net_ports.last_port = cfg->net_ports.range_start;
 
 	cfg->source_port = 2427;
 	osmo_strlcpy(cfg->source_addr, "0.0.0.0", sizeof(cfg->source_addr));
-	osmo_strlcpy(cfg->osmux_addr, "0.0.0.0", sizeof(cfg->osmux_addr));
+	osmo_strlcpy(cfg->global_osmux_options.osmux_addr, "0.0.0.0", sizeof(cfg->global_osmux_options.osmux_addr));
 
 	cfg->rtp_processing_cb = &mgcp_rtp_processing_default;
 	cfg->setup_rtp_processing_cb = &mgcp_setup_rtp_processing_default;
diff --git a/src/libosmo-mgcp/mgcp_sdp.c b/src/libosmo-mgcp/mgcp_sdp.c
index 801ae35..27d24fe 100644
--- a/src/libosmo-mgcp/mgcp_sdp.c
+++ b/src/libosmo-mgcp/mgcp_sdp.c
@@ -601,7 +601,7 @@
 
 		payload_types[0] = payload_type;
 		if (mgcp_conn_rtp_is_osmux(conn))
-			local_port = endp->trunk->cfg->osmux_port;
+			local_port = osmux_get_global_opt()->osmux_port;
 		else
 			local_port = conn->end.local_port;
 		rc = add_audio(sdp, payload_types, 1, local_port);
diff --git a/src/libosmo-mgcp/mgcp_stat.c b/src/libosmo-mgcp/mgcp_stat.c
index 49493ce..bd5df51 100644
--- a/src/libosmo-mgcp/mgcp_stat.c
+++ b/src/libosmo-mgcp/mgcp_stat.c
@@ -31,6 +31,7 @@
 #include <osmocom/mgcp/mgcp_trunk.h>
 
 /* Helper function for mgcp_format_stats_rtp() to calculate packet loss */
+__attribute__((no_sanitize("integer")))
 void calc_loss(struct mgcp_conn_rtp *conn, uint32_t *expected, int *loss)
 {
 	struct mgcp_rtp_state *state = &conn->state;
@@ -94,7 +95,7 @@
 	str += nchars;
 	str_len -= nchars;
 
-	if (conn->conn->endp->trunk->cfg->osmux != OSMUX_USAGE_OFF) {
+	if (osmux_get_global_opt()->osmux != OSMUX_USAGE_OFF) {
 		/* Error Counter */
 		nchars = snprintf(str, str_len,
 				  "\r\nX-Osmo-CP: EC TI=%" PRIu64 ", TO=%" PRIu64,
diff --git a/src/libosmo-mgcp/mgcp_threads.c b/src/libosmo-mgcp/mgcp_threads.c
new file mode 100644
index 0000000..fe939b4
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_threads.c
@@ -0,0 +1,457 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild
+ *
+ * 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 <pthread.h>
+#include <stdio.h>
+#include <talloc.h>
+#include <assert.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/mgcp/mgcp_threads.h>
+#include <osmocom/mgcp/mgcp_trunk.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+#include <osmocom/mgcp/mgcp_endp.h>
+#include <osmocom/vty/cpu_sched_vty.h>
+
+static struct log_info log_info = {};
+void __thread *top_ctx;
+
+static void thread_handle_vty_msg(struct to_trunkthread_cfg_msg *w, struct per_thread_info *thread_info);
+static ssize_t thread_wait_vty_cmd_response(struct mgcp_trunk *master_trunk, int i);
+
+#define GETF(yy, zz)                                                                                                   \
+	static struct spsc *get_spsc_##yy##_##zz##_for_thread(struct mgcp_trunk *trunk, unsigned int threadnum)        \
+	{                                                                                                              \
+		return trunk->thread_info[threadnum].chan_##yy.zz;                                                     \
+	}
+
+GETF(cfg, a)
+GETF(cfg, b)
+GETF(mgcp, a)
+GETF(mgcp, b)
+
+#undef GETFUN
+
+void thread_push_msg(struct mgcp_trunk *trunk, unsigned int threadnum, void *elem)
+{
+	while (!spsc_push(get_spsc_mgcp_a_for_thread(trunk, threadnum), elem))
+		;
+}
+
+static void _thread_dispatch_ep_msg(struct mgcp_trunk *master_trunk, int threadnum, struct to_trunkthread_cfg_msg *m)
+{
+	/* this trunk does not have threads yet - we're parsing the config file */
+	if (!master_trunk->thread_info)
+		return;
+
+	if (!master_trunk->use_threads) {
+		thread_handle_vty_msg(m, master_trunk->thread_info);
+		return;
+	}
+
+	while (!spsc_push(get_spsc_cfg_a_for_thread(master_trunk, threadnum), m))
+		;
+
+	thread_wait_vty_cmd_response(master_trunk, threadnum);
+}
+
+void thread_dispatch_ep_msg(struct mgcp_trunk *master_trunk, int threadnum, struct vty *v,
+			    enum trunkthread_cfg_msg_t ty, struct to_trunkthread_cfg_msg *m)
+{
+	OSMO_ASSERT(m->type != IS_INVALID);
+	m->type = ty;
+	m->vty = v;
+
+	_thread_dispatch_ep_msg(master_trunk, threadnum, m);
+}
+
+/*! send vty message to a trunks thread.
+ *  \param[in] trunk master trunk.
+ *  \param[in] threadnum thread number.
+ *  \param[in] vty vty struct.
+ *  \param[in] epname epname or NULL for all endpoints.
+ *  \param[in] show_stats show stats as well.
+ *  \param[in] active_only only show active endpoint(s). */
+void thread_dispatch_ep_dump_msg(struct mgcp_trunk *master_trunk, int threadnum, struct vty *vty, const char *epname,
+				 bool show_stats, bool active_only)
+{
+	struct to_trunkthread_cfg_msg m = { .type = IS_VTYMSG,
+					    .vty = vty,
+					    .v.epname = { 0 },
+					    .v.show_stats = show_stats,
+					    .v.active_only = active_only };
+
+	if (epname)
+		osmo_strlcpy(m.v.epname, epname, sizeof(m.v.epname));
+
+	_thread_dispatch_ep_msg(master_trunk, threadnum, &m);
+}
+
+/*! wait for a trunks thread to respond to a previous cfg message. */
+static ssize_t thread_wait_vty_cmd_response(struct mgcp_trunk *master_trunk, int i)
+{
+	struct to_trunkthread_cfg_msg w;
+	if (!master_trunk->use_threads)
+		return 0;
+
+	ssize_t rv = prep_pop(get_spsc_cfg_b_for_thread(master_trunk, i));
+	while (spsc_pop(get_spsc_cfg_b_for_thread(master_trunk, i), &w))
+		;
+	return rv;
+}
+
+static int map_ep_num_to_thread(struct mgcp_trunk *thread_parent_trunk, unsigned int which_epnum)
+{
+	int epnum_total = thread_parent_trunk->v.vty_number_endpoints;
+	int num_threads_total = thread_parent_trunk->num_threads;
+
+	which_epnum = which_epnum >= epnum_total ? epnum_total - 1 : which_epnum;
+	int a = epnum_total / num_threads_total;
+	int b = epnum_total % num_threads_total;
+
+	int rv = (a + 1) * b > which_epnum ? which_epnum / (a + 1) : (which_epnum - (b * (a + 1))) / a + b;
+	return rv;
+}
+
+int get_trunk_thread_for_ep_name(const char *epname, struct mgcp_trunk *thread_parent_trunk)
+{
+	char epname_stripped[MGCP_ENDPOINT_MAXLEN];
+	if (thread_parent_trunk->trunk_type == MGCP_TRUNK_E1)
+		return 0;
+
+	mgcp_endp_strip_name(epname_stripped, epname, thread_parent_trunk);
+
+	int epnum = strtol(epname_stripped, NULL, 16);
+
+	/* virtual trunk ep nums start with 1, split begins at index 0*/
+	epnum = epnum > 0 ? epnum - 1 : 0;
+	return map_ep_num_to_thread(thread_parent_trunk, epnum);
+}
+
+void get_ep_num_per_thread(struct mgcp_trunk *parent_trunk, int which_threadnum, unsigned int *ep_per_thread,
+			   unsigned int *ep_start_offset)
+{
+	int epnum_total = parent_trunk->v.vty_number_endpoints;
+	int num_threads_total = parent_trunk->num_threads;
+
+	int a = epnum_total / num_threads_total;
+	int b = epnum_total % num_threads_total;
+
+	*ep_per_thread = which_threadnum < b ? a + 1 : a;
+
+	if (which_threadnum <= b)
+		*ep_start_offset = which_threadnum * (a + 1);
+	else
+		*ep_start_offset = (b) * (a + 1) + (which_threadnum - b) * a;
+}
+
+/* it's fine to call this during cfg file parsing: num_threads is 0 */
+static void send_async_vty_update_msg_to_trunk_threads(struct mgcp_config *c, struct mgcp_trunk *t)
+{
+	struct to_trunkthread_cfg_msg m = { 0 };
+	m.type = IS_CFGMSG;
+	if (t) {
+		m.c.content |= HAS_TRUNK;
+		m.c.t = *t;
+	}
+	if (c) {
+		m.c.content |= HAS_CFG;
+		m.c.c = *c;
+	}
+	OSMO_ASSERT(m.c.content != 0);
+
+	/* if we don't have a trunk then this is a cfg change affecting all trunks */
+	if (t) {
+		if (!t->thread_info) /* we're parsing the config file */
+			return;
+
+		if (!t->use_threads) {
+			thread_handle_vty_msg(&m, t->thread_info);
+			return;
+		}
+		for (int i = 0; i < t->num_threads; i++)
+			while (!spsc_push(get_spsc_cfg_a_for_thread(t, i), &m))
+				;
+	} else {
+		llist_for_each_entry (t, &c->trunks, entry) {
+			if (!t->thread_info) /* we're parsing the config file */
+				continue;
+
+			if (!t->use_threads) {
+				thread_handle_vty_msg(&m, t->thread_info);
+			} else {
+				for (int i = 0; i < t->num_threads; i++)
+					while (!spsc_push(get_spsc_cfg_a_for_thread(t, i), &m))
+						;
+			}
+		}
+	}
+}
+
+void send_async_vty_trunk_update_msg(struct mgcp_trunk *t)
+{
+	send_async_vty_update_msg_to_trunk_threads(NULL, t);
+}
+
+void send_async_vty_cfg_update_msg(struct mgcp_config *c)
+{
+	send_async_vty_update_msg_to_trunk_threads(c, NULL);
+}
+
+/* just copy all settings, then sanitize them */
+static void update_local_trunk(struct per_thread_info *tinfo, struct mgcp_trunk *source_trunk)
+{
+	struct mgcp_trunk t, *dest_trunk = tinfo->this_trunk;
+
+	t = *source_trunk;
+	t.cfg = tinfo->this_trunk->cfg;
+
+	/* keep this: own data! */
+	t.this_thread_info = dest_trunk->this_thread_info;
+
+	t.keepalive_timer = dest_trunk->keepalive_timer;
+	t.number_endpoints = dest_trunk->number_endpoints;
+	t.number_endpoints_offset = dest_trunk->number_endpoints_offset;
+	t.endpoints = dest_trunk->endpoints;
+	t.ratectr = dest_trunk->ratectr;
+	t.stats = dest_trunk->stats;
+	t.num_threads = dest_trunk->num_threads;
+
+	if (dest_trunk->trunk_type == MGCP_TRUNK_VIRTUAL)
+		t.v = dest_trunk->v;
+	else
+		t.e1 = dest_trunk->e1;
+
+	*dest_trunk = t;
+
+	if (dest_trunk->keepalive_interval != source_trunk->keepalive_interval)
+		mgcp_trunk_set_keepalive(dest_trunk, dest_trunk->keepalive_interval);
+}
+
+/* just copy all settings, then sanitize them */
+static void update_local_cfg(struct per_thread_info *tinfo, struct mgcp_config *source_cfg)
+{
+	struct mgcp_config c;
+	c = *source_cfg;
+
+	/* no dynamic updates due to possible active threading! */
+	c.global_osmux_options.osmux = tinfo->this_trunk->cfg->global_osmux_options.osmux;
+
+	/* ensure our per-thread config copy only contains our own trunk 
+		this is mostly to ensure the osmux trunk lookup works as-is */
+	INIT_LLIST_HEAD(&c.trunks);
+	llist_add_tail(&tinfo->this_trunk->entry, &c.trunks);
+
+	//FIXME: whole config?
+	*tinfo->this_trunk->cfg = c;
+}
+
+void handle_vty_cmd(struct per_thread_info *thread_info, struct to_trunkthread_cfg_msg *m);
+
+static void thread_handle_vty_msg(struct to_trunkthread_cfg_msg *w, struct per_thread_info *thread_info)
+{
+	switch (w->type) {
+	case IS_CFGMSG:
+		if (w->c.content & HAS_TRUNK)
+			update_local_trunk(thread_info, &w->c.t);
+		if (w->c.content & HAS_CFG)
+			update_local_cfg(thread_info, &w->c.c);
+		break;
+	case IS_VTYMSG:
+	case IS_FREEEPMSG:
+	case IS_TAPMSG:
+	case IS_LOOPMSG:
+		handle_vty_cmd(thread_info, w);
+		break;
+	default:
+		break;
+	}
+}
+
+static int thread_cfg_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	uint64_t ev_cnt;
+	struct per_thread_info *thread_info = (struct per_thread_info *)ofd->data;
+	int rc = read(ofd->fd, &ev_cnt, sizeof(ev_cnt));
+	assert(rc == sizeof(ev_cnt));
+
+	struct to_trunkthread_cfg_msg w;
+	while (spsc_pop(thread_info->chan_cfg.a, &w)) {
+		thread_handle_vty_msg(&w, thread_info);
+	}
+
+	return 0;
+}
+
+static int thread_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	uint64_t ev_cnt;
+	struct per_thread_info *thread_info = (struct per_thread_info *)ofd->data;
+	int returnfd = thread_info->parent_trunk->cfg->gw_fd.bfd.fd;
+
+	int rc = read(ofd->fd, &ev_cnt, sizeof(ev_cnt));
+	assert(rc == sizeof(ev_cnt));
+
+	struct to_trunkthread_mgcp_msg w;
+	while (spsc_pop(thread_info->chan_mgcp.a, &w)) {
+		/* update offsets to pointers for interthread q */
+		w.x.rq.pdatap = &w.x.pdata;
+		w.x.pdata.trans = (char *)((uintptr_t)w.x.pdata.trans + &w.msg[0]);
+		w.x.pdata.epname = (char *)((uintptr_t)w.x.pdata.epname + &w.msg[0]);
+		if (w.x.pdata.save)
+			w.x.pdata.save = (char *)((uintptr_t)w.x.pdata.save + &w.msg[0]);
+
+		/* update data to this threads sub-trunk */
+		w.x.rq.trunk = thread_info->this_trunk;
+		w.x.rq.pdatap->cfg = thread_info->this_cfg;
+
+		struct msgb *ret = thread_handle_mgcp_message(&w, thread_info);
+		if (!ret)
+			return 0;
+
+		ssize_t srv = sendto(returnfd, ret->l2h, msgb_l2len(ret), 0, &w.x.addr.u.sa, sizeof(w.x.addr));
+
+		msgb_free(ret);
+		memset(&w, 0, sizeof(w));
+	}
+	return 0;
+}
+
+void *split_per_thead(void *info)
+{
+	char thrdname[16];
+	struct per_thread_info *thread_info = (struct per_thread_info *)info;
+	struct mgcp_trunk *parent_trunk = thread_info->parent_trunk;
+	int this_thread_number = thread_info->tid;
+
+	//top_ctx = talloc_named_const(NULL, 0, "top_thread_ctx");
+	/* NO msgb_alloc with default context allowed! TLS is slow! */
+
+	thread_info->this_cfg = talloc_zero(top_ctx, struct mgcp_config);
+	thread_info->this_trunk = talloc_zero(top_ctx, struct mgcp_trunk);
+
+	/* just copy everything */
+	*thread_info->this_trunk = *parent_trunk;
+	*thread_info->this_cfg = *parent_trunk->cfg;
+	thread_info->this_trunk->cfg = thread_info->this_cfg;
+
+	struct mgcp_trunk *t = thread_info->this_trunk;
+
+	get_ep_num_per_thread(parent_trunk, this_thread_number, &t->v.vty_number_endpoints,
+			      &t->number_endpoints_offset);
+	thread_info->eps_free = t->v.vty_number_endpoints;
+
+	/* illegal copied members: */
+	t->keepalive_timer = (struct osmo_timer_list){ 0 };
+	t->endpoints = 0;
+	t->number_endpoints = 0;
+	t->this_thread_info = info;
+	/* keep our rate & stats counters
+	t->ratectr = (struct mgcp_ratectr_trunk){ 0 };
+	t->stats = (struct mgcp_stat_trunk){ 0 };
+	*/
+
+	if (parent_trunk->use_threads) {
+		/* give this thread a useful name */
+		snprintf(thrdname, 16, "%s_%d_%d", parent_trunk->trunk_type == MGCP_TRUNK_VIRTUAL ? "v" : "e1",
+			 t->trunk_nr, this_thread_number);
+		prctl(PR_SET_NAME, thrdname, NULL, NULL, NULL);
+		osmo_cpu_sched_vty_apply_localthread();
+
+		osmo_ctx_init(thrdname);
+		osmo_init_logging2(OTC_GLOBAL, &log_info);
+		log_enable_multithread();
+		//msgb_talloc_ctx_init(OTC_GLOBAL,0); nooo is global!
+		osmo_select_init();
+		//osmo_stats_init(OTC_GLOBAL);
+
+		/* this trunks rate ctrs are actually the parent trunks counters and not part of this ratectr!
+		this is only used for dynamically allocated per-conn ratecounters */
+		rate_ctr_init(OTC_GLOBAL);
+	}
+
+	mgcp_trunk_set_keepalive(t, t->keepalive_interval);
+	//mgcp_ratectr_trunk_alloc(t);
+	//mgcp_stat_trunk_alloc(t);
+	mgcp_trunk_equip(t);
+
+	if (!parent_trunk->use_threads)
+		return 0;
+
+	/* fix wrong total count due to mgcp_trunk_equip */
+	osmo_stat_item_set(osmo_stat_item_group_get_item(t->stats.common, TRUNK_STAT_ENDPOINTS_TOTAL),
+			   parent_trunk->number_endpoints);
+
+	struct osmo_fd chan_mgcp_fd;
+	osmo_fd_setup(&chan_mgcp_fd, get_a_rdfd(&thread_info->chan_mgcp), OSMO_FD_READ, &thread_cb, info, 0);
+	osmo_fd_register(&chan_mgcp_fd);
+
+	struct osmo_fd chan_cfg_fd;
+	osmo_fd_setup(&chan_cfg_fd, get_a_rdfd(&thread_info->chan_cfg), OSMO_FD_READ, &thread_cfg_cb, info, 0);
+	osmo_fd_register(&chan_cfg_fd);
+
+	/* main loop */
+	while (1) {
+		osmo_select_main_ctx(0);
+	}
+
+	//FIXME: shutdown
+}
+
+void split_trunks_into_threads(struct mgcp_config *cfg)
+{
+	struct mgcp_trunk *trunk;
+
+	prctl(PR_SET_NAME, "main", NULL, NULL, NULL);
+
+	llist_for_each_entry (trunk, &cfg->trunks, entry) {
+		int num_threads = cfg->num_threads_for_virttrunk;
+
+		/* e1 has no threads! */
+		if (trunk->trunk_type == MGCP_TRUNK_E1) {
+			num_threads = 1;
+			trunk->use_threads = false;
+		}
+
+		trunk->num_threads = num_threads;
+		trunk->thread_info = talloc_zero_array(cfg, struct per_thread_info, num_threads);
+
+		for (int i = 0; i < num_threads; i++) {
+			trunk->thread_info[i].parent_trunk = trunk;
+			trunk->thread_info[i].tid = i;
+			trunk->thread_info[i].chan_mgcp = chan_init(10, sizeof(struct to_trunkthread_mgcp_msg));
+			trunk->thread_info[i].chan_cfg =
+				chan_init_ex(2, sizeof(struct to_trunkthread_cfg_msg), false, true, true, true);
+			/* currently unused
+			osmo_fd_setup(&trunk->thread_info[i].master_evfd, get_b_rdfd(&trunk->thread_info[i].qc), OSMO_FD_READ, &master_cb, &trunk->thread_info[i], 0);
+			osmo_fd_register(&trunk->thread_info[i].master_evfd);
+*/
+			if (trunk->use_threads)
+				pthread_create(&trunk->thread_info[i].thr, 0, split_per_thead, &trunk->thread_info[i]);
+			else
+				split_per_thead(&trunk->thread_info[i]);
+		}
+	}
+}
diff --git a/src/libosmo-mgcp/mgcp_threads_vty.c b/src/libosmo-mgcp/mgcp_threads_vty.c
new file mode 100644
index 0000000..59bce2f
--- /dev/null
+++ b/src/libosmo-mgcp/mgcp_threads_vty.c
@@ -0,0 +1,189 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Eric Wild
+ *
+ * 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/vty/vty.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/mgcp/mgcp_threads.h>
+#include <osmocom/mgcp/mgcp_conn.h>
+#include <osmocom/mgcp/mgcp_endp.h>
+
+void dump_rtp_end(struct vty *vty, struct mgcp_conn_rtp *conn)
+{
+	struct mgcp_rtp_state *state = &conn->state;
+	struct mgcp_rtp_end *end = &conn->end;
+	struct mgcp_rtp_codec *codec = end->codec;
+	struct rate_ctr *tx_packets, *tx_bytes;
+	struct rate_ctr *rx_packets, *rx_bytes;
+	struct rate_ctr *dropped_packets;
+
+	tx_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_PACKETS_TX_CTR);
+	tx_bytes = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_OCTETS_TX_CTR);
+	rx_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_PACKETS_RX_CTR);
+	rx_bytes = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_OCTETS_RX_CTR);
+	dropped_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_DROPPED_PACKETS_CTR);
+
+	vty_out(vty,
+		"   Packets Sent: %" PRIu64 " (%" PRIu64 " bytes total)%s"
+		"   Packets Received: %" PRIu64 " (%" PRIu64 " bytes total)%s"
+		"   Timestamp Errs: %" PRIu64 "->%" PRIu64 "%s"
+		"   Dropped Packets: %" PRIu64 "%s"
+		"   Payload Type: %d Rate: %u Channels: %d %s"
+		"   Frame Duration: %u Frame Denominator: %u%s"
+		"   FPP: %d Packet Duration: %u%s"
+		"   FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s"
+		"   Output-Enabled: %d Force-PTIME: %d%s",
+		tx_packets->current, tx_bytes->current, VTY_NEWLINE, rx_packets->current, rx_bytes->current,
+		VTY_NEWLINE, state->in_stream.err_ts_ctr->current, state->out_stream.err_ts_ctr->current, VTY_NEWLINE,
+		dropped_packets->current, VTY_NEWLINE, codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE,
+		codec->frame_duration_num, codec->frame_duration_den, VTY_NEWLINE, end->frames_per_packet,
+		end->packet_duration_ms, VTY_NEWLINE, end->fmtp_extra, codec->audio_name, codec->subtype_name,
+		VTY_NEWLINE, end->output_enabled, end->force_output_ptime, VTY_NEWLINE);
+}
+
+void dump_endpoint(struct vty *vty, struct mgcp_endpoint *endp, unsigned int trunk_nr, enum mgcp_trunk_type trunk_type,
+		   int show_stats)
+{
+	struct mgcp_conn *conn;
+
+	vty_out(vty, "%s trunk %u endpoint %s:%s", trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", trunk_nr,
+		endp->name, VTY_NEWLINE);
+	vty_out(vty, "   Availability: %s%s", mgcp_endp_avail(endp) ? "available" : "not in service", VTY_NEWLINE);
+
+	if (llist_empty(&endp->conns)) {
+		vty_out(vty, "   No active connections%s", VTY_NEWLINE);
+		return;
+	}
+
+	llist_for_each_entry (conn, &endp->conns, entry) {
+		vty_out(vty, "   CONN: %s%s", mgcp_conn_dump(conn), VTY_NEWLINE);
+
+		if (show_stats) {
+			if (endp->trunk->cfg->conn_timeout) {
+				struct timeval remaining;
+				osmo_timer_remaining(&conn->watchdog, NULL, &remaining);
+				vty_out(vty, "   Currently remaining timeout (seconds): %d.%06d%s",
+					(int)remaining.tv_sec, (int)remaining.tv_usec, VTY_NEWLINE);
+			}
+
+			/* FIXME: Also add verbosity for other
+			 * connection types (E1) as soon as
+			 * the implementation is available */
+			if (conn->type == MGCP_CONN_TYPE_RTP) {
+				dump_rtp_end(vty, &conn->u.rtp);
+			}
+		}
+	}
+}
+
+void handle_vty_cmd(struct per_thread_info *thread_info, struct to_trunkthread_cfg_msg *m)
+{
+	struct mgcp_trunk *t = thread_info->this_trunk;
+	struct vty *vty = m->vty;
+
+	switch (m->type) {
+	case IS_VTYMSG: {
+		struct vtymsg *w = &m->v;
+		/* all our eps */
+		if (!strlen(w->epname)) {
+			for (int i = 0; i < t->number_endpoints; i++) {
+				struct mgcp_endpoint *ep = t->endpoints[i];
+				if (!w->active_only || !llist_empty(&ep->conns))
+					dump_endpoint(m->vty, ep, t->trunk_nr, t->trunk_type, w->show_stats);
+			}
+		} else {
+			struct mgcp_endpoint *ep = mgcp_endp_find_specific(w->epname, t);
+			if (ep && (!w->active_only || !llist_empty(&ep->conns)))
+				dump_endpoint(m->vty, ep, t->trunk_nr, t->trunk_type, w->show_stats);
+		}
+		break;
+	}
+	case IS_FREEEPMSG: {
+		mgcp_endp_release(mgcp_endp_find_specific(m->f.endp, t));
+		break;
+	}
+	case IS_TAPMSG: {
+		struct tapmsg *w = &m->t;
+		struct mgcp_endpoint *endp = mgcp_endp_find_specific(w->epname, t);
+		struct mgcp_conn_rtp *conn = mgcp_conn_get_rtp(endp, w->connid);
+
+		if (!conn) {
+			vty_out(vty, "Conn ID %s is invalid.%s", w->connid, VTY_NEWLINE);
+			goto out;
+		}
+
+		struct mgcp_rtp_tap *tap = w->direction_is_in ? &conn->tap_in : &conn->tap_out;
+
+		memset(&tap->forward, 0, sizeof(tap->forward));
+
+		tap->forward.u.sa.sa_family = osmo_ip_str_type(w->addr);
+		switch (tap->forward.u.sa.sa_family) {
+		case AF_INET:
+			if (inet_pton(AF_INET, w->addr, &tap->forward.u.sin.sin_addr) != 1)
+				goto out;
+			tap->forward.u.sin.sin_port = w->destport;
+			break;
+		case AF_INET6:
+			if (inet_pton(AF_INET6, w->addr, &tap->forward.u.sin6.sin6_addr) != 1)
+				goto out;
+			tap->forward.u.sin6.sin6_port = w->destport;
+			break;
+		default:
+			goto out;
+		}
+		tap->enabled = 1;
+		break;
+	}
+	case IS_LOOPMSG: {
+		struct loopmsg *w = &m->l;
+		struct mgcp_conn *conn;
+		struct mgcp_endpoint *endp = mgcp_endp_find_specific(w->epname, t);
+
+		llist_for_each_entry (conn, &endp->conns, entry) {
+			if (conn->type == MGCP_CONN_TYPE_RTP)
+				/* Handle it like a MDCX, switch on SSRC patching if enabled */
+				mgcp_rtp_end_config(endp, 1, &conn->u.rtp.end);
+			else {
+				/* FIXME: Introduce support for other connection (E1)
+			 * types when implementation is available */
+				vty_out(vty,
+					"%%Can't enable SSRC patching,"
+					"connection %s is not an RTP connection.%s",
+					mgcp_conn_dump(conn), VTY_NEWLINE);
+			}
+
+			if (w->enable_loop)
+				conn->mode = MGCP_CONN_LOOPBACK;
+			else
+				conn->mode = conn->mode_orig;
+		}
+		break;
+	}
+	default:
+		break;
+	}
+
+out:
+
+	vty_flush(vty);
+	/* lazily ack by sending back the message */
+	while (!spsc_push(thread_info->chan_cfg.b, m))
+		;
+}
diff --git a/src/libosmo-mgcp/mgcp_trunk.c b/src/libosmo-mgcp/mgcp_trunk.c
index f41e5a5..4ac0454 100644
--- a/src/libosmo-mgcp/mgcp_trunk.c
+++ b/src/libosmo-mgcp/mgcp_trunk.c
@@ -69,6 +69,7 @@
 	trunk->audio_send_name = 1;
 	trunk->v.vty_number_endpoints = 512;
 	trunk->omit_rtcp = 0;
+	trunk->number_endpoints_offset = 0;
 
 	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
 
@@ -102,13 +103,13 @@
 	case MGCP_TRUNK_VIRTUAL:
 		/* Due to historical reasons the endpoints on the virtual
 		 * trunk start counting at 1. */
-		first_endpoint_nr = 1;
+		first_endpoint_nr = trunk->number_endpoints_offset + 1;
 		number_endpoints = trunk->v.vty_number_endpoints;
 		break;
 	case MGCP_TRUNK_E1:
 		/* The first timeslot on an E1 line is reserved for framing
 		 * and alignment and can not be used for audio transport */
-	        first_endpoint_nr = 1 * MGCP_ENDP_E1_SUBSLOTS;
+	        first_endpoint_nr = trunk->number_endpoints_offset + 1 * MGCP_ENDP_E1_SUBSLOTS;
 		number_endpoints = (NUM_E1_TS-1) * MGCP_ENDP_E1_SUBSLOTS;
 		break;
 	default:
diff --git a/src/libosmo-mgcp/mgcp_vty.c b/src/libosmo-mgcp/mgcp_vty.c
index a05733f..062442c 100644
--- a/src/libosmo-mgcp/mgcp_vty.c
+++ b/src/libosmo-mgcp/mgcp_vty.c
@@ -32,7 +32,9 @@
 #include <osmocom/mgcp/mgcp_conn.h>
 #include <osmocom/mgcp/mgcp_endp.h>
 #include <osmocom/mgcp/mgcp_trunk.h>
+#include <osmocom/mgcp/mgcp_threads.h>
 
+#include <stdio.h>
 #include <string.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -120,6 +122,8 @@
 		trunk->audio_send_name ? "" : "no ", VTY_NEWLINE);
 	vty_out(vty, " number endpoints %u%s",
 		trunk->v.vty_number_endpoints, VTY_NEWLINE);
+	vty_out(vty, " number threads %u%s",
+		g_cfg->num_threads_for_virttrunk, VTY_NEWLINE);
 	vty_out(vty, " %sallow-transcoding%s",
 		trunk->no_audio_transcoding ? "no " : "", VTY_NEWLINE);
 	if (strlen(g_cfg->call_agent_addr))
@@ -129,7 +133,7 @@
 		vty_out(vty, " rtp force-ptime %d%s", g_cfg->force_ptime,
 			VTY_NEWLINE);
 
-	switch (g_cfg->osmux) {
+	switch (g_cfg->global_osmux_options.osmux) {
 	case OSMUX_USAGE_ON:
 		vty_out(vty, " osmux on%s", VTY_NEWLINE);
 		break;
@@ -141,17 +145,17 @@
 		vty_out(vty, " osmux off%s", VTY_NEWLINE);
 		break;
 	}
-	if (g_cfg->osmux) {
+	if (g_cfg->global_osmux_options.osmux) {
 		vty_out(vty, " osmux bind-ip %s%s",
-			g_cfg->osmux_addr, VTY_NEWLINE);
+			g_cfg->global_osmux_options.osmux_addr, VTY_NEWLINE);
 		vty_out(vty, " osmux batch-factor %d%s",
-			g_cfg->osmux_batch, VTY_NEWLINE);
+			g_cfg->global_osmux_options.osmux_batch, VTY_NEWLINE);
 		vty_out(vty, " osmux batch-size %u%s",
-			g_cfg->osmux_batch_size, VTY_NEWLINE);
+			g_cfg->global_osmux_options.osmux_batch_size, VTY_NEWLINE);
 		vty_out(vty, " osmux port %u%s",
-			g_cfg->osmux_port, VTY_NEWLINE);
+			g_cfg->global_osmux_options.osmux_port, VTY_NEWLINE);
 		vty_out(vty, " osmux dummy %s%s",
-			g_cfg->osmux_dummy ? "on" : "off", VTY_NEWLINE);
+			g_cfg->global_osmux_options.osmux_dummy ? "on" : "off", VTY_NEWLINE);
 	}
 
 	if (g_cfg->conn_timeout)
@@ -160,81 +164,6 @@
 	return CMD_SUCCESS;
 }
 
-static void dump_rtp_end(struct vty *vty, struct mgcp_conn_rtp *conn)
-{
-	struct mgcp_rtp_state *state = &conn->state;
-	struct mgcp_rtp_end *end = &conn->end;
-	struct mgcp_rtp_codec *codec = end->codec;
-	struct rate_ctr *tx_packets, *tx_bytes;
-	struct rate_ctr *rx_packets, *rx_bytes;
-	struct rate_ctr *dropped_packets;
-
-	tx_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_PACKETS_TX_CTR);
-	tx_bytes = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_OCTETS_TX_CTR);
-	rx_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_PACKETS_RX_CTR);
-	rx_bytes = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_OCTETS_RX_CTR);
-	dropped_packets = rate_ctr_group_get_ctr(conn->rate_ctr_group, RTP_DROPPED_PACKETS_CTR);
-
-	vty_out(vty,
-		"   Packets Sent: %" PRIu64 " (%" PRIu64 " bytes total)%s"
-		"   Packets Received: %" PRIu64 " (%" PRIu64 " bytes total)%s"
-		"   Timestamp Errs: %" PRIu64 "->%" PRIu64 "%s"
-		"   Dropped Packets: %" PRIu64 "%s"
-		"   Payload Type: %d Rate: %u Channels: %d %s"
-		"   Frame Duration: %u Frame Denominator: %u%s"
-		"   FPP: %d Packet Duration: %u%s"
-		"   FMTP-Extra: %s Audio-Name: %s Sub-Type: %s%s"
-		"   Output-Enabled: %d Force-PTIME: %d%s",
-		tx_packets->current, tx_bytes->current, VTY_NEWLINE,
-		rx_packets->current, rx_bytes->current, VTY_NEWLINE,
-		state->in_stream.err_ts_ctr->current,
-		state->out_stream.err_ts_ctr->current,
-	        VTY_NEWLINE,
-		dropped_packets->current, VTY_NEWLINE,
-		codec->payload_type, codec->rate, codec->channels, VTY_NEWLINE,
-		codec->frame_duration_num, codec->frame_duration_den,
-		VTY_NEWLINE, end->frames_per_packet, end->packet_duration_ms,
-		VTY_NEWLINE, end->fmtp_extra, codec->audio_name,
-		codec->subtype_name, VTY_NEWLINE, end->output_enabled,
-		end->force_output_ptime, VTY_NEWLINE);
-}
-
-static void dump_endpoint(struct vty *vty, struct mgcp_endpoint *endp,
-			  unsigned int trunk_nr, enum mgcp_trunk_type trunk_type, int show_stats)
-{
-	struct mgcp_conn *conn;
-
-	vty_out(vty, "%s trunk %u endpoint %s:%s",
-		trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1", trunk_nr, endp->name, VTY_NEWLINE);
-	vty_out(vty, "   Availability: %s%s",
-		mgcp_endp_avail(endp) ? "available" : "not in service", VTY_NEWLINE);
-
-	if (llist_empty(&endp->conns)) {
-		vty_out(vty, "   No active connections%s", VTY_NEWLINE);
-		return;
-	}
-
-	llist_for_each_entry(conn, &endp->conns, entry) {
-		vty_out(vty, "   CONN: %s%s", mgcp_conn_dump(conn), VTY_NEWLINE);
-
-		if (show_stats) {
-			if (endp->trunk->cfg->conn_timeout) {
-				struct timeval remaining;
-				osmo_timer_remaining(&conn->watchdog, NULL, &remaining);
-				vty_out(vty, "   Currently remaining timeout (seconds): %d.%06d%s",
-					(int)remaining.tv_sec, (int)remaining.tv_usec, VTY_NEWLINE);
-			}
-
-			/* FIXME: Also add verbosity for other
-			 * connection types (E1) as soon as
-			 * the implementation is available */
-			if (conn->type == MGCP_CONN_TYPE_RTP) {
-				dump_rtp_end(vty, &conn->u.rtp);
-			}
-		}
-	}
-}
-
 static void dump_ratectr_global(struct vty *vty, struct mgcp_ratectr_global *ratectr)
 {
 	vty_out(vty, "%s", VTY_NEWLINE);
@@ -303,9 +232,6 @@
 
 static void dump_trunk(struct vty *vty, struct mgcp_trunk *trunk, int show_stats, int active_only)
 {
-	int i;
-	int active_count = 0;
-
 	vty_out(vty, "%s trunk %d with %d endpoints:%s",
 		trunk->trunk_type == MGCP_TRUNK_VIRTUAL ? "Virtual" : "E1",
 		trunk->trunk_nr, trunk->number_endpoints, VTY_NEWLINE);
@@ -315,21 +241,13 @@
 		return;
 	}
 
-	for (i = 0; i < trunk->number_endpoints; ++i) {
-		struct mgcp_endpoint *endp = trunk->endpoints[i];
-		if (!active_only || !llist_empty(&endp->conns)) {
-			dump_endpoint(vty, endp, trunk->trunk_nr, trunk->trunk_type,
-				      show_stats);
-			if (i < trunk->number_endpoints - 1)
-				vty_out(vty, "%s", VTY_NEWLINE);
-		}
-		if (!llist_empty(&endp->conns))
-			active_count++;
+	vty_out(vty, "%s endpoints: %s", active_only ? "ACTIVE" : "ALL", VTY_NEWLINE);
+	for (int i = 0; i < trunk->num_threads; i++) {
+		thread_dispatch_ep_dump_msg(trunk, i, vty, 0, show_stats, active_only);
+		vty_out(vty, "%s", VTY_NEWLINE);
+		vty_flush(vty);
 	}
 
-	if (active_count == 0)
-		vty_out(vty, "No endpoints in use.%s", VTY_NEWLINE);
-
 	if (show_stats)
 		dump_ratectr_trunk(vty, trunk);
 }
@@ -342,7 +260,7 @@
 	llist_for_each_entry(trunk, &g_cfg->trunks, entry)
 		dump_trunk(vty, trunk, show_stats, active_only);
 
-	if (g_cfg->osmux)
+	if (g_cfg->global_osmux_options.osmux)
 		vty_out(vty, "Osmux used CID: %d%s", osmux_cid_pool_count_used(),
 			VTY_NEWLINE);
 
@@ -377,25 +295,28 @@
 dump_mgcp_endpoint(struct vty *vty, struct mgcp_trunk *trunk, const char *epname)
 {
 	struct mgcp_endpoint *endp;
+	int threadnum;
 
-	if (trunk) {
-		/* If a trunk is given, search on that specific trunk only */
-		endp = mgcp_endp_by_name_trunk(NULL, epname, trunk);
-		if (!endp) {
-			vty_out(vty, "endpoint %s not configured on trunk %u%s", epname, trunk->trunk_nr, VTY_NEWLINE);
-			return;
-		}
-	} else {
+	if (!trunk) {
 		/* If no trunk is given, search on all possible trunks */
 		endp = mgcp_endp_by_name(NULL, epname, g_cfg);
 		if (!endp) {
 			vty_out(vty, "endpoint %s not configured%s", epname, VTY_NEWLINE);
 			return;
 		}
+		trunk = endp->trunk;
+	} else {
+		/* If a trunk is given, search on that specific trunk only */
+		endp = mgcp_endp_by_name_trunk(NULL, epname, trunk);
+		if (!endp) {
+			vty_out(vty, "endpoint %s not configured on trunk %u%s", epname,
+			 trunk->trunk_nr, VTY_NEWLINE);
+		return;
+		}
 	}
 
-	trunk = endp->trunk;
-	dump_endpoint(vty, endp, trunk->trunk_nr, trunk->trunk_type, true);
+	threadnum = get_trunk_thread_for_ep_name(epname, trunk);
+	thread_dispatch_ep_dump_msg(trunk, threadnum, vty, epname, true, false);
 }
 
 DEFUN(show_mcgp_endpoint, show_mgcp_endpoint_cmd,
@@ -424,6 +345,8 @@
 		return CMD_WARNING;
 	}
 
+	/* single e1 thread trunk */
+	trunk = trunk->thread_info[0].this_trunk;
 	dump_mgcp_endpoint(vty, trunk, argv[1]);
 	return CMD_SUCCESS;
 }
@@ -444,6 +367,7 @@
 	      "IPv6 Address to use in SDP record\n")
 {
 	osmo_strlcpy(g_cfg->local_ip, argv[0], sizeof(g_cfg->local_ip));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -456,6 +380,7 @@
       "IPv6 Address to bind to\n")
 {
 	osmo_strlcpy(g_cfg->source_addr, argv[0], sizeof(g_cfg->source_addr));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -466,6 +391,7 @@
 {
 	unsigned int port = atoi(argv[0]);
 	g_cfg->source_port = port;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -517,7 +443,7 @@
 	g_cfg->net_ports.range_start = start;
 	g_cfg->net_ports.range_end = end;
 	g_cfg->net_ports.last_port = g_cfg->net_ports.range_start;
-
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 ALIAS_DEPRECATED(cfg_mgcp_rtp_port_range,
@@ -534,6 +460,7 @@
 	      "IPv4 Address to bind to\n")
 {
 	osmo_strlcpy(g_cfg->net_ports.bind_addr_v4, argv[0], sizeof(g_cfg->net_ports.bind_addr_v4));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 ALIAS_DEPRECATED(cfg_mgcp_rtp_bind_ip,
@@ -549,6 +476,7 @@
 	      "Address to bind to\n")
 {
 	osmo_strlcpy(g_cfg->net_ports.bind_addr_v4, "", sizeof(g_cfg->net_ports.bind_addr_v4));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 ALIAS_DEPRECATED(cfg_mgcp_rtp_no_bind_ip,
@@ -565,6 +493,7 @@
 	      "IPv6 Address to bind to\n")
 {
 	osmo_strlcpy(g_cfg->net_ports.bind_addr_v6, argv[0], sizeof(g_cfg->net_ports.bind_addr_v6));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -576,6 +505,7 @@
 	      "Address to bind to\n")
 {
 	osmo_strlcpy(g_cfg->net_ports.bind_addr_v6, "", sizeof(g_cfg->net_ports.bind_addr_v6));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -586,6 +516,7 @@
 	      RTP_STR "automatic rtp bind ip selection\n")
 {
 	g_cfg->net_ports.bind_addr_probe = true;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -596,6 +527,7 @@
 	      NO_STR RTP_STR "no automatic rtp bind ip selection\n")
 {
 	g_cfg->net_ports.bind_addr_probe = false;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -608,6 +540,7 @@
 {
 	int dscp = atoi(argv[0]);
 	g_cfg->endp_dscp = dscp;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -620,6 +553,7 @@
 {
 	int prio = atoi(argv[0]);
 	g_cfg->endp_priority = prio;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -632,6 +566,7 @@
 	      "The required ptime (packet duration) in ms\n" "10 ms\n20 ms\n40 ms\n")
 {
 	g_cfg->force_ptime = atoi(argv[0]);
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -641,6 +576,7 @@
 	      "no rtp force-ptime", NO_STR RTP_STR FORCE_PTIME_STR)
 {
 	g_cfg->force_ptime = 0;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -659,6 +595,7 @@
 
 	osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt);
 	talloc_free(txt);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -670,6 +607,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->no_audio_transcoding = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -681,6 +619,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->no_audio_transcoding = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -720,6 +659,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->audio_send_ptime = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -732,6 +672,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->audio_send_ptime = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -744,6 +685,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->audio_send_name = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -756,6 +698,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->audio_send_name = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -777,6 +720,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->force_realloc = atoi(argv[0]);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -790,6 +734,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->rtp_accept_all = atoi(argv[0]);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -811,6 +756,7 @@
 {
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	trunk->omit_rtcp = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -823,6 +769,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->omit_rtcp = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -834,6 +781,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->force_constant_ssrc = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -845,6 +793,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->force_constant_ssrc = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -856,6 +805,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->force_aligned_timing = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -867,6 +817,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->force_aligned_timing = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -878,6 +829,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->rfc5993_hr_convert = true;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -889,6 +841,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	trunk->rfc5993_hr_convert = false;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -902,6 +855,7 @@
 	trunk->force_constant_ssrc = 0;
 	trunk->force_aligned_timing = 0;
 	trunk->rfc5993_hr_convert = false;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -914,6 +868,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	mgcp_trunk_set_keepalive(trunk, atoi(argv[0]));
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -926,6 +881,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -937,6 +893,7 @@
 	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
 	OSMO_ASSERT(trunk);
 	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_NEVER);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -949,6 +906,7 @@
       "IPv6 Address of the call agent\n")
 {
 	osmo_strlcpy(g_cfg->call_agent_addr, argv[0], sizeof(g_cfg->call_agent_addr));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1052,6 +1010,7 @@
 
 	osmo_talloc_replace_string(g_cfg, &trunk->audio_fmtp_extra, txt);
 	talloc_free(txt);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1097,6 +1056,7 @@
 	struct mgcp_trunk *trunk = vty->index;
 	OSMO_ASSERT(trunk);
 	trunk->force_realloc = atoi(argv[0]);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1110,6 +1070,7 @@
 	struct mgcp_trunk *trunk = vty->index;
 	OSMO_ASSERT(trunk);
 	trunk->rtp_accept_all = atoi(argv[0]);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1121,6 +1082,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->audio_send_ptime = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1132,6 +1094,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->audio_send_ptime = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1143,6 +1106,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->audio_send_name = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1154,6 +1118,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->audio_send_name = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1164,6 +1129,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->omit_rtcp = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1174,6 +1140,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->omit_rtcp = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1184,6 +1151,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->force_constant_ssrc = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1194,6 +1162,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->force_constant_ssrc = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1204,6 +1173,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->force_aligned_timing = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1214,6 +1184,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->force_aligned_timing = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1224,6 +1195,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->rfc5993_hr_convert = true;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1234,6 +1206,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->rfc5993_hr_convert = false;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1246,6 +1219,7 @@
 	trunk->force_constant_ssrc = 0;
 	trunk->force_aligned_timing = 0;
 	trunk->rfc5993_hr_convert = false;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1257,6 +1231,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	mgcp_trunk_set_keepalive(trunk, atoi(argv[0]));
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1268,6 +1243,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	mgcp_trunk_set_keepalive(trunk, MGCP_KEEPALIVE_ONCE);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1278,6 +1254,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	mgcp_trunk_set_keepalive(trunk, 0);
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1288,6 +1265,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->no_audio_transcoding = 0;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1298,6 +1276,7 @@
 {
 	struct mgcp_trunk *trunk = vty->index;
 	trunk->no_audio_transcoding = 1;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1311,6 +1290,7 @@
 	struct mgcp_trunk *trunk = vty->index;
 	int line_nr = atoi(argv[0]);
 	trunk->e1.vty_line_nr = line_nr;
+	send_async_vty_trunk_update_msg(trunk);
 	return CMD_SUCCESS;
 }
 
@@ -1323,7 +1303,8 @@
 {
 	struct mgcp_trunk *trunk;
 	struct mgcp_endpoint *endp;
-	struct mgcp_conn *conn;
+	int threadnum;
+	struct to_trunkthread_cfg_msg m = {0};
 
 	trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_E1, atoi(argv[0]));
 	if (!trunk) {
@@ -1346,24 +1327,13 @@
 	}
 
 	endp = trunk->endpoints[endp_no];
-	int loop = atoi(argv[2]);
-	llist_for_each_entry(conn, &endp->conns, entry) {
-		if (conn->type == MGCP_CONN_TYPE_RTP)
-			/* Handle it like a MDCX, switch on SSRC patching if enabled */
-			mgcp_rtp_end_config(endp, 1, &conn->u.rtp.end);
-		else {
-			/* FIXME: Introduce support for other connection (E1)
-			 * types when implementation is available */
-			vty_out(vty, "%%Can't enable SSRC patching,"
-				"connection %s is not an RTP connection.%s",
-				mgcp_conn_dump(conn), VTY_NEWLINE);
-		}
 
-		if (loop)
-			conn->mode = MGCP_CONN_LOOPBACK;
-		else
-			conn->mode = conn->mode_orig;
-	}
+
+
+	threadnum = get_trunk_thread_for_ep_name(endp->name, trunk);
+	m.l.enable_loop = atoi(argv[2]);;
+	osmo_strlcpy(m.l.epname, endp->name, sizeof(m.t.epname));
+	thread_dispatch_ep_msg(trunk, threadnum, vty, IS_LOOPMSG, &m);
 
 	return CMD_SUCCESS;
 }
@@ -1380,11 +1350,10 @@
       "Destination IPv6 of the data\n"
       "Destination port\n")
 {
-	struct mgcp_rtp_tap *tap;
 	struct mgcp_trunk *trunk;
 	struct mgcp_endpoint *endp;
-	struct mgcp_conn_rtp *conn;
-        const char *conn_id = NULL;
+	int threadnum;
+	struct to_trunkthread_cfg_msg m = {0};
 
 	trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_E1, atoi(argv[0]));
 	if (!trunk) {
@@ -1408,41 +1377,26 @@
 
 	endp = trunk->endpoints[endp_no];
 
-	conn_id = argv[2];
-	conn = mgcp_conn_get_rtp(endp, conn_id);
-	if (!conn) {
-		vty_out(vty, "Conn ID %s is invalid.%s",
-			conn_id, VTY_NEWLINE);
-		return CMD_WARNING;
-	}
+/// v---
 
+	bool direction_is_in = false;
 	if (strcmp(argv[3], "in") == 0)
-		tap = &conn->tap_in;
+		direction_is_in = true;
 	else if (strcmp(argv[3], "out") == 0)
-		tap = &conn->tap_out;
+	{}
 	else {
 		vty_out(vty, "Unknown mode... tricked vty?%s", VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	memset(&tap->forward, 0, sizeof(tap->forward));
+	threadnum = get_trunk_thread_for_ep_name(endp->name, trunk);
+	m.t.direction_is_in = direction_is_in;
+	osmo_strlcpy(m.t.epname, endp->name, sizeof(m.t.epname));
+	osmo_strlcpy(m.t.connid, argv[2], sizeof(m.t.connid));
+	osmo_strlcpy(m.t.addr, argv[4], sizeof(m.t.addr));
+	m.t.destport = htons(atoi(argv[5]));
+	thread_dispatch_ep_msg(trunk, threadnum, vty, IS_TAPMSG, &m);
 
-	tap->forward.u.sa.sa_family = osmo_ip_str_type(argv[4]);
-	switch (tap->forward.u.sa.sa_family) {
-	case AF_INET:
-		if (inet_pton(AF_INET, argv[4], &tap->forward.u.sin.sin_addr) != 1)
-			return CMD_WARNING;
-		tap->forward.u.sin.sin_port = htons(atoi(argv[5]));
-		break;
-	case AF_INET6:
-		if (inet_pton(AF_INET6, argv[4], &tap->forward.u.sin6.sin6_addr) != 1)
-			return CMD_WARNING;
-		tap->forward.u.sin6.sin6_port = htons(atoi(argv[5]));
-		break;
-	default:
-		return CMD_WARNING;
-	}
-	tap->enabled = 1;
 	return CMD_SUCCESS;
 }
 
@@ -1452,6 +1406,8 @@
 {
 	struct mgcp_trunk *trunk;
 	struct mgcp_endpoint *endp;
+	int threadnum;
+	struct to_trunkthread_cfg_msg m = {0};
 
 	trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_E1, atoi(argv[0]));
 	if (!trunk) {
@@ -1474,7 +1430,11 @@
 	}
 
 	endp = trunk->endpoints[endp_no];
-	mgcp_endp_release(endp);
+
+	threadnum = get_trunk_thread_for_ep_name(endp->name, trunk);
+	osmo_strlcpy(m.f.endp, endp->name, sizeof(m.f.endp));
+	thread_dispatch_ep_msg(trunk, threadnum, vty, IS_FREEEPMSG, &m);
+
 	return CMD_SUCCESS;
 }
 
@@ -1539,13 +1499,14 @@
 	OSMO_ASSERT(trunk);
 
 	if (strcmp(argv[0], "off") == 0) {
-		g_cfg->osmux = OSMUX_USAGE_OFF;
+		g_cfg->global_osmux_options.osmux = OSMUX_USAGE_OFF;
 		return CMD_SUCCESS;
 	} else if (strcmp(argv[0], "on") == 0)
-		g_cfg->osmux = OSMUX_USAGE_ON;
+		g_cfg->global_osmux_options.osmux = OSMUX_USAGE_ON;
 	else if (strcmp(argv[0], "only") == 0)
-		g_cfg->osmux = OSMUX_USAGE_ONLY;
+		g_cfg->global_osmux_options.osmux = OSMUX_USAGE_ONLY;
 
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 
 }
@@ -1557,7 +1518,8 @@
       "IPv4 Address to bind to\n"
       "IPv6 Address to bind to\n")
 {
-	osmo_strlcpy(g_cfg->osmux_addr, argv[0], sizeof(g_cfg->osmux_addr));
+	osmo_strlcpy(g_cfg->global_osmux_options.osmux_addr, argv[0], sizeof(g_cfg->global_osmux_options.osmux_addr));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1566,7 +1528,8 @@
       "osmux batch-factor <1-8>",
       OSMUX_STR "Batching factor\n" "Number of messages in the batch\n")
 {
-	g_cfg->osmux_batch = atoi(argv[0]);
+	g_cfg->global_osmux_options.osmux_batch = atoi(argv[0]);
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1575,7 +1538,8 @@
       "osmux batch-size <1-65535>",
       OSMUX_STR "batch size\n" "Batch size in bytes\n")
 {
-	g_cfg->osmux_batch_size = atoi(argv[0]);
+	g_cfg->global_osmux_options.osmux_batch_size = atoi(argv[0]);
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1583,7 +1547,8 @@
       cfg_mgcp_osmux_port_cmd,
       "osmux port <1-65535>", OSMUX_STR "port\n" "UDP port\n")
 {
-	g_cfg->osmux_port = atoi(argv[0]);
+	g_cfg->global_osmux_options.osmux_port = atoi(argv[0]);
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1594,10 +1559,10 @@
       "Disable dummy padding\n")
 {
 	if (strcmp(argv[0], "on") == 0)
-		g_cfg->osmux_dummy = 1;
+		g_cfg->global_osmux_options.osmux_dummy = 1;
 	else if (strcmp(argv[0], "off") == 0)
-		g_cfg->osmux_dummy = 0;
-
+		g_cfg->global_osmux_options.osmux_dummy = 0;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1608,6 +1573,7 @@
       "Qualified domain name expected in MGCP endpoint names, or '*' to accept any domain\n")
 {
 	osmo_strlcpy(g_cfg->domain, argv[0], sizeof(g_cfg->domain));
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1621,6 +1587,23 @@
       "Timeout value (sec.)\n")
 {
 	g_cfg->conn_timeout = strtoul(argv[0], NULL, 10);
+	send_async_vty_cfg_update_msg(g_cfg);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mgcp_num_threads_for_virttrunk,
+      cfg_mgcp_num_threads_for_virttrunk_cmd,
+      "number threads <1-65534>",
+      "Sets the number of threads that will handle the configured endpoints,"
+	  " the thread name is automatially set to <v|e1>_<trunknum>_<threadnum> starting with thread 0\n"
+      "Number of threads\n")
+{
+	struct mgcp_trunk *trunk = mgcp_trunk_by_num(g_cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
+	OSMO_ASSERT(trunk);
+	int v = strtoul(argv[0], NULL, 10);
+	g_cfg->num_threads_for_virttrunk = v;
+	trunk->use_threads = v ? true : false;
+	send_async_vty_cfg_update_msg(g_cfg);
 	return CMD_SUCCESS;
 }
 
@@ -1694,6 +1677,7 @@
 	install_element(MGCP_NODE, &cfg_mgcp_no_allow_transcoding_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_domain_cmd);
 	install_element(MGCP_NODE, &cfg_mgcp_conn_timeout_cmd);
+	install_element(MGCP_NODE, &cfg_mgcp_num_threads_for_virttrunk_cmd);
 
 	install_element(MGCP_NODE, &cfg_mgcp_trunk_cmd);
 	install_node(&trunk_node, config_write_trunk);
@@ -1734,9 +1718,10 @@
 	int rc;
 	struct mgcp_trunk *trunk;
 
-	cfg->osmux_port = OSMUX_PORT;
-	cfg->osmux_batch = 4;
-	cfg->osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX;
+	cfg->global_osmux_options.osmux_port = OSMUX_PORT;
+	cfg->global_osmux_options.osmux_batch = 4;
+	cfg->global_osmux_options.osmux_batch_size = OSMUX_BATCH_DEFAULT_MAX;
+	cfg->num_threads_for_virttrunk = 1;
 
 	g_cfg = cfg;
 	rc = vty_read_config_file(config_file, NULL);
@@ -1761,5 +1746,6 @@
 	}
 	cfg->role = role;
 
+	osmux_set_global_opt(&g_cfg->global_osmux_options);
 	return 0;
 }
diff --git a/src/osmo-mgw/mgw_main.c b/src/osmo-mgw/mgw_main.c
index d12011c..cf7fad5 100644
--- a/src/osmo-mgw/mgw_main.c
+++ b/src/osmo-mgw/mgw_main.c
@@ -42,6 +42,7 @@
 #include <osmocom/mgcp/mgcp_endp.h>
 #include <osmocom/mgcp/mgcp_trunk.h>
 #include <osmocom/mgcp/mgcp_ctrl.h>
+#include <osmocom/mgcp/mgcp_threads.h>
 
 #include <osmocom/core/application.h>
 #include <osmocom/core/msgb.h>
@@ -77,8 +78,6 @@
 /* FIXME: Make use of the rtp proxy code */
 
 static struct mgcp_config *cfg;
-static struct mgcp_trunk *reset_trunk;
-static int reset_endpoints = 0;
 static int daemonize = 0;
 
 const char *osmomgw_copyright =
@@ -187,66 +186,35 @@
 	}
 }
 
-/* Callback function to be called when the RSIP ("Reset in Progress") mgcp
- * command is received */
-static int mgcp_rsip_cb(struct mgcp_trunk *trunk)
-{
-	/* Set flag so that, when read_call_agent() is called next time
-	 * the reset can progress */
-	reset_endpoints = 1;
-
-	reset_trunk = trunk;
-
-	return 0;
-}
-
 static int read_call_agent(struct osmo_fd *fd, unsigned int what)
 {
-	struct osmo_sockaddr addr;
-	socklen_t slen = sizeof(addr);
-	struct msgb *msg;
 	struct msgb *resp;
 	int i;
-
-	msg = (struct msgb *) fd->data;
+	struct to_trunkthread_mgcp_msg w = {0};
+	socklen_t slen = sizeof(w.x.addr);
 
 	/* read one less so we can use it as a \0 */
-	int rc = recvfrom(cfg->gw_fd.bfd.fd, msg->data, msg->data_len - 1, 0,
-		(struct sockaddr *) &addr, &slen);
+	ssize_t rc = recvfrom(cfg->gw_fd.bfd.fd, w.msg, sizeof(w.msg)-1, 0,
+		(struct sockaddr *) &w.x.addr, &slen);
 	if (rc < 0) {
 		perror("Gateway failed to read");
 		return -1;
-	} else if (slen > sizeof(addr)) {
+	} else if (slen > sizeof(w.x.addr)) {
 		fprintf(stderr, "Gateway received message from outerspace: %zu %zu\n",
-			(size_t) slen, sizeof(addr));
+			(size_t) slen, sizeof(w.x.addr));
 		return -1;
 	}
 
-	/* handle message now */
-	msg->l2h = msgb_put(msg, rc);
-	resp = mgcp_handle_message(cfg, msg);
-	msgb_reset(msg);
+	w.x.pdata.cfg = cfg;
+	w.x.msglen = rc;
+
+	resp = mgcp_submit_message_to_trunkthread(cfg, &w);
 
 	if (resp) {
-		sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, &addr.u.sa, sizeof(addr));
+		sendto(cfg->gw_fd.bfd.fd, resp->l2h, msgb_l2len(resp), 0, &w.x.addr.u.sa, sizeof(w.x.addr));
 		msgb_free(resp);
 	}
 
-	/* reset endpoints */
-	if (reset_endpoints) {
-		LOGP(DLMGCP, LOGL_NOTICE,
-		     "Asked to reset endpoints: %u/%d\n",
-		     reset_trunk->trunk_nr, reset_trunk->trunk_type);
-
-		/* reset flag */
-		reset_endpoints = 0;
-
-		/* Walk over all endpoints and trigger a release, this will release all
-		 * endpoints, possible open connections are forcefully dropped */
-		for (i = 0; i < reset_trunk->number_endpoints; ++i)
-			mgcp_endp_release(reset_trunk->endpoints[i]);
-	}
-
 	return 0;
 }
 
@@ -324,6 +292,10 @@
 	osmo_init_logging2(tall_mgw_ctx, &log_info);
 	libosmo_abis_init(tall_mgw_ctx);
 
+
+	rate_ctr_init(tall_mgw_ctx);
+	osmo_stats_init(tall_mgw_ctx);
+
 	cfg = mgcp_config_alloc();
 	if (!cfg)
 		return -1;
@@ -334,23 +306,33 @@
 	vty_info.usr_attr_letters[MGW_CMD_ATTR_NEWCONN] = 'n';
 
 	vty_init(&vty_info);
+	osmo_cpu_sched_vty_init(tall_mgw_ctx);
+	osmo_cpu_sched_force_apply_later(true);
 	logging_vty_add_cmds();
 	osmo_talloc_vty_add_cmds();
 	osmo_stats_vty_add_cmds();
 	mgcp_vty_init();
 	ctrl_vty_init(cfg);
 	e1inp_vty_init();
-	osmo_cpu_sched_vty_init(tall_mgw_ctx);
 
 	handle_options(argc, argv);
 
-	rate_ctr_init(tall_mgw_ctx);
-	osmo_stats_init(tall_mgw_ctx);
+	talloc_disable_null_tracking();
+	log_enable_multithread();
 
 	rc = mgcp_parse_config(config_file, cfg, MGCP_BSC);
 	if (rc < 0)
 		return rc;
 
+	if (cfg->global_osmux_options.osmux) {
+		struct mgcp_trunk *trunk;
+		fprintf(stderr, "Osmux usage impossible with threads! Disabling threads..\n");
+		llist_for_each_entry(trunk, &cfg->trunks, entry) {
+			trunk->use_threads = false;
+			trunk->num_threads = 1;
+		}
+	}
+
 	/* start telnet after reading config for vty_get_bind_addr() */
 	rc = telnet_init_dynif(tall_mgw_ctx, NULL,
 			       vty_get_bind_addr(), OSMO_VTY_PORT_MGW);
@@ -363,9 +345,7 @@
 			ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_MGW);
 	}
 
-	/* Set the reset callback function. This functions is called when the
-	 * mgcp-command "RSIP" (Reset in Progress) is received */
-	cfg->reset_cb = mgcp_rsip_cb;
+	split_trunks_into_threads(cfg);
 
 	/* we need to bind a socket */
 	flags = OSMO_SOCK_F_BIND;
@@ -381,11 +361,7 @@
 	}
 
 	cfg->gw_fd.bfd.cb = read_call_agent;
-	cfg->gw_fd.bfd.data = msgb_alloc(4096, "mgcp-msg");
-	if (!cfg->gw_fd.bfd.data) {
-		fprintf(stderr, "Gateway memory error.\n");
-		return -1;
-	}
+	cfg->gw_fd.bfd.data = 0;
 
 	LOGP(DLMGCP, LOGL_NOTICE, "Configured for MGCP, listen on %s:%u\n",
 	     cfg->source_addr, cfg->source_port);
diff --git a/tests/mgcp/mgcp_test.c b/tests/mgcp/mgcp_test.c
index a8aad14..0eccc63 100644
--- a/tests/mgcp/mgcp_test.c
+++ b/tests/mgcp/mgcp_test.c
@@ -31,6 +31,7 @@
 #include <osmocom/mgcp/mgcp_sdp.h>
 #include <osmocom/mgcp/mgcp_codec.h>
 #include <osmocom/mgcp/mgcp_network.h>
+#include <osmocom/mgcp/mgcp_threads.h>
 
 #include <osmocom/core/application.h>
 #include <osmocom/core/talloc.h>
@@ -761,6 +762,24 @@
 	return 0;
 }
 
+
+static struct msgb * handle_message_threadwrapper(struct mgcp_config *cfg, struct mgcp_trunk *trunk, struct msgb *inp, const char* testname) {
+		struct msgb * msg;
+		struct to_trunkthread_mgcp_msg w = {0};
+		struct per_thread_info inf = {0};
+
+		trunk->thread_info = &inf;
+		w.x.pdata.cfg = cfg;
+		w.x.msglen = msgb_l2len(inp);
+		memcpy(w.msg, inp->data, msgb_l2len(inp) );
+
+		msg = mgcp_handle_message(cfg, &w);
+		if (msg || !w.x.successfully_parsed)
+			return msg;
+		msg = thread_handle_mgcp_message(&w, &inf);
+		return msg;
+}
+
 static void test_messages(void)
 {
 	struct mgcp_config *cfg;
@@ -770,7 +789,7 @@
 	struct mgcp_conn_rtp *conn = NULL;
 	char last_conn_id[256];
 	int rc;
-	char *last_endpoint = mgcp_debug_get_last_endpoint_name();
+	char *last_endpoint;
 
 	cfg = mgcp_config_alloc();
 	trunk = mgcp_trunk_by_num(cfg, MGCP_TRUNK_VIRTUAL, MGCP_VIRT_TRUNK_ID);
@@ -794,7 +813,8 @@
 					   t->extra_fmtp);
 
 		inp = create_msg(t->req, last_conn_id);
-		msg = mgcp_handle_message(cfg, inp);
+		msg = handle_message_threadwrapper(cfg, trunk, inp, t->name);
+		last_endpoint = mgcp_debug_get_last_endpoint_name();
 		msgb_free(inp);
 		if (!t->exp_resp) {
 			if (msg) {
@@ -924,7 +944,7 @@
 		printf("Testing %s\n", t->name);
 
 		inp = create_msg(t->req, last_conn_id);
-		msg = mgcp_handle_message(cfg, inp);
+		msg = handle_message_threadwrapper(cfg, trunk, inp, t->name);
 
 		msgb_free(inp);
 		if (msg && check_response(msg->data, t->exp_resp) != 0) {
@@ -944,7 +964,7 @@
 		/* Retransmit... */
 		printf("Re-transmitting %s\n", t->name);
 		inp = create_msg(t->req, last_conn_id);
-		msg = mgcp_handle_message(cfg, inp);
+		msg = handle_message_threadwrapper(cfg, trunk, inp, t->name);
 		msgb_free(inp);
 		if (check_response(msg->data, t->exp_resp) != 0) {
 			printf("%s failed '%s'\n", t->name, (char *)msg->data);
@@ -979,7 +999,7 @@
         mgcp_trunk_equip(trunk);
 
 	inp = create_msg(CRCX, NULL);
-	msg = mgcp_handle_message(cfg, inp);
+	msg = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
 	OSMO_ASSERT(msg);
 	OSMO_ASSERT(get_conn_id_from_response(msg->data, conn_id,
 					      sizeof(conn_id)) == 0);
@@ -988,7 +1008,7 @@
 
 	/* send the RQNT and check for the CB */
 	inp = create_msg(RQNT, conn_id);
-	msg = mgcp_handle_message(cfg, inp);
+	msg = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
 	if (strncmp((const char *)msg->l2h, "200", 3) != 0) {
 		printf("FAILED: message is not 200. '%s'\n", msg->l2h);
 		abort();
@@ -1003,7 +1023,7 @@
 	msgb_free(inp);
 
 	inp = create_msg(DLCX, conn_id);
-	msgb_free(mgcp_handle_message(cfg, inp));
+	msg = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
 	msgb_free(inp);
 	mgcp_endpoints_release(trunk);
 	talloc_free(cfg);
@@ -1386,7 +1406,7 @@
 	struct in_addr addr;
 	struct mgcp_conn_rtp *conn = NULL;
 	char conn_id[256];
-	char *last_endpoint = mgcp_debug_get_last_endpoint_name();
+	char* last_endpoint;
 
 	printf("Testing multiple payload types\n");
 
@@ -1396,9 +1416,9 @@
         mgcp_trunk_equip(trunk);
 
 	/* Allocate endpoint 1 at mgw with two codecs */
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_1, NULL);
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1412,9 +1432,9 @@
 	OSMO_ASSERT(conn->end.codec->payload_type == 18);
 
 	/* Allocate 2 at mgw with three codecs, last one ignored */
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_2, NULL);
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1433,9 +1453,9 @@
 	 * it makes and since we already decided in OS#2658 that a missing
 	 * LCO should pick a sane default codec, it makes sense to expect
 	 * the same behaviour if SDP lacks proper payload type information */
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_3, NULL);
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1449,9 +1469,9 @@
 	OSMO_ASSERT(conn->end.codec->payload_type == 0);
 
 	/* Allocate 4 at mgw with a single codec */
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_4, NULL);
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1465,10 +1485,10 @@
 	OSMO_ASSERT(conn->end.codec->payload_type == 18);
 
 	/* Allocate 5 at mgw and let osmo-mgw pick a codec from the list */
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_GSM_EXACT, NULL);
 	trunk->no_audio_transcoding = 1;
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1482,8 +1502,8 @@
 	OSMO_ASSERT(conn->end.codec->payload_type == 0);
 
 	inp = create_msg(MDCX_NAT_DUMMY, conn_id);
-	last_endpoint[0] = '\0';
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	msgb_free(inp);
 	msgb_free(resp);
 	OSMO_ASSERT(strcmp(last_endpoint,"rtpbridge/5 at mgw") == 0);
@@ -1509,10 +1529,10 @@
 	conn = mgcp_conn_get_rtp(endp, conn_id);
 	OSMO_ASSERT(!conn);
 
-	last_endpoint[0] = '\0';
 	inp = create_msg(CRCX_MULT_GSM_EXACT, NULL);
 	trunk->no_audio_transcoding = 0;
-	resp = mgcp_handle_message(cfg, inp);
+	resp = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
+	last_endpoint = mgcp_debug_get_last_endpoint_name();
 	OSMO_ASSERT(get_conn_id_from_response(resp->data, conn_id,
 					      sizeof(conn_id)) == 0);
 	msgb_free(inp);
@@ -1596,7 +1616,7 @@
         mgcp_trunk_equip(trunk);
 
 	inp = create_msg(CRCX, NULL);
-	msg = mgcp_handle_message(cfg, inp);
+	msg = handle_message_threadwrapper(cfg, trunk, inp, "__FILE__""__LINE__");
 
 	if (check_response(msg->data, CRCX_RET_NO_RTPMAP) != 0) {
 		printf("FAILED: there should not be a RTPMAP: %s\n",

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-mgw/+/25432
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-mgw
Gerrit-Branch: master
Gerrit-Change-Id: I31be8253600c8af0a43c967d0d128f5ba7b16260
Gerrit-Change-Number: 25432
Gerrit-PatchSet: 1
Gerrit-Owner: Hoernchen <ewild at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20210909/1b45849a/attachment.htm>


More information about the gerrit-log mailing list