sgsn: Handle Detach Requests even when there is no mmctx

Currently, when a Detach Request is received with an unknown TLLI,
it is answered by another Detach Request (!), even when a power_off
Type is used.

This patch uses gsm48_rx_gmm_det_req to handle the message instead.
So this function is changed to cope with a NULL mmctx. In that case
it doesn't unassign the llme, so this must be done manually
afterwards.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index 5c82227..73731fe 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -114,7 +114,8 @@
 };
 
 #define LOGMMCTXP(level, mm, fmt, args...) \
-	LOGP(DMM, level, "MM(%s/%08x) " fmt, (mm)->imsi, (mm)->p_tmsi, ## args)
+	LOGP(DMM, level, "MM(%s/%08x) " fmt, (mm) ? (mm)->imsi : "---", \
+	     (mm) ? (mm)->p_tmsi : GSM_RESERVED_TMSI, ## args)
 
 /* look-up a SGSN MM context based on TLLI + RAI */
 struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index ff852eb..4a71ba4 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -497,14 +497,14 @@
 }
 
 /* Chapter 9.4.6.2 Detach accept */
-static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby)
+static int _tx_detach_ack(struct msgb *msg, uint8_t force_stby,
+			  struct sgsn_mm_ctx *mm)
 {
-	struct msgb *msg = gsm48_msgb_alloc();
 	struct gsm48_hdr *gh;
 
-	LOGMMCTXP(LOGL_INFO, mm, "<- GPRS DETACH ACCEPT\n");
+	/* MMCTX might be NULL! */
 
-	mmctx2msgid(msg, mm);
+	DEBUGP(DMM, "<- GPRS MM DETACH ACC (force-standby: %d)\n", force_stby);
 
 	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
 	gh->proto_discr = GSM48_PDISC_MM_GPRS;
@@ -514,6 +514,22 @@
 	return gsm48_gmm_sendmsg(msg, 0, mm);
 }
 
+static int gsm48_tx_gmm_det_ack(struct sgsn_mm_ctx *mm, uint8_t force_stby)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	mmctx2msgid(msg, mm);
+	return _tx_detach_ack(msg, force_stby, mm);
+}
+
+static int gsm48_tx_gmm_det_ack_oldmsg(struct msgb *oldmsg, uint8_t force_stby)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+
+	gmm_copy_id(msg, oldmsg);
+	return _tx_detach_ack(msg, force_stby, NULL);
+}
+
 /* Transmit Chapter 9.4.12 Identity Request */
 static int gsm48_tx_gmm_id_req(struct sgsn_mm_ctx *mm, uint8_t id_type)
 {
@@ -864,7 +880,7 @@
 {
 	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg);
 	uint8_t detach_type, power_off;
-	int rc;
+	int rc = 0;
 
 	detach_type = gh->data[0] & 0x7;
 	power_off = gh->data[0] & 0x8;
@@ -875,14 +891,18 @@
 		msgb_tlli(msg), get_value_string(gprs_det_t_mo_strs, detach_type),
 		power_off ? "Power-off" : "");
 
-	/* Only sent the Detach Accept (MO) if power off isn't indicated,
+	/* Only send the Detach Accept (MO) if power off isn't indicated,
 	 * see 04.08, 4.7.4.1.2/3 for details */
 	if (!power_off) {
 		/* force_stby = 0 */
-		rc = gsm48_tx_gmm_det_ack(ctx, 0);
+		if (ctx)
+			rc = gsm48_tx_gmm_det_ack(ctx, 0);
+		else
+			rc = gsm48_tx_gmm_det_ack_oldmsg(msg, 0);
 	}
 
-	mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST");
+	if (ctx)
+		mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST");
 
 	return rc;
 }
@@ -1117,9 +1137,8 @@
 
 		/* Don't force it into re-attachment */
 		if (gh->msg_type == GSM48_MT_GMM_DETACH_REQ) {
-			rc = gsm48_tx_gmm_detach_req_oldmsg(
-				msg, GPRS_DET_T_MT_REATT_NOTREQ,
-				GMM_CAUSE_IMPL_DETACHED);
+			/* Handle Detach Request */
+			rc = gsm48_rx_gmm_det_req(NULL, msg);
 
 			/* TLLI unassignment */
 			gprs_llgmm_assign(llme, llme->tlli, 0xffffffff,
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index cb3c294..05d1ee0 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -221,6 +221,41 @@
 	OSMO_ASSERT(!ictx);
 }
 
+/*
+ * Test that a GMM Detach will remove the associated LLME if there is no MMCTX.
+ */
+static void test_gmm_detach_no_mmctx(void)
+{
+	struct gprs_llc_lle *lle;
+	uint32_t local_tlli;
+	struct msgb *msg;
+
+	printf("Testing GMM detach (no MMCTX)\n");
+
+	/* DTAP - Detach Request (MO) */
+	/* normal detach, power_off = 0 */
+	static const unsigned char detach_req[] = {
+		0x08, 0x05, 0x01, 0x18, 0x05, 0xf4, 0xef, 0xe2,
+		0xb7, 0x00, 0x19, 0x03, 0xb9, 0x97, 0xcb
+	};
+
+	/* Create an LLME  */
+	OSMO_ASSERT(count(gprs_llme_list()) == 0);
+	local_tlli = gprs_tmsi2tlli(0x23, TLLI_LOCAL);
+	lle = gprs_lle_get_or_create(local_tlli, 3);
+
+	OSMO_ASSERT(count(gprs_llme_list()) == 1);
+
+	/* inject the detach */
+	msg = create_msg(detach_req, ARRAY_SIZE(detach_req));
+	msgb_tlli(msg) = local_tlli;
+	gsm0408_gprs_rcvmsg(msg, lle->llme);
+	msgb_free(msg);
+
+	/* verify that the LLME is gone */
+	OSMO_ASSERT(count(gprs_llme_list()) == 0);
+}
+
 static struct log_info_cat gprs_categories[] = {
 	[DMM] = {
 		.name = "DMM",
@@ -285,6 +320,7 @@
 	test_llme();
 	test_gmm_detach();
 	test_gmm_detach_power_off();
+	test_gmm_detach_no_mmctx();
 	printf("Done\n");
 	return 0;
 }
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 4c475d9..bca2b4b 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -1,4 +1,5 @@
 Testing LLME allocations
 Testing GMM detach
 Testing GMM detach (power off)
+Testing GMM detach (no MMCTX)
 Done