sgsn: Cleanup after RA Update Reject / Attach Reject

Currently, the LLME is not cleaned up after sending an RA Update
Reject. This happens after entering a routing area from outside,
since in that case the SGSN sends an RA Update Reject (implicitly
detached) which causes the MS to restart the attach procedure.
The LLME is also not updated if an Attach Request with message errors
(encoding, invalid MI type) is received or if an MM context cannot be
allocated.

This patch changes gsm48_rx_gmm_ra_upd_req and gsm48_rx_gmm_att_req
to unassign the LLME or free the MM context (if available) after a
Reject message has been sent.

Ticket: OW#1324
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index a966949..a2c30be 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -803,6 +803,8 @@
 	char mi_string[GSM48_MI_SIZE];
 	struct gprs_ra_id ra_id;
 	uint16_t cid;
+	enum gsm48_gmm_cause reject_cause;
+	int rc;
 
 	LOGP(DMM, LOGL_INFO, "-> GMM ATTACH REQUEST ");
 
@@ -863,8 +865,10 @@
 			return gsm48_tx_gmm_att_rej(msg, GMM_CAUSE_IMSI_UNKNOWN);
 #else
 			ctx = sgsn_mm_ctx_alloc(0, &ra_id);
-			if (!ctx)
-				return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_NET_FAIL);
+			if (!ctx) {
+				reject_cause = GMM_CAUSE_NET_FAIL;
+				goto rejected;
+			}
 			strncpy(ctx->imsi, mi_string, sizeof(ctx->imsi));
 #endif
 		}
@@ -891,7 +895,8 @@
 	default:
 		LOGP(DMM, LOGL_NOTICE, "Rejecting ATTACH REQUEST with "
 			"MI type %u\n", mi_type);
-		return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_MS_ID_NOT_DERIVED);
+		reject_cause = GMM_CAUSE_MS_ID_NOT_DERIVED;
+		goto rejected;
 	}
 	/* Update MM Context with currient RA and Cell ID */
 	ctx->ra = ra_id;
@@ -923,7 +928,22 @@
 
 err_inval:
 	LOGPC(DMM, LOGL_INFO, "\n");
-	return gsm48_tx_gmm_att_rej_oldmsg(msg, GMM_CAUSE_SEM_INCORR_MSG);
+	reject_cause = GMM_CAUSE_SEM_INCORR_MSG;
+
+rejected:
+	/* Send ATTACH REJECT */
+	LOGMMCTXP(LOGL_NOTICE, ctx,
+		  "Rejecting Attach Request with cause '%s' (%d)\n",
+		  get_value_string(gmm_cause_names, reject_cause), reject_cause);
+	rc = gsm48_tx_gmm_att_rej_oldmsg(msg, reject_cause);
+	if (ctx)
+		mm_ctx_cleanup_free(ctx, "GPRS ATTACH REJ");
+	else
+		/* TLLI unassignment */
+		gprs_llgmm_assign(llme, llme->tlli, 0xffffffff, GPRS_ALGO_GEA0, NULL);
+
+	return rc;
+
 }
 
 /* Section 4.7.4.1 / 9.4.5.2 MO Detach request */
@@ -1061,6 +1081,8 @@
 	struct gprs_ra_id old_ra_id;
 	struct tlv_parsed tp;
 	uint8_t upd_type;
+	enum gsm48_gmm_cause reject_cause;
+	int rc;
 
 	/* Update Type 10.5.5.18 */
 	upd_type = *cur++ & 0x0f;
@@ -1074,8 +1096,10 @@
 
 	/* MS Radio Access Capability 10.5.5.12a */
 	ms_ra_acc_cap_len = *cur++;
-	if (ms_ra_acc_cap_len > 52)
-		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC);
+	if (ms_ra_acc_cap_len > 52) {
+		reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC;
+		goto rejected;
+	}
 	cur += ms_ra_acc_cap_len;
 
 	/* Optional: Old P-TMSI Signature, Requested READY timer, TMSI Status,
@@ -1087,8 +1111,8 @@
 	case GPRS_UPD_T_RA_LA:
 	case GPRS_UPD_T_RA_LA_IMSI_ATT:
 		LOGP(DMM, LOGL_NOTICE, "Update type %i unsupported in Mode III, is your SI13 corrupt?\n", upd_type);
-		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_PROTO_ERR_UNSPEC);
-		break;
+		reject_cause = GMM_CAUSE_PROTO_ERR_UNSPEC;
+		goto rejected;
 	case GPRS_UPD_T_RA:
 	case GPRS_UPD_T_PERIODIC:
 		break;
@@ -1104,7 +1128,8 @@
 		/* The MS has to perform GPRS attach */
 		/* Device is still IMSI attached for CS but initiate GPRS ATTACH,
 		 * see GSM 04.08, 4.7.5.1.4 and G.6 */
