nat: Change MGCP DLCX handling and send dummy MDCX to the BTS.

When setting a new MSC timeslot to a SCCP connection check if
any of the existing connections have this timeslot, if so we will
send a DLCX down the stream to make sure it is closed there, when
we will CRCX this new timeslot we will happily reallocate it.

When the SCCP connection goes away, or we get a DLCX from the
network, or the BSC is gone we will send a DLCX message down the
stream as well.

When we receive a CRCX from the network we will forward the CRCX
as usual and send a dummy MDCX after it.

For the DLCX and the dummy MDCX we send a custom MGCP message
that will not provoke an answer. Even if the downstream MGCP GW
will answer we will ignore it due the dummy transaction id that
is not used anywhere else.

This change should make sure that we close the dowstream endpoint
all the time, even when the DLCX arrives after the SCCP connection
is torndown.
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index 353cf08..6ef1f59 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -162,8 +162,6 @@
 	char *transaction_id;
 	/* the bsc we are talking to */
 	struct bsc_connection *bsc;
-	/* pending delete */
-	int pending_delete;
 };
 
 /**
@@ -257,10 +255,10 @@
  */
 int bsc_write_mgcp(struct bsc_connection *bsc, const u_int8_t *data, unsigned int length);
 int bsc_mgcp_assign(struct sccp_connections *, struct msgb *msg);
-void bsc_mgcp_clear(struct sccp_connections *);
-void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int);
+void bsc_mgcp_init(struct sccp_connections *);
+void bsc_mgcp_dlcx(struct sccp_connections *);
 void bsc_mgcp_free_endpoints(struct bsc_nat *nat);
-int bsc_mgcp_init(struct bsc_nat *nat);
+int bsc_mgcp_nat_init(struct bsc_nat *nat);
 
 struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *, int endpoint_number);
 struct msgb *bsc_mgcp_rewrite(char *input, int length, const char *ip, int port);
diff --git a/openbsc/src/nat/bsc_mgcp_utils.c b/openbsc/src/nat/bsc_mgcp_utils.c
index 70f7509..7bba413 100644
--- a/openbsc/src/nat/bsc_mgcp_utils.c
+++ b/openbsc/src/nat/bsc_mgcp_utils.c
@@ -26,6 +26,8 @@
 #include <openbsc/mgcp.h>
 #include <openbsc/mgcp_internal.h>
 
+#include <sccp/sccp.h>
+
 #include <osmocore/talloc.h>
 #include <osmocore/gsm0808.h>
 
@@ -37,10 +39,12 @@
 
 int bsc_mgcp_assign(struct sccp_connections *con, struct msgb *msg)
 {
+	struct sccp_connections *mcon;
 	struct tlv_parsed tp;
 	u_int16_t cic;
 	u_int8_t timeslot;
 	u_int8_t multiplex;
+	int combined;
 
 	if (!msg->l3h) {
 		LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n");
@@ -62,18 +66,27 @@
 	timeslot = cic & 0x1f;
 	multiplex = (cic & ~0x1f) >> 5;
 
-	con->msc_timeslot = (32 * multiplex) + timeslot;
+
+	combined = (32 * multiplex) + timeslot;
+
+	/* find stale connections using that endpoint */
+	llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) {
+		if (mcon->msc_timeslot == combined) {
+			LOGP(DNAT, LOGL_ERROR,
+			     "Timeslot %d was assigned to 0x%x and now 0x%x\n",
+			     combined,
+			     sccp_src_ref_to_int(&mcon->patched_ref),
+			     sccp_src_ref_to_int(&con->patched_ref));
+			bsc_mgcp_dlcx(mcon);
+		}
+	}
+
+	con->msc_timeslot = combined;
 	con->bsc_timeslot = con->msc_timeslot;
 	return 0;
 }
 
