gbproxy: Implement IMSI acquisition

To modify or route messages based on the IMSI the latter must be known
when the action shall take place.

This patch modifies the gbproxy to optionally retain and enqueue
messages from the MS while initiating an identification procedure.
Further message processing of the LLC PTP link towards the SGSN will
be done, when the identity of the MS has been acquired.

Note that the N(U) of the LLC GMM SAPI are not adjusted, so it is
possible that adjacent messages of a single LLC link arriving either
at the BSS or the SGSN have the same N(U) and might get discarded,
leading to retransmissions and additional delay.

Note also that retransmissions and packet loss are not yet handled
explicitely. If for instance the generated IDENT REQ gets lost, the
gbproxy will not act on its own. In this case, the MS will time out
and eventually resend the Attach Request on which the gbproxy will
act exactly like before (thus having two Attach Req messages in its
queue, which will both be sent after the Ident Resp arrives).

This has been tested successfully with an E71, needing one
retransmission by the SGSN due to an N(U) collision.

Ticket: OW#1261
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
index 280c557..965059b 100644
--- a/openbsc/src/gprs/gb_proxy.c
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -72,6 +72,11 @@
 	.ctr_desc = global_ctr_description,
 };
 
+static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_peer *peer,
+			  uint16_t ns_bvci);
+static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg,
+			     uint16_t ns_bvci);
+
 static int check_peer_nsei(struct gbproxy_peer *peer, uint16_t nsei)
 {
 	if (peer->nsei != nsei) {
@@ -92,6 +97,81 @@
 	msgb_pull(msg, strip_len);
 }
 
+/* Transmit Chapter 9.2.10 Identity Request */
+static void gprs_put_identity_req(struct msgb *msg, uint8_t id_type)
+{
+	struct gsm48_hdr *gh;
+
+	id_type &= GSM_MI_TYPE_MASK;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM_GPRS;
+	gh->msg_type = GSM48_MT_GMM_ID_REQ;
+	gh->data[0] = id_type;
+}
+
+static void gprs_push_llc_ui(struct msgb *msg,
+			     int is_uplink, unsigned sapi, unsigned nu)
+{
+	const uint8_t e_bit = 0;
+	const uint8_t pm_bit = 1;
+	const uint8_t cr_bit = is_uplink ? 0 : 1;
+	size_t msg_size = msgb_length(msg);
+	uint8_t *llc;
+	uint8_t *fcs_field;
+	uint32_t fcs;
+
+	nu &= 0x01ff; /* 9 Bit */
+
+	llc = msgb_push(msg, 3);
+	llc[0] = (cr_bit << 6) | (sapi & 0x0f);
+	llc[1] = 0xc0 | (nu >> 6); /* UI frame */
+	llc[2] = (nu << 2) | ((e_bit & 1) << 1) | (pm_bit & 1);
+
+	fcs = gprs_llc_fcs(llc, msgb_length(msg));
+	fcs_field = msgb_put(msg, 3);
+	fcs_field[0] = (uint8_t)(fcs >> 0);
+	fcs_field[1] = (uint8_t)(fcs >> 8);
+	fcs_field[2] = (uint8_t)(fcs >> 16);
+}
+
+static void gprs_push_bssgp_dl_unitdata(struct msgb *msg,
+					uint32_t tlli)
+{
+	struct bssgp_ud_hdr *budh;
+	uint8_t *llc = msgb_data(msg);
+	size_t llc_size = msgb_length(msg);
+	const size_t llc_ie_hdr_size = 3;
+	const uint8_t qos_profile[] = {0x00, 0x50, 0x20}; /* hard-coded */
+	const uint8_t lifetime[] = {0x02, 0x58}; /* 6s hard-coded */
+
+	const size_t bssgp_overhead = sizeof(*budh) +
+		TVLV_GROSS_LEN(sizeof(lifetime)) + llc_ie_hdr_size;
+	uint8_t *ie;
+	uint32_t tlli_be = htonl(tlli);
+
+	budh = (struct bssgp_ud_hdr *)msgb_push(msg, bssgp_overhead);
+
+	budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
+	memcpy(&budh->tlli, &tlli_be, sizeof(budh->tlli));
+	memcpy(&budh->qos_profile, qos_profile, sizeof(budh->qos_profile));
+
+	ie = budh->data;
+	tvlv_put(ie, BSSGP_IE_PDU_LIFETIME, sizeof(lifetime), lifetime);
+	ie += TVLV_GROSS_LEN(sizeof(lifetime));
+
+	/* Note: Add alignment before the LLC IE if inserting other IE */
+
+	*(ie++) = BSSGP_IE_LLC_PDU;
+	*(ie++) = llc_size / 256;
+	*(ie++) = llc_size % 256;
+
+	OSMO_ASSERT(ie == llc);
+
+	msgb_bssgph(msg) = (uint8_t *)budh;
+	msgb_tlli(msg) = tlli;
+}
+
 /* update peer according to the BSS message */
 static void gbprox_update_current_raid(uint8_t *raid_enc,
 				       struct gbproxy_peer *peer,
@@ -175,9 +255,9 @@
 }
 
 /* patch BSSGP message */
-static void gbprox_process_bssgp_ul(struct gbproxy_config *cfg,
-				    struct msgb *msg,
-				    struct gbproxy_peer *peer)
+static int gbprox_process_bssgp_ul(struct gbproxy_config *cfg,
+				   struct msgb *msg,
+				   struct gbproxy_peer *peer)
 {
 	struct gprs_gb_parse_context parse_ctx = {0};
 	int rc;
@@ -185,8 +265,9 @@
 	time_t now;
 	struct gbproxy_tlli_info *tlli_info = NULL;
 
-	if (!cfg->core_mcc && !cfg->core_mnc && !cfg->core_apn)
-		return;
+	if (!cfg->core_mcc && !cfg->core_mnc && !cfg->core_apn &&
+	    !cfg->acquire_imsi)
+		return 1;
 
 	parse_ctx.to_bss = 0;
 
@@ -200,7 +281,7 @@
 			     "NSEI=%u(BSS) patching: "
 			     "failed to parse BSSGP/GMM message\n",
 			     msgb_nsei(msg));
-			return;
+			return 0;
 		}
 	}
 