-		return gsm48_tx_gmm_ra_upd_rej(msg, GMM_CAUSE_IMPL_DETACHED);
+		reject_cause = GMM_CAUSE_IMPL_DETACHED;
+		goto rejected;
 	}
 
 	/* Store new BVCI/NSEI in MM context (FIXME: delay until we ack?) */
@@ -1150,6 +1175,21 @@
 
 	/* Send RA UPDATE ACCEPT */
 	return gsm48_tx_gmm_ra_upd_ack(mmctx);
+
+rejected:
+	/* Send RA UPDATE REJECT */
+	LOGMMCTXP(LOGL_NOTICE, mmctx,
+		  "Rejecting RA Update Request with cause '%s' (%d)\n",
+		  get_value_string(gmm_cause_names, reject_cause), reject_cause);
+	rc = gsm48_tx_gmm_ra_upd_rej(msg, reject_cause);
+	if (mmctx)
+		mm_ctx_cleanup_free(mmctx, "GPRS RA UPDATE REJ");
+	else
+		/* TLLI unassignment */
+		gprs_llgmm_assign(llme, llme->tlli, 0xffffffff, GPRS_ALGO_GEA0,
+				  NULL);
+
+	return rc;
 }
 
 static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg)
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 00259a5..1756888 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -404,6 +404,148 @@
 	sgsn_acl_del("123456789012345", &sgsn->cfg);
 }
 