-void bsc_mgcp_clear(struct sccp_connections *con)
-{
-	con->msc_timeslot = -1;
-	con->bsc_timeslot = -1;
-}
-
-void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
+static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
 {
 	if (nat->bsc_endpoints[i].transaction_id) {
 		talloc_free(nat->bsc_endpoints[i].transaction_id);
@@ -81,17 +94,74 @@
 	}
 
 	nat->bsc_endpoints[i].bsc = NULL;
-	mgcp_free_endp(&nat->mgcp_cfg->endpoints[i]);
 }
 
 void bsc_mgcp_free_endpoints(struct bsc_nat *nat)
 {
 	int i;
 
-	for (i = 1; i < nat->mgcp_cfg->number_endpoints; ++i)
+	for (i = 1; i < nat->mgcp_cfg->number_endpoints; ++i){
 		bsc_mgcp_free_endpoint(nat, i);
+		mgcp_free_endp(&nat->mgcp_cfg->endpoints[i]);
+	}
 }
 
+/* send a MDCX where we do not want a response */
+static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, struct mgcp_endpoint *endp)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+		       "MDCX 23 %d@mgw MGCP 1.0\r\n"
+		       "Z: noanswer\r\n"
+		       "\r\n"
+		       "c=IN IP4 %s\r\n"
+		       "m=audio %d RTP/AVP 255\r\n",
+		       ENDPOINT_NUMBER(endp),
+		       bsc->nat->mgcp_cfg->source_addr,
+		       endp->rtp_port);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+		return;
+	}
+}
+
+static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint)
+{
+	char buf[2096];
+	int len;
+
+	len = snprintf(buf, sizeof(buf),
+		       "DLCX 23 %d@mgw MGCP 1.0\r\n"
+		       "Z: noanswer\r\n", endpoint);
+	if (len < 0) {
+		LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+		return;
+	}
+
+	bsc_write_mgcp(bsc, (u_int8_t *) buf, len);
+}
+
+void bsc_mgcp_init(struct sccp_connections *con)
+{
+	con->msc_timeslot = -1;
+	con->bsc_timeslot = -1;
+}
+
+void bsc_mgcp_dlcx(struct sccp_connections *con)
+{
+	/* send a DLCX down the stream */
+	if (con->bsc_timeslot != -1) {
+		int endp = mgcp_timeslot_to_endpoint(0, con->msc_timeslot);
+		bsc_mgcp_send_dlcx(con->bsc, endp);
+		bsc_mgcp_free_endpoint(con->bsc->nat, endp);
+	}
+
+	bsc_mgcp_init(con);
+}
+
+
 struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint)
 {
 	struct sccp_connections *con = NULL;
@@ -164,7 +234,6 @@
 
 	bsc_endp->transaction_id = talloc_strdup(nat, transaction_id);
 	bsc_endp->bsc = sccp->bsc;
-	bsc_endp->pending_delete = 0;
 
 	/* we need to update some bits */
 	if (state == MGCP_ENDP_CRCX) {
@@ -176,15 +245,19 @@
 		} else {
 			mgcp_endp->bts = sock.sin_addr;
 		}
-	} else if (state == MGCP_ENDP_DLCX) {
-		/* we will free the endpoint now in case the BSS does not respond */
-		bsc_mgcp_clear(sccp);
-		bsc_endp->pending_delete = 1;
-		mgcp_free_endp(mgcp_endp);
-	}
 
-	bsc_write(sccp->bsc, bsc_msg, NAT_IPAC_PROTO_MGCP);
-	return MGCP_POLICY_DEFER;
+		/* send the message and a fake MDCX for force sending of a dummy packet */
+		bsc_write(sccp->bsc, bsc_msg, NAT_IPAC_PROTO_MGCP);
+		bsc_mgcp_send_mdcx(sccp->bsc, mgcp_endp);
+		return MGCP_POLICY_DEFER;
+	} else if (state == MGCP_ENDP_DLCX) {
+		/* we will free the endpoint now and send a DLCX to the BSC */
+		bsc_mgcp_dlcx(sccp);
+		return MGCP_POLICY_CONT;
+	} else {
+		bsc_write(sccp->bsc, bsc_msg, NAT_IPAC_PROTO_MGCP);
+		return MGCP_POLICY_DEFER;
+	}
 }
 
 /*
@@ -234,13 +307,7 @@
 		return;
 	}
 
-	/* make it point to our endpoint if it was not deleted */
-	if (bsc_endp->pending_delete) {
-		bsc_endp->bsc = NULL;
-		bsc_endp->pending_delete = 0;
-	} else {
-		endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
-	}
+	endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
 
 	/* free some stuff */
 	talloc_free(bsc_endp->transaction_id);
@@ -401,7 +468,7 @@
 	return rc;
 }
 
