changeset 15:ccc5ab6d8388

first version of themwi-mncc for ThemWi2
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 26 Jun 2022 16:31:47 -0800
parents aea422af79dd
children 4c2000b3aed7
files .hgignore mncc/Makefile mncc/call_setup.c mncc/extsock.c mncc/gsm_call.c mncc/gsm_call.h mncc/intswitch.c mncc/main.c mncc/mncc_recv.c mncc/mncc_sock.c mncc/mtsock.c mncc/struct.h
diffstat 12 files changed, 1223 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Sun Jun 26 14:42:33 2022 -0800
+++ b/.hgignore	Sun Jun 26 16:31:47 2022 -0800
@@ -2,6 +2,8 @@
 
 \.[oa]$
 
+^mncc/themwi-mncc$
+
 ^utils/themwi-check-own$
 ^utils/themwi-dump-numdb$
 ^utils/themwi-short-dial$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/Makefile	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,18 @@
+CC=	gcc
+CFLAGS=	-O2
+PROG=	themwi-mncc
+OBJS=	call_setup.o extsock.o gsm_call.o intswitch.o main.o mncc_recv.o \
+	mncc_sock.o mtsock.o
+LIBS=	../libnumdb/libnumdb.a ../libutil/libutil.a
+INSTBIN=/usr/local/bin
+
+all:	${PROG}
+
+${PROG}: ${OBJS} ${LIBS}
+	${CC} ${CFLAGS} -o $@ ${OBJS} ${LIBS}
+
+install:
+	install -c -o bin -g bin -m 755 ${PROG} ${INSTBIN}
+
+clean:
+	rm -f *.o ${PROG} errs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/call_setup.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,220 @@
+/*
+ * In this module we implement setup of new calls: either new MO calls
+ * coming from GSM or new MT calls coming from a ThemWi call socket.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "struct.h"
+#include "gsm_call.h"
+
+preen_msc_provided_number(nums)
+	struct gsm_mncc_number *nums;
+{
+	int len;
+
+	len = grok_number_string(nums->number, 0);
+	switch (len) {
+	case 4:
+		nums->type = GSM48_TON_NET_SPEC;
+		nums->plan = GSM48_NPI_PRIVATE;
+		break;
+	case 11:
+		if (nums->number[0] != '1')
+			return(0);
+		nums->type = GSM48_TON_INTERNATIONAL;
+		nums->plan = GSM48_NPI_ISDN_E164;
+		break;
+	default:
+		return(0);
+	}
+	nums->screen = GSM48_SCRN_NETWORK;
+	return(1);
+}
+
+void
+reject_mo_call(callref, cause_loc, cause_val)
+	uint32_t callref;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_REJ_REQ;
+	msg.callref = callref;
+	mncc_set_cause(&msg, cause_loc, cause_val);
+	send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc));
+}
+
+void
+process_mo_call_setup(msg)
+	struct gsm_mncc *msg;
+{
+	struct gsm_call *call;
+	char nanp[11];
+	int res, is_nanp, is_itn, is_local;
+
+	if (preen_msc_provided_number(&msg->calling))
+		msg->fields |= MNCC_F_CALLING;
+	else
+		msg->fields &= ~MNCC_F_CALLING;
+	if (!(msg->fields & MNCC_F_CALLED)) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INVAL_MAND_INF);
+		return;
+	}
+	call = create_gsm_call(msg->callref);
+	if (!call) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		return;
+	}
+	/* route based on destination address */
+	refresh_number_db();
+	is_nanp = is_itn = 0;
+	switch (grok_number_string(msg->called.number, 0)) {
+	case 4:
+		if (msg->called.type != GSM48_TON_UNKNOWN &&
+		    msg->called.type != GSM48_TON_NET_SPEC &&
+		    msg->called.type != GSM48_TON_SHORT_CODE)
+			break;
+		if (msg->called.plan != GSM48_NPI_UNKNOWN &&
+		    msg->called.plan != GSM48_NPI_ISDN_E164 &&
+		    msg->called.plan != GSM48_NPI_PRIVATE)
+			break;
+		res = lookup_short_dial_number(msg->called.number, nanp);
+		if (!res) {
+			reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_UNASSIGNED_NR);
+			call->gc_flag = 1;
+			return;
+		}
+		if (nanp[0]) {
+			is_nanp = 1;
+			msg->called.type = GSM48_TON_INTERNATIONAL;
+			msg->called.plan = GSM48_NPI_ISDN_E164;
+			msg->called.number[0] = '1';
+			strcpy(msg->called.number+1, nanp);
+		} else {
+			is_itn = 1;
+			msg->called.type = GSM48_TON_NET_SPEC;
+			msg->called.plan = GSM48_NPI_PRIVATE;
+		}
+		break;
+	case 10:
+		if (msg->called.type != GSM48_TON_UNKNOWN &&
+		    msg->called.type != GSM48_TON_NATIONAL)
+			break;
+		if (msg->called.plan != GSM48_NPI_UNKNOWN &&
+		    msg->called.plan != GSM48_NPI_ISDN_E164 &&
+		    msg->called.plan != GSM48_NPI_NATIONAL)
+			break;
+		if (!is_nanp_valid_prefix(msg->called.number)) {
+			reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_INV_NR_FORMAT);
+			call->gc_flag = 1;
+			return;
+		}
+		is_nanp = 1;
+		/* canonicalize to international format */
+		bcopy(msg->called.number, msg->called.number+1, 11);
+		msg->called.number[0] = '1';
+		msg->called.type = GSM48_TON_INTERNATIONAL;
+		msg->called.plan = GSM48_NPI_ISDN_E164;
+		break;
+	case 11:
+		if (msg->called.type != GSM48_TON_UNKNOWN &&
+		    msg->called.type != GSM48_TON_INTERNATIONAL)
+			break;
+		if (msg->called.plan != GSM48_NPI_UNKNOWN &&
+		    msg->called.plan != GSM48_NPI_ISDN_E164)
+			break;
+		if (msg->called.number[0] != '1')
+			break;
+		if (!is_nanp_valid_prefix(msg->called.number+1)) {
+			reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_INV_NR_FORMAT);
+			call->gc_flag = 1;
+			return;
+		}
+		is_nanp = 1;
+		/* canonicalize to international format */
+		msg->called.type = GSM48_TON_INTERNATIONAL;
+		msg->called.plan = GSM48_NPI_ISDN_E164;
+		break;
+	}
+	is_local = is_itn;
+	if (is_nanp && is_nanp_locally_owned(msg->called.number+1))
+		is_local = 1;
+	/* weed out attempts to call yourself */
+	if (is_local && !strcmp(msg->calling.number, msg->called.number)) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INCOMPAT_DEST);
+		call->gc_flag = 1;
+		return;
+	}
+	/* actually route the call */
+	if (is_local) {
+		internal_switch_mo_setup(call, msg);
+		return;
+	}
+	/* outbound calls remain to be implemented */
+	reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+			GSM48_CC_CAUSE_NO_ROUTE);
+	call->gc_flag = 1;
+}
+
+static void
+reject_mt_call(conn, callref, cause_loc, cause_val)
+	struct socket_conn *conn;
+	uint32_t callref;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_REJ_REQ;
+	msg.callref = callref;
+	mncc_set_cause(&msg, cause_loc, cause_val);
+	mncc_signal_to_socket_nocall(conn, &msg);
+}
+
+void
+process_ext_mtcall_setup(conn, msg)
+	struct socket_conn *conn;
+	struct gsm_mncc *msg;
+{
+	struct gsm_call *call;
+
+	if (!(msg->fields & MNCC_F_CALLED) && !msg->imsi[0]) {
+		reject_mt_call(conn, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INVAL_MAND_INF);
+		return;
+	}
+	call = create_new_mt_call();
+	if (!call) {
+		reject_mt_call(conn, msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		return;
+	}
+	call->socket = conn;
+	call->socket_ref = msg->callref;
+	/* forward to GSM MNCC interface */
+	msg->callref = call->callref;
+	send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc));
+}
+
+preen_connected_number(msg)
+	struct gsm_mncc *msg;
+{
+	if (preen_msc_provided_number(&msg->connected))
+		msg->fields |= MNCC_F_CONNECTED;
+	else
+		msg->fields &= ~MNCC_F_CONNECTED;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/extsock.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,229 @@
+/*
+ * In this module we gather functions that deal with external
+ * socket connections, both externally-originating MT calls
+ * and outbound MO calls.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "struct.h"
+#include "gsm_call.h"
+
+void
+extsock_dec_refcount(conn)
+	struct socket_conn *conn;
+{
+	if (!conn->ncalls) {
+		syslog(LOG_CRIT, "FATAL BUG: ncalls=0 on socket call clearing");
+		exit(1);
+	}
+	conn->ncalls--;
+}
+
+static void
+send_rel_on_broken_socket(call)
+	struct gsm_call *call;
+{
+	struct gsm_mncc msg;
+
+	bzero(&msg, sizeof(struct gsm_mncc));
+	msg.msg_type = MNCC_REL_REQ;
+	msg.callref = call->callref;
+	mncc_set_cause(&msg, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_DEST_OOO);
+	send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc));
+}
+
+static void
+broken_socket_clear_calls(conn)
+	struct socket_conn *conn;
+{
+	extern struct gsm_call *call_list_head;
+	struct gsm_call *call;
+
+	for (call = call_list_head; call; call = call->next) {
+		if (call->gc_flag)
+			continue;
+		if (call->socket == conn) {
+			send_rel_on_broken_socket(call);
+			extsock_dec_refcount(conn);
+			call->gc_flag = 1;
+		}
+	}
+	if (conn->ncalls) {
+		syslog(LOG_CRIT,
+		    "FATAL BUG: ncalls!=0 after broken socket call clearing");
+		exit(1);
+	}
+}
+
+static void
+report_runt(msg)
+	union mncc_msg *msg;
+{
+	syslog(LOG_CRIT,
+		"MNCC message type 0x%x from ThemWi call socket is too short!",
+		msg->msg_type);
+}
+
+static void
+handle_setup_req(conn, msg, msglen)
+	struct socket_conn *conn;
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_socket_call(conn, msg->callref);
+	if (call) {
+		syslog(LOG_ERR,
+			"duplicate MNCC_SETUP_REQ from socket for callref 0x%x",
+			msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* further processing */
+	process_ext_mtcall_setup(conn, msg);
+}
+
+static void
+handle_signaling_msg(conn, msg, msglen)
+	struct socket_conn *conn;
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_socket_call(conn, msg->callref);
+	if (!call) {
+		syslog(LOG_ERR,
+		"MNCC message from ThemWi call socket: callref 0x%x not found",
+			msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* forward to GSM MNCC interface */
+	msg->callref = call->callref;
+	send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc));
+	if (msg->msg_type == MNCC_REJ_REQ) {
+		extsock_dec_refcount(conn);
+		call->gc_flag = 1;
+	}
+}
+
+static void
+handle_rtp_msg(conn, msg, msglen)
+	struct socket_conn *conn;
+	struct gsm_mncc_rtp *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc_rtp)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_socket_call(conn, msg->callref);
+	if (!call) {
+		syslog(LOG_ERR,
+		"MNCC message from ThemWi call socket: callref 0x%x not found",
+			msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* forward to GSM MNCC interface */
+	msg->callref = call->callref;
+	send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc_rtp));
+}
+
+void
+extsock_read_select(conn)
+	struct socket_conn *conn;
+{
+	union mncc_msg msg;
+	int rc;
+
+	rc = recv(conn->fd, &msg, sizeof msg, 0);
+	if (rc < 4) {
+		if (conn->ncalls)
+			broken_socket_clear_calls(conn);
+		close(conn->fd);
+		conn->fd = -1;
+		return;
+	}
+	switch (msg.msg_type) {
+	case MNCC_SETUP_REQ:
+		handle_setup_req(conn, &msg, rc);
+		return;
+	case MNCC_SETUP_RSP:
+	case MNCC_SETUP_COMPL_REQ:
+	case MNCC_CALL_PROC_REQ:
+	case MNCC_PROGRESS_REQ:
+	case MNCC_ALERT_REQ:
+	case MNCC_NOTIFY_REQ:
+	case MNCC_DISC_REQ:
+	case MNCC_REL_REQ:
+	case MNCC_FACILITY_REQ:
+	case MNCC_START_DTMF_RSP:
+	case MNCC_START_DTMF_REJ:
+	case MNCC_STOP_DTMF_RSP:
+	case MNCC_MODIFY_REQ:
+	case MNCC_MODIFY_RSP:
+	case MNCC_MODIFY_REJ:
+	case MNCC_HOLD_CNF:
+	case MNCC_HOLD_REJ:
+	case MNCC_RETRIEVE_CNF:
+	case MNCC_RETRIEVE_REJ:
+	case MNCC_USERINFO_REQ:
+	case MNCC_REJ_REQ:
+		handle_signaling_msg(conn, &msg, rc);
+		return;
+	case MNCC_RTP_CREATE:
+	case MNCC_RTP_CONNECT:
+	case MNCC_RTP_FREE:
+		handle_rtp_msg(conn, &msg, rc);
+		return;
+	default:
+		syslog(LOG_CRIT,
+		    "unknown MNCC message type 0x%x from ThemWi call socket",
+			msg.msg_type);
+	}
+}
+
+mncc_signal_to_socket(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	msg->callref = call->socket_ref;
+	return send(call->socket->fd, msg, sizeof(struct gsm_mncc), 0);
+}
+
+mncc_signal_to_socket_nocall(conn, msg)
+	struct socket_conn *conn;
+	struct gsm_mncc *msg;
+{
+	return send(conn->fd, msg, sizeof(struct gsm_mncc), 0);
+}
+
+mncc_rtp_to_socket(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc_rtp *msg;
+{
+	msg->callref = call->socket_ref;
+	return send(call->socket->fd, msg, sizeof(struct gsm_mncc_rtp), 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/gsm_call.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,101 @@
+/*
+ * In this module we implement allocation, freeing and retrieval
+ * of gsm_call structures.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "struct.h"
+#include "gsm_call.h"
+
+struct gsm_call *call_list_head;
+
+static uint32_t mt_callref;
+
+struct gsm_call *
+find_gsm_callref(callref)
+	uint32_t callref;
+{
+	struct gsm_call *call;
+
+	for (call = call_list_head; call; call = call->next) {
+		if (call->gc_flag)
+			continue;
+		if (call->callref == callref)
+			return call;
+	}
+	return 0;
+}
+
+struct gsm_call *
+find_socket_call(conn, ref)
+	struct socket_conn *conn;
+	uint32_t ref;
+{
+	struct gsm_call *call;
+
+	for (call = call_list_head; call; call = call->next) {
+		if (call->gc_flag)
+			continue;
+		if (call->socket == conn && call->socket_ref == ref)
+			return call;
+	}
+	return 0;
+}
+
+struct gsm_call *
+create_gsm_call(callref)
+	uint32_t callref;
+{
+	struct gsm_call *call;
+
+	call = malloc(sizeof(struct gsm_call));
+	if (call) {
+		bzero(call, sizeof(struct gsm_call));
+		call->callref = callref;
+		call->next = call_list_head;
+		call_list_head = call;
+	}
+	return call;
+}
+
+uint32_t
+alloc_mt_callref()
+{
+	mt_callref++;
+	if (mt_callref > 0x7FFFFFFF)
+		mt_callref = 1;
+	return mt_callref;
+}
+
+struct gsm_call *
+create_new_mt_call()
+{
+	uint32_t callref;
+
+	for (;;) {
+		callref = alloc_mt_callref();
+		if (!find_gsm_callref(callref))
+			break;
+	}
+	return create_gsm_call(callref);
+}
+
+gc_call_list()
+{
+	struct gsm_call *call, **cp;
+
+	for (cp = &call_list_head; call = *cp; ) {
+		if (call->gc_flag) {
+			*cp = call->next;
+			free(call);
+			continue;
+		}
+		cp = &call->next;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/gsm_call.h	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,7 @@
+/* extern declarations for functions in gsm_call.c module */
+
+extern struct gsm_call *find_gsm_callref();
+extern struct gsm_call *find_socket_call();
+extern struct gsm_call *create_gsm_call();
+extern struct gsm_call *create_new_mt_call();
+extern uint32_t alloc_mt_callref();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/intswitch.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,211 @@
+/*
+ * In this module we implement internally switched calls,
+ * going from one GSM subscriber to another.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "../include/gsm48_const.h"
+#include "struct.h"
+#include "gsm_call.h"
+
+void
+internal_switch_mo_setup(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	struct gsm_call *mt;
+	struct gsm_mncc callproc;
+
+	if (!(msg->fields & MNCC_F_BEARER_CAP)) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_INVAL_MAND_INF);
+		call->gc_flag = 1;
+		return;
+	}
+	/* same speech-only restriction as in OsmoMSC's mncc_builtin */
+	if (msg->bearer_cap.transfer != GSM48_BCAP_ITCAP_SPEECH ||
+	    msg->bearer_cap.mode != GSM48_BCAP_TMOD_CIRCUIT) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		call->gc_flag = 1;
+		return;
+	}
+	mt = create_new_mt_call();
+	if (!mt) {
+		reject_mo_call(msg->callref, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		call->gc_flag = 1;
+		return;
+	}
+	call->other_leg = mt;
+	mt->other_leg = call;
+	/* send call proceeding */
+	bzero(&callproc, sizeof(struct gsm_mncc));
+	callproc.msg_type = MNCC_CALL_PROC_REQ;
+	callproc.callref = call->callref;
+	send_mncc_to_gsm(&callproc, sizeof(struct gsm_mncc));
+	/* turn MNCC_SETUP_IND into MNCC_SETUP_REQ for MT */
+	msg->msg_type = MNCC_SETUP_REQ;
+	msg->callref = mt->callref;
+	msg->imsi[0] = '\0';
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+}
+
+static void
+handle_setup_cnf(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	struct gsm_mncc ack;
+	struct gsm_mncc_bridge bridge;
+
+	/* acknowledge connect */
+	bzero(&ack, sizeof(struct gsm_mncc));
+	ack.msg_type = MNCC_SETUP_COMPL_REQ;
+	ack.callref = call->callref;
+	send_mncc_to_gsm(&ack, sizeof(struct gsm_mncc));
+	/* do we have the far end? */
+	if (!call->other_leg)
+		return;
+	msg->msg_type = MNCC_SETUP_RSP;
+	msg->callref = call->other_leg->callref;
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+	/* bridge TCH */
+	bzero(&bridge, sizeof(struct gsm_mncc_bridge));
+	bridge.msg_type = MNCC_BRIDGE;
+	bridge.callref[0] = call->callref;
+	bridge.callref[1] = call->other_leg->callref;
+	send_mncc_to_gsm(&bridge, sizeof(struct gsm_mncc_bridge));
+}
+
+static void
+forward_to_remote(call, msg, new_msg_type)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+	uint32_t new_msg_type;
+{
+	if (!call->other_leg) {
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	msg->msg_type = new_msg_type;
+	msg->callref = call->other_leg->callref;
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+}
+
+static void
+handle_disconnect(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	struct gsm_call *remote;
+
+	/* release on near end */
+	msg->msg_type = MNCC_REL_REQ;
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+	/* disconnect on far end */
+	remote = call->other_leg;
+	if (!remote)
+		return;
+	msg->msg_type = MNCC_DISC_REQ;
+	msg->callref = remote->callref;
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+	/* sever the end-to-end association */
+	call->other_leg = 0;
+	remote->other_leg = 0;
+}
+
+static void
+handle_release(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	struct gsm_call *remote;
+
+	/* do we have a far end? */
+	remote = call->other_leg;
+	/* free the near end */
+	call->gc_flag = 1;
+	/* if no remote, nothing more to do */
+	if (!remote)
+		return;
+	/* send them a release request */
+	msg->msg_type = MNCC_REL_REQ;
+	msg->callref = remote->callref;
+	send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+	/* sever the end-to-end association */
+	remote->other_leg = 0;
+}
+
+void
+internal_switch_mncc(call, msg)
+	struct gsm_call *call;
+	struct gsm_mncc *msg;
+{
+	switch (msg->msg_type) {
+	case MNCC_SETUP_CNF:
+		handle_setup_cnf(call, msg);
+		return;
+	case MNCC_ALERT_IND:
+		forward_to_remote(call, msg, MNCC_ALERT_REQ);
+		return;
+	case MNCC_NOTIFY_IND:
+		forward_to_remote(call, msg, MNCC_NOTIFY_REQ);
+		return;
+	case MNCC_USERINFO_IND:
+		forward_to_remote(call, msg, MNCC_USERINFO_REQ);
+		return;
+	case MNCC_DISC_IND:
+		handle_disconnect(call, msg);
+		return;
+	case MNCC_START_DTMF_IND:
+		msg->msg_type = MNCC_START_DTMF_REJ;
+		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_STOP_DTMF_IND:
+		msg->msg_type = MNCC_STOP_DTMF_RSP;
+		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_MODIFY_IND:
+		msg->msg_type = MNCC_MODIFY_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_HOLD_IND:
+		msg->msg_type = MNCC_HOLD_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_RETRIEVE_IND:
+		msg->msg_type = MNCC_RETRIEVE_REJ;
+		mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		send_mncc_to_gsm(msg, sizeof(struct gsm_mncc));
+		return;
+	case MNCC_SETUP_COMPL_IND:
+	case MNCC_CALL_CONF_IND:
+		/* no handling needed */
+		return;
+	case MNCC_REL_IND:
+	case MNCC_REJ_IND:
+		handle_release(call, msg);
+		return;
+	case MNCC_REL_CNF:
+		call->gc_flag = 1;
+		return;
+	default:
+		syslog(LOG_ERR,
+			"MNCC message type 0x%x unhandled for internal switch",
+			msg->msg_type);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/main.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,80 @@
+/*
+ * Main module for ThemWi MNCC daemon.
+ */
+
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <signal.h>
+#include <syslog.h>
+#include <unistd.h>
+#include "struct.h"
+
+extern int mncc_socket;
+extern int mtcall_listener;
+extern struct socket_conn *mtcall_socket_head;
+
+static int max_fd;
+
+update_max_fd(newfd)
+{
+	if (newfd > max_fd)
+		max_fd = newfd;
+}
+
+main(argc, argv)
+	char **argv;
+{
+	fd_set fds;
+	struct socket_conn *conn, **connp;
+	int c;
+
+	openlog("themwi-mncc", 0, LOG_LOCAL5);
+	if (read_number_db() < 0) {
+		fprintf(stderr, "error reading number database\n");
+		exit(1);
+	}
+	if (open_mncc_socket() < 0) {
+		fprintf(stderr, "error connecting to GSM MNCC socket\n");
+		exit(1);
+	}
+	if (create_mtcall_socket() < 0) {
+		fprintf(stderr, "error creating MT call socket\n");
+		exit(1);
+	}
+	signal(SIGPIPE, SIG_IGN);
+	/* main select loop */
+	for (;;) {
+		FD_ZERO(&fds);
+		FD_SET(mncc_socket, &fds);
+		FD_SET(mtcall_listener, &fds);
+		for (connp = &mtcall_socket_head; conn = *connp; ) {
+			if (conn->fd < 0) {
+				*connp = conn->next;
+				free(conn);
+				continue;
+			}
+			FD_SET(conn->fd, &fds);
+			connp = &conn->next;
+		}
+		c = select(max_fd+1, &fds, 0, 0, 0);
+		if (c < 0) {
+			if (errno == EINTR)
+				continue;
+			syslog(LOG_CRIT, "select: %m");
+			exit(1);
+		}
+		if (FD_ISSET(mncc_socket, &fds))
+			mncc_socket_select();
+		if (FD_ISSET(mtcall_listener, &fds))
+			mtsock_accept_handler();
+		for (conn = mtcall_socket_head; conn; conn = conn->next)
+			if (FD_ISSET(conn->fd, &fds))
+				extsock_read_select(conn);
+		gc_call_list();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/mncc_recv.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,188 @@
+/*
+ * In this module we implement initial handling of MNCC messages
+ * coming from OsmoMSC, dispatching them further as appropriate.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "struct.h"
+#include "gsm_call.h"
+
+static void
+report_runt(msg)
+	union mncc_msg *msg;
+{
+	syslog(LOG_CRIT, "MNCC message type 0x%x from GSM is too short!",
+		msg->msg_type);
+}
+
+static void
+handle_setup_ind(msg, msglen)
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_gsm_callref(msg->callref);
+	if (call) {
+		syslog(LOG_ERR, "duplicate MNCC_SETUP_IND for callref 0x%x",
+			msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* further processing */
+	process_mo_call_setup(msg);
+}
+
+static void
+handle_signaling_msg(msg, msglen)
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_gsm_callref(msg->callref);
+	if (!call) {
+		syslog(LOG_ERR,
+			"MNCC message type 0x%x: callref 0x%x not found",
+			msg->msg_type, msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	if (msg->msg_type == MNCC_SETUP_CNF)
+		preen_connected_number(msg);
+	/* dispatch according to internal switch or socket */
+	if (call->socket)
+		mncc_signal_to_socket(call, msg);
+	else
+		internal_switch_mncc(call, msg);
+}
+
+static void
+handle_release_msg(msg, msglen)
+	struct gsm_mncc *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_gsm_callref(msg->callref);
+	if (!call) {
+		syslog(LOG_ERR,
+			"MNCC message type 0x%x: callref 0x%x not found",
+			msg->msg_type, msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* dispatch according to internal switch or socket */
+	if (call->socket) {
+		mncc_signal_to_socket(call, msg);
+		extsock_dec_refcount(call->socket);
+		call->gc_flag = 1;
+	} else
+		internal_switch_mncc(call, msg);
+}
+
+static void
+handle_rtp_msg(msg, msglen)
+	struct gsm_mncc_rtp *msg;
+	unsigned msglen;
+{
+	struct gsm_call *call;
+
+	if (msglen < sizeof(struct gsm_mncc_rtp)) {
+		report_runt(msg);
+		return;
+	}
+	call = find_gsm_callref(msg->callref);
+	if (!call) {
+		syslog(LOG_ERR,
+			"MNCC message type 0x%x: callref 0x%x not found",
+			msg->msg_type, msg->callref);
+		/* drop it like OsmoMSC's mncc_builtin does */
+		return;
+	}
+	/* only for socket connections - no RTP handling for internal */
+	if (call->socket)
+		mncc_rtp_to_socket(call, msg);
+}
+
+static void
+handle_mncc_hello(msg, msglen)
+	struct gsm_mncc_hello *msg;
+	unsigned msglen;
+{
+	if (msglen < sizeof(struct gsm_mncc_hello)) {
+		syslog(LOG_CRIT, "MNCC_SOCKET_HELLO message is too short!");
+		exit(1);
+	}
+	if (msg->version != MNCC_SOCK_VERSION) {
+		syslog(LOG_CRIT, "MNCC hello error: version number mismatch");
+		exit(1);
+	}
+	if (msg->mncc_size != sizeof(struct gsm_mncc)) {
+		syslog(LOG_CRIT, "MNCC hello error: mncc_size mismatch");
+		exit(1);
+	}
+}
+
+void
+mncc_msg_from_gsm(msg, msglen)
+	union mncc_msg *msg;
+	unsigned msglen;
+{
+	switch (msg->msg_type) {
+	case MNCC_SETUP_IND:
+		handle_setup_ind(msg, msglen);
+		return;
+	case MNCC_SETUP_CNF:
+	case MNCC_SETUP_COMPL_IND:
+	case MNCC_CALL_CONF_IND:
+	case MNCC_ALERT_IND:
+	case MNCC_NOTIFY_IND:
+	case MNCC_DISC_IND:
+	case MNCC_FACILITY_IND:
+	case MNCC_START_DTMF_IND:
+	case MNCC_STOP_DTMF_IND:
+	case MNCC_MODIFY_IND:
+	case MNCC_MODIFY_CNF:
+	case MNCC_MODIFY_REJ:
+	case MNCC_HOLD_IND:
+	case MNCC_RETRIEVE_IND:
+	case MNCC_USERINFO_IND:
+		handle_signaling_msg(msg, msglen);
+		return;
+	case MNCC_REL_IND:
+	case MNCC_REL_CNF:
+	case MNCC_REJ_IND:
+		handle_release_msg(msg, msglen);
+		return;
+	case MNCC_RTP_CREATE:
+	case MNCC_RTP_CONNECT:
+	case MNCC_RTP_FREE:
+		handle_rtp_msg(msg, msglen);
+		return;
+	case MNCC_SOCKET_HELLO:
+		handle_mncc_hello(msg, msglen);
+		return;
+	default:
+		syslog(LOG_CRIT, "unknown MNCC message type 0x%x from GSM",
+			msg->msg_type);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/mncc_sock.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,63 @@
+/*
+ * In this module we implement low-level handling
+ * of the main MNCC socket that connects to OsmoMSC.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+
+static char mncc_socket_pathname[] = "/var/gsm/mncc_socket";
+
+int mncc_socket;
+
+open_mncc_socket()
+{
+	struct sockaddr_un sa;
+	unsigned sa_len;
+	int rc;
+
+	mncc_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (mncc_socket < 0) {
+		syslog(LOG_CRIT, "socket(AF_UNIX, SOCK_SEQPACKET, 0): %m");
+		return(-1);
+	}
+	fill_sockaddr_un(mncc_socket_pathname, &sa, &sa_len);
+	rc = connect(mncc_socket, (struct sockaddr *) &sa, sa_len);
+	if (rc < 0) {
+		syslog(LOG_ERR, "connect to %s: %m", mncc_socket_pathname);
+		return(-1);
+	}
+	update_max_fd(mncc_socket);
+	return(0);
+}
+
+void
+mncc_socket_select()
+{
+	union mncc_msg msg;
+	int rc;
+
+	rc = recv(mncc_socket, &msg, sizeof msg, 0);
+	if (rc < 0) {
+		syslog(LOG_CRIT, "error reading from MNCC socket: %m");
+		exit(1);
+	}
+	if (rc < 4) {
+		syslog(LOG_CRIT, "short read from MNCC socket: %d bytes", rc);
+		exit(1);
+	}
+	mncc_msg_from_gsm(&msg, rc);
+}
+
+send_mncc_to_gsm(msg, msglen)
+	union mncc_msg *msg;
+	unsigned msglen;
+{
+	return send(mncc_socket, msg, msglen, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/mtsock.c	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,75 @@
+/*
+ * In this module we implement the MT call socket
+ * to which other ThemWi system sw components connect
+ * in order to send MT calls toward the GSM network.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "../include/mncc.h"
+#include "struct.h"
+
+static char mtcall_socket_pathname[] = "/var/gsm/mtcall_socket";
+
+int mtcall_listener;
+struct socket_conn *mtcall_socket_head;
+
+create_mtcall_socket()
+{
+	struct sockaddr_un sa;
+	unsigned sa_len;
+	int rc;
+
+	mtcall_listener = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+	if (mtcall_listener < 0) {
+		syslog(LOG_CRIT, "socket(AF_UNIX, SOCK_SEQPACKET, 0): %m");
+		return(-1);
+	}
+	unlink(mtcall_socket_pathname);
+	fill_sockaddr_un(mtcall_socket_pathname, &sa, &sa_len);
+	rc = bind(mtcall_listener, (struct sockaddr *) &sa, sa_len);
+	if (rc < 0) {
+		syslog(LOG_ERR, "bind to %s: %m", mtcall_socket_pathname);
+		return(-1);
+	}
+	rc = listen(mtcall_listener, 3);
+	if (rc < 0) {
+		syslog(LOG_CRIT, "listen on UNIX socket: %m");
+		return(-1);
+	}
+	update_max_fd(mtcall_listener);
+	return(0);
+}
+
+void
+mtsock_accept_handler()
+{
+	struct sockaddr_un sa;
+	socklen_t sa_len;
+	int fd;
+	struct socket_conn *conn;
+
+	fd = accept(mtcall_listener, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		syslog(LOG_CRIT, "accept on UNIX socket: %m");
+		exit(1);
+	}
+	conn = malloc(sizeof(struct socket_conn));
+	if (!conn) {
+		syslog(LOG_CRIT, "malloc for mtcall socket conn: %m");
+		close(fd);
+		return;
+	}
+	bzero(conn, sizeof(struct socket_conn));
+	conn->fd = fd;
+	conn->next = mtcall_socket_head;
+	mtcall_socket_head = conn;
+	update_max_fd(fd);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mncc/struct.h	Sun Jun 26 16:31:47 2022 -0800
@@ -0,0 +1,29 @@
+/*
+ * This header file defines internal data structures
+ * for ThemWi MNCC daemon, talking to OsmoMSC.
+ */
+
+#ifndef	__STRUCT_H
+#define	__STRUCT_H
+
+struct socket_conn {
+	int	fd;
+	unsigned ncalls;
+	struct socket_conn *next;
+};
+
+/* GSM call leg on MNCC-MSC side, either MO or MT */
+struct gsm_call {
+	/* always present */
+	uint32_t callref;
+	/* only for internal switching */
+	struct gsm_call *other_leg;
+	/* only for external calls */
+	struct socket_conn *socket;
+	uint32_t socket_ref;
+	/* linked list management */
+	int gc_flag;
+	struct gsm_call *next;
+};
+
+#endif	/* include guard */