@@ -221,7 +302,7 @@
 		     msgb_nsei(msg), parse_ctx.pdu_type);
 		/* Increment counter */
 		rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PATCH_PEER_ERR]);
-		return;
+		return 0;
 	}
 
 	now = time(NULL);
@@ -248,12 +329,103 @@
 
 	tlli_info = gbproxy_update_tlli_state_ul(peer, now, &parse_ctx);
 
+	if (tlli_info && tlli_info->imsi_acq_pending && parse_ctx.g48_hdr &&
+	    parse_ctx.g48_hdr->proto_discr == GSM48_PDISC_MM_GPRS &&
+	    parse_ctx.g48_hdr->msg_type == GSM48_MT_GMM_ID_RESP &&
+	    parse_ctx.imsi_len > 0) {
+		struct msgb *stored_msg;
+		/* Got identity response with IMSI, assuming the request had
+		 * been generated by the gbproxy */
+
+		LOGP(DLLC, LOGL_DEBUG,
+		     "NSEI=%d(BSS) IMSI acquisition succeeded, "
+		     "flushing stored messages\n",
+		     msgb_nsei(msg));
+
+		/* TODO: Patch and flush stored messages towards the SGSN */
+		while ((stored_msg = msgb_dequeue(&tlli_info->stored_msgs))) {
+			struct gprs_gb_parse_context tmp_parse_ctx = {0};
+			tmp_parse_ctx.to_bss = 0;
+			len_change = 0;
+
+			gprs_gb_parse_bssgp(msgb_bssgph(stored_msg),
+					    msgb_bssgp_len(stored_msg),
+					    &tmp_parse_ctx);
+			gbproxy_patch_bssgp(msg, msgb_bssgph(stored_msg),
+					    msgb_bssgp_len(stored_msg),
+					    peer, tlli_info, &len_change,
+					    &tmp_parse_ctx);
+
+			gbproxy_update_tlli_state_after(peer, tlli_info, now,
+							&tmp_parse_ctx);
+
+			rc = gbprox_relay2sgsn(cfg, stored_msg, msgb_bvci(msg));
+
+			if (rc < 0)
+				LOGP(DLLC, LOGL_ERROR,
+				     "NSEI=%d(BSS) failed to send stored message "
+				     "(%s)\n",
+				     msgb_nsei(msg),
+				     parse_ctx.llc_msg_name ? parse_ctx.llc_msg_name : "BSSGP");
+			msgb_free(stored_msg);
+		}
+
+		tlli_info->imsi_acq_pending = 0;
+
+		return 0;
+	}
+
+	if (tlli_info && tlli_info->imsi_acq_pending) {
+		struct msgb *stored_msg;
+
+		LOGP(DLLC, LOGL_DEBUG,
+		     "NSEI=%d(BSS) IMSI acquisition in progess, "
+		     "storing message (%s)\n",
+		     msgb_nsei(msg),
+		     parse_ctx.llc_msg_name ? parse_ctx.llc_msg_name : "BSSGP");
+
+		/* Enqueue unpatched messages */
+		stored_msg = gprs_msgb_copy(msg, "process_bssgp_ul");
+		msgb_enqueue(&tlli_info->stored_msgs, stored_msg);
+
+		/* TODO: Timeout, retransmit IDENT REQ if expired */
+		return 0;
+	}
+
+	if (cfg->acquire_imsi && tlli_info && tlli_info->mi_data_len == 0) {
+		struct msgb *idreq_msg;
+		struct msgb *stored_msg = gprs_msgb_copy(msg, "process_bssgp_ul");
+
+		LOGP(DLLC, LOGL_DEBUG,
+		     "NSEI=%d(BSS) IMSI is required but not available, "
+		     "storing message (%s)\n",
+		     msgb_nsei(msg),
+		     parse_ctx.llc_msg_name ? parse_ctx.llc_msg_name : "BSSGP");
+
+		/* Enqueue message */
+		msgb_enqueue(&tlli_info->stored_msgs, stored_msg);
+
+		/* Send IDENT REQ */
+		idreq_msg = gsm48_msgb_alloc();
+		gprs_put_identity_req(idreq_msg, GSM_MI_TYPE_IMSI);
+		gprs_push_llc_ui(idreq_msg, 0, GPRS_SAPI_GMM,
+				 /* TODO: use a real N(U) */0);
+		gprs_push_bssgp_dl_unitdata(idreq_msg, tlli_info->tlli.current);
+		gbprox_relay2peer(idreq_msg, peer, msgb_bvci(msg));
+		msgb_free(idreq_msg);
+
+		tlli_info->imsi_acq_pending = 1;
+
+		return 0;
+	}
+
+
 	gbproxy_patch_bssgp(msg, msgb_bssgph(msg), msgb_bssgp_len(msg),
 			    peer, tlli_info, &len_change, &parse_ctx);
 
 	gbproxy_update_tlli_state_after(peer, tlli_info, now, &parse_ctx);
 
