gbproxy: Separate SGSN numeric namespaces

Currently the SGSN side message's TLLI are searched without checking
the originating SGSN. This leads to collisions if both SGSN use the
same P-TMSI for different MS.

With this patch, the SGSN NSEI is stored within the tlli_info and is
used in comparisons to separate the namespaces.

Note that this type of collision cannot happen with BSS numbers,
since the tlli_info are already separated and stored per (BSS) peer.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
index ee80375..4b46b0e 100644
--- a/openbsc/src/gprs/gb_proxy.c
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -257,7 +257,7 @@
 			sgsn_tlli = rand_r(&peer->cfg->sgsn_tlli_state);
 			sgsn_tlli = (sgsn_tlli & 0x7fffffff) | 0x78000000;
 
-			if (gbproxy_find_tlli_by_sgsn_tlli(peer, sgsn_tlli))
+			if (gbproxy_find_tlli_by_any_sgsn_tlli(peer, sgsn_tlli))
 				sgsn_tlli = 0;
 		} while (!sgsn_tlli);
 	}
@@ -309,6 +309,7 @@
 	while ((stored_msg = msgb_dequeue(&tlli_info->stored_msgs))) {
 		struct gprs_gb_parse_context tmp_parse_ctx = {0};
 		tmp_parse_ctx.to_bss = 0;
+		tmp_parse_ctx.peer_nsei = msgb_nsei(stored_msg);
 		int len_change = 0;
 
 		gprs_gb_parse_bssgp(msgb_bssgph(stored_msg),
@@ -511,13 +512,13 @@
 	time_t now;
 	struct gbproxy_tlli_info *tlli_info = NULL;
 	uint32_t sgsn_nsei = cfg->nsip_sgsn_nsei;
-	int send_msg_directly = 0;
 
 	if (!cfg->core_mcc && !cfg->core_mnc && !cfg->core_apn &&
 	    !cfg->acquire_imsi && !cfg->patch_ptmsi && !cfg->route_to_sgsn2)
 		return 1;
 
 	parse_ctx.to_bss = 0;
+	parse_ctx.peer_nsei = msgb_nsei(msg);
 
 	/* Parse BSSGP/LLC */
 	rc = gprs_gb_parse_bssgp(msgb_bssgph(msg), msgb_bssgp_len(msg),
@@ -560,11 +561,16 @@
 		}
 	}
 
-	if (tlli_info && cfg->route_to_sgsn2 && gbproxy_check_tlli(peer, tlli_info)) {
-		sgsn_nsei = cfg->nsip_sgsn2_nsei;
-		send_msg_directly = 1;
+	if (tlli_info && cfg->route_to_sgsn2) {
+		if (cfg->acquire_imsi && tlli_info->imsi_len == 0)
+			sgsn_nsei = 0xffff;
+		else if (gbproxy_check_tlli(peer, tlli_info))
+			sgsn_nsei = cfg->nsip_sgsn2_nsei;
 	}
 
+	if (tlli_info)
+		tlli_info->sgsn_nsei = sgsn_nsei;
+
 	/* Handle IMSI acquisition */
 	if (cfg->acquire_imsi) {
 		rc = gbproxy_imsi_acquisition(peer, msg, sgsn_nsei, now,
@@ -578,7 +584,7 @@
 
 	gbproxy_update_tlli_state_after(peer, tlli_info, now, &parse_ctx);
 
-	if (send_msg_directly) {
+	if (sgsn_nsei != cfg->nsip_sgsn_nsei) {
 		/* Send message directly to the selected SGSN */
 		rc = gbprox_relay2sgsn(cfg, msg, msgb_bvci(msg), sgsn_nsei);
 		/* Don't let the calling code handle the transmission */
@@ -604,6 +610,7 @@
 		return;
 
 	parse_ctx.to_bss = 1;
+	parse_ctx.peer_nsei = msgb_nsei(msg);
 
 	rc = gprs_gb_parse_bssgp(msgb_bssgph(msg), msgb_bssgp_len(msg),
 				 &parse_ctx);
diff --git a/openbsc/src/gprs/gb_proxy_tlli.c b/openbsc/src/gprs/gb_proxy_tlli.c
index c4140f7..8aadd2f 100644
--- a/openbsc/src/gprs/gb_proxy_tlli.c
+++ b/openbsc/src/gprs/gb_proxy_tlli.c
@@ -60,16 +60,33 @@
 	return NULL;
 }
 
-struct gbproxy_tlli_info *gbproxy_find_tlli_by_sgsn_tlli(
+struct gbproxy_tlli_info *gbproxy_find_tlli_by_any_sgsn_tlli(
 	struct gbproxy_peer *peer,
 	uint32_t tlli)
 {
 	struct gbproxy_tlli_info *tlli_info;
 	struct gbproxy_patch_state *state = &peer->patch_state;
 
+	/* Don't care about the NSEI */
 	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
 		if (tlli_info->sgsn_tlli.current == tlli ||
-		    tlli_info->sgsn_tlli.assigned == tlli)
+		     tlli_info->sgsn_tlli.assigned == tlli)
+			return tlli_info;
+
+	return NULL;
+}
+
+struct gbproxy_tlli_info *gbproxy_find_tlli_by_sgsn_tlli(
+	struct gbproxy_peer *peer,
+	uint32_t tlli, uint32_t sgsn_nsei)
+{
+	struct gbproxy_tlli_info *tlli_info;
+	struct gbproxy_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
+		if ((tlli_info->sgsn_tlli.current == tlli ||
+		     tlli_info->sgsn_tlli.assigned == tlli) &&
+		    tlli_info->sgsn_nsei == sgsn_nsei)
 			return tlli_info;
 
 	return NULL;
@@ -409,7 +426,8 @@
 			continue;
 
 		if (!gbproxy_tlli_match(&tlli_info->tlli, &info->tlli) &&
-		    !gbproxy_tlli_match(&tlli_info->sgsn_tlli, &info->sgsn_tlli))
+		    (tlli_info->sgsn_nsei != info->sgsn_nsei ||
+		     !gbproxy_tlli_match(&tlli_info->sgsn_tlli, &info->sgsn_tlli)))
 			continue;
 
 		LOGP(DGPRS, LOGL_INFO,
@@ -508,7 +526,8 @@
 	struct gbproxy_tlli_info *tlli_info = NULL;
 
 	if (parse_ctx->tlli_enc)
-		tlli_info = gbproxy_find_tlli_by_sgsn_tlli(peer, parse_ctx->tlli);
+		tlli_info = gbproxy_find_tlli_by_sgsn_tlli(
+			peer, parse_ctx->tlli, parse_ctx->peer_nsei);
 
 	if (parse_ctx->tlli_enc && parse_ctx->new_ptmsi_enc && tlli_info) {
 		/* A new P-TMSI has been signalled in the message,
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
index 22592a1..da61563 100644
--- a/openbsc/src/gprs/gb_proxy_vty.c
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -479,6 +479,10 @@
 			if (stored_msgs)
 				vty_out(vty, ", STORED %d", stored_msgs);
 
+			if (g_cfg->route_to_sgsn2)
+				vty_out(vty, ", SGSN NSEI %d",
+					tlli_info->sgsn_nsei);
+
 			if (tlli_info->is_deregistered)
 				vty_out(vty, ", DE-REGISTERED");
 
@@ -583,15 +587,15 @@
 	"Delete a GBProxy TLLI entry by NSEI and identification\nNSEI number\n"
 
 DEFUN(delete_gb_tlli_by_id, delete_gb_tlli_by_id_cmd,
-	"delete-gbproxy-tlli <0-65534> (tlli|imsi) IDENT",
+	"delete-gbproxy-tlli <0-65534> (tlli|imsi|sgsn-nsei) IDENT",
 	GBPROXY_DELETE_TLLI_STR
 	"Delete entries with a matching TLLI (hex)\n"
 	"Delete entries with a matching IMSI\n"
 	"Identification to match\n")
 {
 	const uint16_t nsei = atoi(argv[0]);
-	enum {MATCH_TLLI = 't', MATCH_IMSI = 'i'} match;
-	uint32_t tlli = 0;
+	enum {MATCH_TLLI = 't', MATCH_IMSI = 'i', MATCH_SGSN = 's'} match;
+	uint32_t ident = 0;
 	const char *imsi = NULL;
 	struct gbproxy_peer *peer = 0;
 	struct gbproxy_tlli_info *tlli_info, *nxt;
@@ -601,10 +605,11 @@
 
 	match = argv[1][0];
 
-	if (match == MATCH_TLLI)
-		tlli = strtoll(argv[2], NULL, 16);
-	else
-		imsi = argv[2];
+	switch (match) {
+	case MATCH_TLLI: ident = strtoll(argv[2], NULL, 16); break;
+	case MATCH_IMSI: imsi = argv[2]; break;
+	case MATCH_SGSN: ident = strtoll(argv[2], NULL, 0); break;
+	};
 
 	peer = gbproxy_peer_by_nsei(g_cfg, nsei);
 	if (!peer) {
@@ -616,10 +621,16 @@
 	state = &peer->patch_state;
 
 	llist_for_each_entry_safe(tlli_info, nxt, &state->enabled_tllis, list) {
-		if (match == MATCH_TLLI) {
-			if (tlli_info->tlli.current != tlli)
+		switch (match) {
+		case MATCH_TLLI:
+			if (tlli_info->tlli.current != ident)
 				continue;
-		} else {
+			break;
+		case MATCH_SGSN:
+			if (tlli_info->sgsn_nsei != ident)
+				continue;
+			break;
+		case MATCH_IMSI:
 			mi_buf[0] = '\0';
 			gsm48_mi_to_string(mi_buf, sizeof(mi_buf),
 					   tlli_info->imsi,
@@ -627,6 +638,7 @@
 
 			if (strcmp(mi_buf, imsi) != 0)
 				continue;
+			break;
 		}
 
 		vty_out(vty, "Deleting TLLI %08x%s", tlli_info->tlli.current,