-int bsc_mgcp_init(struct bsc_nat *nat)
+int bsc_mgcp_nat_init(struct bsc_nat *nat)
 {
 	int on;
 	struct sockaddr_in addr;
@@ -481,11 +548,7 @@
 		if (bsc_endp->bsc != bsc)
 			continue;
 
-		bsc_endp->bsc = NULL;
-		bsc_endp->pending_delete = 0;
-		if (bsc_endp->transaction_id)
-			talloc_free(bsc_endp->transaction_id);
-		bsc_endp->transaction_id = NULL;
+		bsc_mgcp_free_endpoint(bsc->nat, i);
 		mgcp_free_endp(&bsc->nat->mgcp_cfg->endpoints[i]);
 	}
 }
diff --git a/openbsc/src/nat/bsc_nat.c b/openbsc/src/nat/bsc_nat.c
index 20ddec2..b047c3b 100644
--- a/openbsc/src/nat/bsc_nat.c
+++ b/openbsc/src/nat/bsc_nat.c
@@ -861,7 +861,7 @@
 	/*
 	 * Setup the MGCP code..
 	 */
-	if (bsc_mgcp_init(nat) != 0)
+	if (bsc_mgcp_nat_init(nat) != 0)
 		return -4;
 
 	/* connect to the MSC */
diff --git a/openbsc/src/nat/bsc_nat_utils.c b/openbsc/src/nat/bsc_nat_utils.c
index 1365427..d02e6cd 100644
--- a/openbsc/src/nat/bsc_nat_utils.c
+++ b/openbsc/src/nat/bsc_nat_utils.c
@@ -100,7 +100,7 @@
 	LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n",
 	     sccp_src_ref_to_int(&conn->real_ref),
 	     sccp_src_ref_to_int(&conn->patched_ref), conn->bsc);
-	bsc_mgcp_clear(conn);
+	bsc_mgcp_dlcx(conn);
 	llist_del(&conn->list_entry);
 	talloc_free(conn);
 }
diff --git a/openbsc/src/nat/bsc_sccp.c b/openbsc/src/nat/bsc_sccp.c
index 58302cc..c422cc5 100644
--- a/openbsc/src/nat/bsc_sccp.c
+++ b/openbsc/src/nat/bsc_sccp.c
@@ -101,7 +101,7 @@
 			talloc_free(conn);
 			return -1;
 		} else {
-			bsc_mgcp_clear(conn);
+			bsc_mgcp_dlcx(conn);
 			return 0;
 		}
 	}
@@ -121,7 +121,7 @@
 		return -1;
 	}
 
-	bsc_mgcp_clear(conn);
+	bsc_mgcp_init(conn);
 	llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections);
 	counter_inc(bsc->cfg->stats.sccp.conn);
 	counter_inc(bsc->cfg->nat->stats.sccp.conn);
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
index e046e77..24b9601 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.c
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -395,6 +395,8 @@
 
 static void test_mgcp_ass_tracking(void)
 {
+	struct bsc_connection *bsc;
+	struct bsc_nat *nat;
 	struct sccp_connections con;
 	struct bsc_nat_parsed *parsed;
 	struct msgb *msg;
@@ -402,6 +404,14 @@
 	fprintf(stderr, "Testing MGCP.\n");
 	memset(&con, 0, sizeof(con));
 
+	nat = bsc_nat_alloc();
+	nat->bsc_endpoints = talloc_zero_array(nat,
+					       struct bsc_endpoint,
+					       33);
+	bsc = bsc_connection_alloc(nat);
+	bsc->cfg = bsc_config_alloc(nat, "foo", 2323);
+	con.bsc = bsc;
+
 	msg = msgb_alloc(4096, "foo");
 	copy_to_msg(msg, ass_cmd, sizeof(ass_cmd));
 	parsed = bsc_nat_parse(msg);
@@ -421,11 +431,13 @@
 	}
 	talloc_free(parsed);
 
-	bsc_mgcp_clear(&con);
+	bsc_mgcp_dlcx(&con);
 	if (con.bsc_timeslot != -1 || con.msc_timeslot != -1) {
 		fprintf(stderr, "Clearing should remove the mapping.\n");
 		abort();
 	}
+
+	talloc_free(nat);
 }
 
 /* test the code to find a given connection */