+/*
+ * Test the GMM Rejects
+ */
+static void test_gmm_reject(void)
+{
+	struct gprs_ra_id raid = { 0, };
+	struct sgsn_mm_ctx *ctx = NULL;
+	uint32_t foreign_tlli;
+	struct gprs_llc_lle *lle;
+	int idx;
+
+	/* DTAP - Attach Request */
+	/* Invalid MI length */
+	static const unsigned char attach_req_inv_mi_len[] = {
+		0x08, 0x01, 0x02, 0xf5, 0xe0, 0x21, 0x08, 0x02, 0x09, 0xf4,
+		0xfb, 0xc5, 0x46, 0x79, 0xff, 0xff, 0xff, 0xff, 0x11, 0x22,
+		0x33, 0x40, 0x50, 0x60, 0x19, 0x18, 0xb3, 0x43, 0x2b, 0x25,
+		0x96, 0x62, 0x00, 0x60, 0x80, 0x9a, 0xc2, 0xc6, 0x62, 0x00,
+		0x60, 0x80, 0xba, 0xc8, 0xc6, 0x62, 0x00, 0x60, 0x80, 0x00
+	};
+
+	/* DTAP - Attach Request */
+	/* Invalid MI type (IMEI) */
+	static const unsigned char attach_req_inv_mi_type[] = {
+		0x08, 0x01, 0x02, 0xf5, 0xe0, 0x21, 0x08, 0x02, 0x05, 0xf2,
+		0xfb, 0xc5, 0x46, 0x79, 0x11, 0x22, 0x33, 0x40, 0x50, 0x60,
+		0x19, 0x18, 0xb3, 0x43, 0x2b, 0x25, 0x96, 0x62, 0x00, 0x60,
+		0x80, 0x9a, 0xc2, 0xc6, 0x62, 0x00, 0x60, 0x80, 0xba, 0xc8,
+		0xc6, 0x62, 0x00, 0x60, 0x80, 0x00
+	};
+
+	/* DTAP - Routing Area Update Request */
+	static const unsigned char dtap_ra_upd_req[] = {
+		0x08, 0x08, 0x10, 0x11, 0x22, 0x33, 0x40, 0x50,
+		0x60, 0x1d, 0x19, 0x13, 0x42, 0x33, 0x57, 0x2b,
+		0xf7, 0xc8, 0x48, 0x02, 0x13, 0x48, 0x50, 0xc8,
+		0x48, 0x02, 0x14, 0x48, 0x50, 0xc8, 0x48, 0x02,
+		0x17, 0x49, 0x10, 0xc8, 0x48, 0x02, 0x00, 0x19,
+		0x8b, 0xb2, 0x92, 0x17, 0x16, 0x27, 0x07, 0x04,
+		0x31, 0x02, 0xe5, 0xe0, 0x32, 0x02, 0x20, 0x00
+	};
+
+	/* DTAP - Routing Area Update Request */
+	/* Invalid type: GPRS_UPD_T_RA_LA_IMSI_ATT */
+	static const unsigned char dtap_ra_upd_req_inv_type[] = {
+		0x08, 0x08, 0x12, 0x11, 0x22, 0x33, 0x40, 0x50,
+		0x60, 0x1d, 0x19, 0x13, 0x42, 0x33, 0x57, 0x2b,
+		0xf7, 0xc8, 0x48, 0x02, 0x13, 0x48, 0x50, 0xc8,
+		0x48, 0x02, 0x14, 0x48, 0x50, 0xc8, 0x48, 0x02,
+		0x17, 0x49, 0x10, 0xc8, 0x48, 0x02, 0x00, 0x19,
+		0x8b, 0xb2, 0x92, 0x17, 0x16, 0x27, 0x07, 0x04,
+		0x31, 0x02, 0xe5, 0xe0, 0x32, 0x02, 0x20, 0x00
+	};
+
+	/* DTAP - Routing Area Update Request */
+	/* Invalid cap length */
+	static const unsigned char dtap_ra_upd_req_inv_cap_len[] = {
+		0x08, 0x08, 0x10, 0x11, 0x22, 0x33, 0x40, 0x50,
+		0x60, 0x3d, 0x19, 0x13, 0x42, 0x33, 0x57, 0x2b,
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		0xf7, 0xc8, 0x48, 0x02, 0x13, 0x48, 0x50, 0xc8,
+		0x48, 0x02, 0x14, 0x48, 0x50, 0xc8, 0x48, 0x02,
+		0x17, 0x49, 0x10, 0xc8, 0x48, 0x02, 0x00, 0x19,
+		0x8b, 0xb2, 0x92, 0x17, 0x16, 0x27, 0x07, 0x04,
+		0x31, 0x02, 0xe5, 0xe0, 0x32, 0x02, 0x20, 0x00
+	};
+
+	struct test {
+		const char *title;
+		const unsigned char *msg;
+		unsigned msg_len;
+		unsigned num_resp;
+
+	};
+	static struct test tests[] = {
+		{
+			.title    = "Attach Request (invalid MI length)",
+			.msg      = attach_req_inv_mi_len,
+			.msg_len  = sizeof(attach_req_inv_mi_len),
+			.num_resp = 1 /* Reject */
+
+		},
+		{
+			.title   = "Attach Request (invalid MI type)",
+			.msg     = attach_req_inv_mi_type,
+			.msg_len = sizeof(attach_req_inv_mi_type),
+			.num_resp = 1 /* Reject */
+		},
+		{
+			.title   = "Routing Area Update Request (valid)",
+			.msg     = dtap_ra_upd_req,
+			.msg_len = sizeof(dtap_ra_upd_req),
+			.num_resp = 2 /* XID Reset + Reject */
+		},
+		{
+			.title   = "Routing Area Update Request (invalid type)",
+			.msg     = dtap_ra_upd_req_inv_type,
+			.msg_len = sizeof(dtap_ra_upd_req_inv_type),
+			.num_resp = 1 /* Reject */
+		},
+		{
+			.title   = "Routing Area Update Request (invalid CAP length)",
+			.msg     = dtap_ra_upd_req_inv_cap_len,
+			.msg_len = sizeof(dtap_ra_upd_req_inv_cap_len),
+			.num_resp = 1 /* Reject */
+		},
+	};
+
+	printf("Testing GMM reject\n");
+
+	/* reset the PRNG used by sgsn_alloc_ptmsi */
+	srand(1);
+
+	foreign_tlli = gprs_tmsi2tlli(0xc0000023, TLLI_FOREIGN);
+
+	OSMO_ASSERT(count(gprs_llme_list()) == 0);
+
+	for (idx = 0; idx < ARRAY_SIZE(tests); idx++) {
+		const struct test *test = &tests[idx];
+		printf("  - %s\n", test->title);
+
+		/* Create a LLE/LLME */
+		lle = gprs_lle_get_or_create(foreign_tlli, 3);
+		OSMO_ASSERT(count(gprs_llme_list()) == 1);
+
+		/* Inject the Request message */
+		send_0408_message(lle->llme, foreign_tlli,
+				  test->msg, test->msg_len);
+
+		/* We expect a Reject message */
+		fprintf(stderr, "sgsn_tx_counter = %d (expected %d)\n",
+			sgsn_tx_counter, test->num_resp);
+		OSMO_ASSERT(sgsn_tx_counter == test->num_resp);
+
+		/* verify that LLME/MM are removed */
+		ctx = sgsn_mm_ctx_by_tlli(foreign_tlli, &raid);
+		OSMO_ASSERT(ctx == NULL);
+		OSMO_ASSERT(count(gprs_llme_list()) == 0);
+	}
+}
+
 static struct log_info_cat gprs_categories[] = {
 	[DMM] = {
 		.name = "DMM",
@@ -473,6 +615,7 @@
 	test_gmm_detach_no_mmctx();
 	test_gmm_status_no_mmctx();
 	test_gmm_attach();
+	test_gmm_reject();
 	printf("Done\n");
 	return 0;
 }
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index c03bb1e..d3b333f 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -4,4 +4,10 @@
 Testing GMM detach (no MMCTX)
 Testing GMM Status (no MMCTX)
 Testing GMM attach
+Testing GMM reject
+  - Attach Request (invalid MI length)
+  - Attach Request (invalid MI type)
+  - Routing Area Update Request (valid)
+  - Routing Area Update Request (invalid type)
+  - Routing Area Update Request (invalid CAP length)
 Done