-	return;
+	return 1;
 }
 
 /* patch BSSGP message to use core_mcc/mnc on the SGSN side */
@@ -428,13 +600,16 @@
 				  uint16_t nsvci, uint16_t ns_bvci)
 {
 	struct gbproxy_peer *peer;
+	int rc;
 
 	peer = gbproxy_peer_by_bvci(cfg, ns_bvci);
 
 	if (peer)
 		check_peer_nsei(peer, nsei);
 
-	gbprox_process_bssgp_ul(cfg, msg, peer);
+	rc = gbprox_process_bssgp_ul(cfg, msg, peer);
+	if (!rc)
+		return 0;
 
 	return gbprox_relay2sgsn(cfg, msg, ns_bvci);
 }
diff --git a/openbsc/src/gprs/gb_proxy_tlli.c b/openbsc/src/gprs/gb_proxy_tlli.c
index 91469b0..22585ab 100644
--- a/openbsc/src/gprs/gb_proxy_tlli.c
+++ b/openbsc/src/gprs/gb_proxy_tlli.c
@@ -198,6 +198,8 @@
 	tlli_info->tlli.ptmsi = GSM_RESERVED_TMSI;
 	tlli_info->sgsn_tlli.ptmsi = GSM_RESERVED_TMSI;
 
+	INIT_LLIST_HEAD(&tlli_info->stored_msgs);
+
 	return tlli_info;
 }
 
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
index dcdc06c..49c0020 100644
--- a/openbsc/src/gprs/gb_proxy_vty.c
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -295,6 +295,28 @@
 	return CMD_SUCCESS;
 }
 
+#define GBPROXY_ACQUIRE_IMSI_STR "Acquire the IMSI before establishing a LLC connection (Experimental)\n"
+
+DEFUN(cfg_gbproxy_acquire_imsi,
+      cfg_gbproxy_acquire_imsi_cmd,
+      "acquire-imsi",
+      GBPROXY_ACQUIRE_IMSI_STR)
+{
+	g_cfg->acquire_imsi = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_no_acquire_imsi,
+      cfg_gbproxy_no_acquire_imsi_cmd,
+      "no acquire-imsi",
+      NO_STR GBPROXY_ACQUIRE_IMSI_STR)
+{
+	g_cfg->acquire_imsi = 0;
+
+	return CMD_SUCCESS;
+}
+
 #define GBPROXY_TLLI_LIST_STR "Set TLLI list parameters\n"
 #define GBPROXY_MAX_AGE_STR "Limit maximum age\n"
 
@@ -604,12 +626,14 @@
 	install_element(GBPROXY_NODE, &cfg_gbproxy_core_apn_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_core_apn_match_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_patch_ptmsi_cmd);
+	install_element(GBPROXY_NODE, &cfg_gbproxy_acquire_imsi_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_tlli_list_max_age_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_tlli_list_max_len_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_mcc_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_mnc_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_no_core_apn_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_no_patch_ptmsi_cmd);
+	install_element(GBPROXY_NODE, &cfg_gbproxy_no_acquire_imsi_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_tlli_list_no_max_age_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_tlli_list_no_max_len_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_patch_mode_cmd);