gbproxy: Add NSE peer that can have multiple gbproxy_peers

We want this level of indirection to support multiple BVCs per NSE. The
current code assumes that an NSE only has one BVC which breaks messages
on the signalling BVC which should only be sent once to an NSE
regardless of the number of BVCs it contains.

Change-Id: I97cc6c8f8c0f1b91577ab8f679c4ae217cc88076
Related: SYS#5226
diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c
index 9009651..736bd7a 100644
--- a/src/gbproxy/gb_proxy.c
+++ b/src/gbproxy/gb_proxy.c
@@ -84,10 +84,12 @@
 
 static int check_peer_nsei(struct gbproxy_peer *peer, uint16_t nsei)
 {
-	if (peer->nsei != nsei) {
+	OSMO_ASSERT(peer->nse);
+
+	if (peer->nse->nsei != nsei) {
 		LOGP(DGPRS, LOGL_NOTICE, "Peer entry doesn't match current NSEI "
 		     "BVCI=%u via NSEI=%u (expected NSEI=%u)\n",
-		     peer->bvci, nsei, peer->nsei);
+		     peer->bvci, nsei, peer->nse->nsei);
 		rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_INV_NSEI]);
 		return 0;
 	}
@@ -195,6 +197,9 @@
 	struct gbproxy_patch_state *state = &peer->patch_state;
 	const struct osmo_plmn_id old_plmn = state->local_plmn;
 	struct gprs_ra_id raid;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	if (!raid_enc)
 		return;
@@ -202,15 +207,15 @@
 	gsm48_parse_ra(&raid, raid_enc);
 
 	/* save source side MCC/MNC */
-	if (!peer->cfg->core_plmn.mcc || raid.mcc == peer->cfg->core_plmn.mcc) {
+	if (!cfg->core_plmn.mcc || raid.mcc == cfg->core_plmn.mcc) {
 		state->local_plmn.mcc = 0;
 	} else {
 		state->local_plmn.mcc = raid.mcc;
 	}
 
-	if (!peer->cfg->core_plmn.mnc
+	if (!cfg->core_plmn.mnc
 	    || !osmo_mnc_cmp(raid.mnc, raid.mnc_3_digits,
-			     peer->cfg->core_plmn.mnc, peer->cfg->core_plmn.mnc_3_digits)) {
+			     cfg->core_plmn.mnc, cfg->core_plmn.mnc_3_digits)) {
 		state->local_plmn.mnc = 0;
 		state->local_plmn.mnc_3_digits = false;
 	} else {
@@ -226,7 +231,7 @@
 		     "" : "de",
 		     log_text,
 		     osmo_plmn_name(&state->local_plmn),
-		     osmo_plmn_name2(&peer->cfg->core_plmn));
+		     osmo_plmn_name2(&cfg->core_plmn));
 }
 
 uint32_t gbproxy_make_bss_ptmsi(struct gbproxy_peer *peer,
@@ -234,7 +239,7 @@
 {
 	uint32_t bss_ptmsi;
 	int max_retries = 23, rc = 0;
-	if (!peer->cfg->patch_ptmsi) {
+	if (!peer->nse->cfg->patch_ptmsi) {
 		bss_ptmsi = sgsn_ptmsi;
 	} else {
 		do {
@@ -263,7 +268,7 @@
 {
 	uint32_t sgsn_tlli;
 	int max_retries = 23, rc = 0;
-	if (!peer->cfg->patch_ptmsi) {
+	if (!peer->nse->cfg->patch_ptmsi) {
 		sgsn_tlli = bss_tlli;
 	} else if (link_info->sgsn_tlli.ptmsi != GSM_RESERVED_TMSI &&
 		   gprs_tlli_type(bss_tlli) == TLLI_FOREIGN) {
@@ -330,6 +335,9 @@
 {
 	int rc;
 	struct msgb *stored_msg;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	/* Patch and flush stored messages towards the SGSN */
 	while ((stored_msg = msgb_dequeue_count(&link_info->stored_msgs,
@@ -355,7 +363,7 @@
 			return -1;
 		}
 
-		rc = gbprox_relay2sgsn(peer->cfg, stored_msg,
+		rc = gbprox_relay2sgsn(cfg, stored_msg,
 				       msgb_bvci(stored_msg), link_info->sgsn_nsei);
 
 		if (rc < 0)
@@ -425,6 +433,9 @@
 				    struct gprs_gb_parse_context *parse_ctx)
 {
 	struct msgb *stored_msg;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	if (!link_info)
 		return 1;
@@ -493,9 +504,9 @@
 	/* The message cannot be processed since the IMSI is still missing */
 
 	/* If queue is getting too large, drop oldest msgb before adding new one */
-	if (peer->cfg->stored_msgs_max_len > 0) {
+	if (cfg->stored_msgs_max_len > 0) {
 		int exceeded_max_len = link_info->stored_msgs_len
-				   + 1 - peer->cfg->stored_msgs_max_len;
+				   + 1 - cfg->stored_msgs_max_len;
 
 		for (; exceeded_max_len > 0; exceeded_max_len--) {
 			struct msgb *msgb_drop;
@@ -806,19 +817,23 @@
 static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_peer *peer,
 			     uint16_t ns_bvci)
 {
+	struct gbproxy_nse *nse = peer->nse;
+	OSMO_ASSERT(nse);
+	OSMO_ASSERT(nse->cfg);
+
 	/* create a copy of the message so the old one can
 	 * be free()d safely when we return from gbprox_rcvmsg() */
-	struct gprs_ns2_inst *nsi = peer->cfg->nsi;
+	struct gprs_ns2_inst *nsi = nse->cfg->nsi;
 	struct osmo_gprs_ns2_prim nsp = {};
 	struct msgb *msg = bssgp_msgb_copy(old_msg, "msgb_relay2peer");
 	uint32_t tlli;
 	int rc;
 
 	DEBUGP(DGPRS, "NSEI=%u proxying SGSN->BSS (NS_BVCI=%u, NSEI=%u)\n",
-		msgb_nsei(msg), ns_bvci, peer->nsei);
+		msgb_nsei(msg), ns_bvci, nse->nsei);
 
 	nsp.bvci = ns_bvci;
-	nsp.nsei = peer->nsei;
+	nsp.nsei = nse->nsei;
 
 	/* Strip the old NS header, it will be replaced with a new one */
 	strip_ns_hdr(msg);
@@ -1051,16 +1066,33 @@
 			}
 			from_peer = gbproxy_peer_by_bvci(cfg, bvci);
 			if (!from_peer) {
+				struct gbproxy_nse *nse = gbproxy_nse_by_nsei_or_new(cfg, nsei);
+				if (!nse) {
+					LOGP(DGPRS, LOGL_ERROR, "Could not allocate NSE for NSEI=%u\n", nsei);
+					return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+				}
 				/* if a PTP-BVC is reset, and we don't know that
 				 * PTP-BVCI yet, we should allocate a new peer */
 				LOGP(DGPRS, LOGL_INFO, "Allocationg new peer for BVCI=%u via NSEI=%u\n", bvci, nsei);
-				from_peer = gbproxy_peer_alloc(cfg, bvci);
+				from_peer = gbproxy_peer_alloc(nse, bvci);
 				OSMO_ASSERT(from_peer);
-				from_peer->nsei = nsei;
 			}
 
-			if (!check_peer_nsei(from_peer, nsei))
-				from_peer->nsei = nsei;
+			/* Could have moved to a different NSE */
+			if (!check_peer_nsei(from_peer, nsei)) {
+				struct gbproxy_nse *nse_old = from_peer->nse;
+				struct gbproxy_nse *nse_new = gbproxy_nse_by_nsei_or_new(cfg, nsei);
+				if (!nse_new) {
+					LOGP(DGPRS, LOGL_ERROR, "Could not allocate NSE for NSEI=%u\n", nsei);
+					return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
+				}
+				LOGP(DGPRS, LOGL_NOTICE, "Peer for BVCI=%u moved from NSEI=%u to NSEI=%u\n", bvci, nse_old->nsei, nsei);
+
+				/* Move peer to different NSE */
+				llist_del(&from_peer->list);
+				llist_add(&from_peer->list, &nse_new->bts_peers);
+				from_peer->nse = nse_new;
+			}
 
 			if (TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID)) {
 				struct gprs_ra_id raid;
@@ -1107,10 +1139,13 @@
 static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct tlv_parsed *tp,
 			    uint32_t nsei, uint16_t ns_bvci)
 {
+	struct gbproxy_nse *nse;
 	struct gbproxy_peer *peer;
 	unsigned int n_peers = 0;
 	int errctr = GBPROX_GLOB_CTR_PROTO_ERR_SGSN;
 
+	/* FIXME: Handle paging logic to only page each matching NSE */
+
 	LOGP(DGPRS, LOGL_INFO, "NSEI=%u(SGSN) BSSGP PAGING ",
 		nsei);
 	if (TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
@@ -1128,29 +1163,35 @@
 	} else if (TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
 		errctr = GBPROX_GLOB_CTR_INV_RAI;
 		/* iterate over all peers and dispatch the paging to each matching one */
-		llist_for_each_entry(peer, &cfg->bts_peers, list) {
-			if (!memcmp(peer->ra, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA), 6)) {
-				LOGPC(DGPRS, LOGL_INFO, "routing by RAI to peer BVCI=%u\n", peer->bvci);
-				gbprox_relay2peer(msg, peer, ns_bvci);
-				n_peers++;
+		llist_for_each_entry(nse, &cfg->nse_peers, list) {
+			llist_for_each_entry(peer, &nse->bts_peers, list) {
+				if (!memcmp(peer->ra, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA), 6)) {
+					LOGPC(DGPRS, LOGL_INFO, "routing by RAI to peer BVCI=%u\n", peer->bvci);
+					gbprox_relay2peer(msg, peer, ns_bvci);
+					n_peers++;
+				}
 			}
 		}
 	} else if (TLVP_PRESENT(tp, BSSGP_IE_LOCATION_AREA)) {
 		errctr = GBPROX_GLOB_CTR_INV_LAI;
 		/* iterate over all peers and dispatch the paging to each matching one */
-		llist_for_each_entry(peer, &cfg->bts_peers, list) {
-			if (!memcmp(peer->ra, TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA), 5)) {
-				LOGPC(DGPRS, LOGL_INFO, "routing by LAI to peer BVCI=%u\n", peer->bvci);
-				gbprox_relay2peer(msg, peer, ns_bvci);
-				n_peers++;
+		llist_for_each_entry(nse, &cfg->nse_peers, list) {
+			llist_for_each_entry(peer, &nse->bts_peers, list) {
+				if (!memcmp(peer->ra, TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA), 5)) {
+					LOGPC(DGPRS, LOGL_INFO, "routing by LAI to peer BVCI=%u\n", peer->bvci);
+					gbprox_relay2peer(msg, peer, ns_bvci);
+					n_peers++;
+				}
 			}
 		}
 	} else if (TLVP_PRESENT(tp, BSSGP_IE_BSS_AREA_ID)) {
 		/* iterate over all peers and dispatch the paging to each matching one */
-		llist_for_each_entry(peer, &cfg->bts_peers, list) {
-			LOGPC(DGPRS, LOGL_INFO, "broadcasting to peer BVCI=%u\n", peer->bvci);
-			gbprox_relay2peer(msg, peer, ns_bvci);
-			n_peers++;
+		llist_for_each_entry(nse, &cfg->nse_peers, list) {
+			llist_for_each_entry(peer, &nse->bts_peers, list) {
+				LOGPC(DGPRS, LOGL_INFO, "broadcasting to peer BVCI=%u\n", peer->bvci);
+				gbprox_relay2peer(msg, peer, ns_bvci);
+				n_peers++;
+			}
 		}
 	} else {
 		LOGPC(DGPRS, LOGL_INFO, "\n");
@@ -1174,6 +1215,7 @@
 			struct msgb *msg, struct tlv_parsed *tp,
 			uint32_t nsei, uint16_t ns_bvci)
 {
+	struct gbproxy_nse *nse;
 	struct gbproxy_peer *peer;
 	uint16_t ptp_bvci;
 
@@ -1204,8 +1246,10 @@
 	 * from the SGSN.  As the signalling BVCI is shared
 	 * among all the BSS's that we multiplex, it needs to
 	 * be relayed  */
-	llist_for_each_entry(peer, &cfg->bts_peers, list)
-		gbprox_relay2peer(msg, peer, ns_bvci);
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		llist_for_each_entry(peer, &nse->bts_peers, list)
+			gbprox_relay2peer(msg, peer, ns_bvci);
+	}
 
 	return 0;
 }
@@ -1454,7 +1498,8 @@
 			rate_ctr_inc(&cfg->ctrg->
 				     ctr[GBPROX_GLOB_CTR_RESTART_RESET_SGSN]);
 		} else {
-			/* bss became unavailable */
+			/* bss became unavailable
+			 * TODO: Block all BVC belonging to that NSE */
 			peer = gbproxy_peer_by_nsei(cfg, nsp->nsei);
 			if (!peer) {
 				/* TODO: use primitive name + status cause name */
@@ -1525,10 +1570,15 @@
 
 void gbprox_reset(struct gbproxy_config *cfg)
 {
-	struct gbproxy_peer *peer, *tmp;
+	struct gbproxy_nse *nse, *ntmp;
 
-	llist_for_each_entry_safe(peer, tmp, &cfg->bts_peers, list)
-		gbproxy_peer_free(peer);
+	llist_for_each_entry_safe(nse, ntmp, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer, *tmp;
+		llist_for_each_entry_safe(peer, tmp, &nse->bts_peers, list)
+			gbproxy_peer_free(peer);
+
+		gbproxy_nse_free(nse);
+	}
 
 	rate_ctr_group_free(cfg->ctrg);
 	gbproxy_init_config(cfg);
@@ -1538,7 +1588,7 @@
 {
 	struct timespec tp;
 
-	INIT_LLIST_HEAD(&cfg->bts_peers);
+	INIT_LLIST_HEAD(&cfg->nse_peers);
 	cfg->ctrg = rate_ctr_group_alloc(tall_sgsn_ctx, &global_ctrg_desc, 0);
 	if (!cfg->ctrg) {
 		LOGP(DGPRS, LOGL_ERROR, "Cannot allocate global counter group!\n");
diff --git a/src/gbproxy/gb_proxy_ctrl.c b/src/gbproxy/gb_proxy_ctrl.c
index 9577383..482bca0 100644
--- a/src/gbproxy/gb_proxy_ctrl.c
+++ b/src/gbproxy/gb_proxy_ctrl.c
@@ -55,7 +55,7 @@
 	struct gbproxy_config *cfg = data;
 	struct gprs_ns2_inst *nsi = cfg->nsi;
 	struct gprs_ns2_nse *nse;
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse_peer;
 
 	cmd->reply = talloc_strdup(cmd, "");
 
@@ -69,8 +69,8 @@
 		gprs_ns2_nse_foreach_nsvc(nse, &ctrl_nsvc_state_cb, cmd);
 
 	/* NS-VCs for BSS peers */
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		nse = gprs_ns2_nse_by_nsei(nsi, peer->nsei);
+	llist_for_each_entry(nse_peer, &cfg->nse_peers, list) {
+		nse = gprs_ns2_nse_by_nsei(nsi, nse_peer->nsei);
 		if (nse)
 			gprs_ns2_nse_foreach_nsvc(nse, &ctrl_nsvc_state_cb, cmd);
 	}
@@ -83,19 +83,22 @@
 static int get_gbproxy_state(struct ctrl_cmd *cmd, void *data)
 {
 	struct gbproxy_config *cfg = data;
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse_peer;
 
 	cmd->reply = talloc_strdup(cmd, "");
 
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		struct gprs_ra_id raid;
-		gsm48_parse_ra(&raid, peer->ra);
+	llist_for_each_entry(nse_peer, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse_peer->bts_peers, list) {
+			struct gprs_ra_id raid;
+			gsm48_parse_ra(&raid, peer->ra);
 
-		cmd->reply = talloc_asprintf_append(cmd->reply, "%u,%u,%u,%u,%u,%u,%s\n",
-				peer->nsei, peer->bvci,
-				raid.mcc, raid.mnc,
-				raid.lac, raid.rac,
-				peer->blocked ? "BLOCKED" : "UNBLOCKED");
+			cmd->reply = talloc_asprintf_append(cmd->reply, "%u,%u,%u,%u,%u,%u,%s\n",
+					nse_peer->nsei, peer->bvci,
+					raid.mcc, raid.mnc,
+					raid.lac, raid.rac,
+					peer->blocked ? "BLOCKED" : "UNBLOCKED");
+		}
 	}
 
 	return CTRL_CMD_REPLY;
@@ -106,9 +109,14 @@
 static int get_num_peers(struct ctrl_cmd *cmd, void *data)
 {
 	struct gbproxy_config *cfg = data;
+	struct gbproxy_nse *nse_peer;
+	uint32_t count = 0;
+
+	llist_for_each_entry(nse_peer, &cfg->nse_peers, list)
+		count += llist_count(&nse_peer->bts_peers);
 
 	cmd->reply = talloc_strdup(cmd, "");
-	cmd->reply = talloc_asprintf_append(cmd->reply, "%u", llist_count(&cfg->bts_peers));
+	cmd->reply = talloc_asprintf_append(cmd->reply, "%u", count);
 
 	return CTRL_CMD_REPLY;
 }
diff --git a/src/gbproxy/gb_proxy_patch.c b/src/gbproxy/gb_proxy_patch.c
index 9c70d3f..6601657 100644
--- a/src/gbproxy/gb_proxy_patch.c
+++ b/src/gbproxy/gb_proxy_patch.c
@@ -44,6 +44,10 @@
 		GBPROX_PEER_CTR_RAID_PATCHED_SGSN :
 		GBPROX_PEER_CTR_RAID_PATCHED_BSS;
 
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
+
 	if (!state->local_plmn.mcc || !state->local_plmn.mnc)
 		return;
 
@@ -58,11 +62,11 @@
 	if (!to_bss) {
 		/* BSS -> SGSN */
 		if (state->local_plmn.mcc)
-			raid.mcc = peer->cfg->core_plmn.mcc;
+			raid.mcc = cfg->core_plmn.mcc;
 
 		if (state->local_plmn.mnc) {
-			raid.mnc = peer->cfg->core_plmn.mnc;
-			raid.mnc_3_digits = peer->cfg->core_plmn.mnc_3_digits;
+			raid.mnc = cfg->core_plmn.mnc;
+			raid.mnc_3_digits = cfg->core_plmn.mnc_3_digits;
 		}
 	} else {
 		/* SGSN -> BSS */
@@ -100,11 +104,14 @@
 
 	size_t apn_len = hdr->apn_len;
 	uint8_t *apn = hdr->apn;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	OSMO_ASSERT(apn_ie_len == apn_len + sizeof(struct apn_ie_hdr));
 	OSMO_ASSERT(apn_ie_len > 2 && apn_ie_len <= 102);
 
-	if (peer->cfg->core_apn_size == 0) {
+	if (cfg->core_apn_size == 0) {
 		char str1[110];
 		/* Remove the IE */
 		LOGP(DGPRS, LOGL_DEBUG,
@@ -119,20 +126,20 @@
 		char str1[110];
 		char str2[110];
 
-		OSMO_ASSERT(peer->cfg->core_apn_size <= 100);
+		OSMO_ASSERT(cfg->core_apn_size <= 100);
 
 		LOGP(DGPRS, LOGL_DEBUG,
 		     "Patching %s to SGSN: "
 		     "Replacing APN '%s' -> '%s'\n",
 		     log_text,
 		     osmo_apn_to_str(str1, apn, apn_len),
-		     osmo_apn_to_str(str2, peer->cfg->core_apn,
-				       peer->cfg->core_apn_size));
+		     osmo_apn_to_str(str2, cfg->core_apn,
+				       cfg->core_apn_size));
 
-		*new_apn_ie_len = peer->cfg->core_apn_size + 2;
-		msgb_resize_area(msg, apn, apn_len, peer->cfg->core_apn_size);
-		memcpy(apn, peer->cfg->core_apn, peer->cfg->core_apn_size);
-		hdr->apn_len = peer->cfg->core_apn_size;
+		*new_apn_ie_len = cfg->core_apn_size + 2;
+		msgb_resize_area(msg, apn, apn_len, cfg->core_apn_size);
+		memcpy(apn, cfg->core_apn, cfg->core_apn_size);
+		hdr->apn_len = cfg->core_apn_size;
 	}
 
 	rate_ctr_inc(&peer->ctrg->ctr[GBPROX_PEER_CTR_APN_PATCHED]);
@@ -207,10 +214,12 @@
 	struct gprs_llc_hdr_parsed *ghp = &parse_ctx->llc_hdr_parsed;
 	int have_patched = 0;
 	int fcs;
-	struct gbproxy_config *cfg = peer->cfg;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	if (parse_ctx->ptmsi_enc && link_info &&
-	    !parse_ctx->old_raid_is_foreign && peer->cfg->patch_ptmsi) {
+	    !parse_ctx->old_raid_is_foreign && cfg->patch_ptmsi) {
 		uint32_t ptmsi;
 		if (parse_ctx->to_bss)
 			ptmsi = link_info->tlli.ptmsi;
@@ -291,13 +300,16 @@
 {
 	const char *err_info = NULL;
 	int err_ctr = -1;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	if (parse_ctx->bssgp_raid_enc)
 		gbproxy_patch_raid((struct gsm48_ra_id *)parse_ctx->bssgp_raid_enc, peer,
 				   parse_ctx->to_bss, "BSSGP");
 
 	if (parse_ctx->need_decryption &&
-	    (peer->cfg->patch_ptmsi || peer->cfg->core_apn)) {
+	    (cfg->patch_ptmsi || cfg->core_apn)) {
 		/* Patching LLC messages has been requested
 		 * explicitly, but the message (including the
 		 * type) is encrypted, so we possibly fail to
@@ -319,7 +331,7 @@
 	if (!link_info)
 		return;
 
-	if (parse_ctx->tlli_enc && peer->cfg->patch_ptmsi) {
+	if (parse_ctx->tlli_enc && cfg->patch_ptmsi) {
 		uint32_t tlli = gbproxy_map_tlli(parse_ctx->tlli,
 						 link_info, parse_ctx->to_bss);
 
@@ -335,7 +347,7 @@
 		}
 	}
 
-	if (parse_ctx->bssgp_ptmsi_enc && peer->cfg->patch_ptmsi) {
+	if (parse_ctx->bssgp_ptmsi_enc && cfg->patch_ptmsi) {
 		uint32_t ptmsi;
 		if (parse_ctx->to_bss)
 			ptmsi = link_info->tlli.ptmsi;
diff --git a/src/gbproxy/gb_proxy_peer.c b/src/gbproxy/gb_proxy_peer.c
index cb76a59..920547c 100644
--- a/src/gbproxy/gb_proxy_peer.c
+++ b/src/gbproxy/gb_proxy_peer.c
@@ -81,25 +81,30 @@
 };
 
 
-/* Find the gbprox_peer by its BVCI */
+/* Find the gbproxy_peer by its BVCI. There can only be one match */
 struct gbproxy_peer *gbproxy_peer_by_bvci(struct gbproxy_config *cfg, uint16_t bvci)
 {
-	struct gbproxy_peer *peer;
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		if (peer->bvci == bvci)
-			return peer;
+	struct gbproxy_nse *nse;
+
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			if (peer->bvci == bvci)
+				return peer;
+		}
 	}
 	return NULL;
 }
 
-/* Find the gbprox_peer by its NSEI */
+/* Find the gbproxy_peer by its NSEI */
+/* FIXME: Only returns the first peer, but we could have multiple on this nsei */
 struct gbproxy_peer *gbproxy_peer_by_nsei(struct gbproxy_config *cfg,
 					  uint16_t nsei)
 {
-	struct gbproxy_peer *peer;
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		if (peer->nsei == nsei)
-			return peer;
+	struct gbproxy_nse *nse;
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		if (nse->nsei == nsei && !llist_empty(&nse->bts_peers))
+			return llist_first_entry(&nse->bts_peers, struct gbproxy_peer, list);
 	}
 	return NULL;
 }
@@ -109,11 +114,16 @@
 struct gbproxy_peer *gbproxy_peer_by_rai(struct gbproxy_config *cfg,
 					 const uint8_t *ra)
 {
-	struct gbproxy_peer *peer;
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		if (!memcmp(peer->ra, ra, 6))
-			return peer;
+	struct gbproxy_nse *nse;
+
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			if (!memcmp(peer->ra, ra, 6))
+				return peer;
+		}
 	}
+
 	return NULL;
 }
 
@@ -122,10 +132,14 @@
 struct gbproxy_peer *gbproxy_peer_by_lai(struct gbproxy_config *cfg,
 					 const uint8_t *la)
 {
-	struct gbproxy_peer *peer;
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		if (!memcmp(peer->ra, la, 5))
-			return peer;
+	struct gbproxy_nse *nse;
+
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			if (!memcmp(peer->ra, la, 5))
+				return peer;
+		}
 	}
 	return NULL;
 }
@@ -135,10 +149,14 @@
 struct gbproxy_peer *gbproxy_peer_by_lac(struct gbproxy_config *cfg,
 					 const uint8_t *la)
 {
-	struct gbproxy_peer *peer;
-	llist_for_each_entry(peer, &cfg->bts_peers, list) {
-		if (!memcmp(peer->ra + 3, la + 3, 2))
-			return peer;
+	struct gbproxy_nse *nse;
+
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			if (!memcmp(peer->ra + 3, la + 3, 2))
+				return peer;
+		}
 	}
 	return NULL;
 }
@@ -177,18 +195,25 @@
 	time_t now;
 	struct timespec ts = {0,};
 	struct gbproxy_peer *peer = (struct gbproxy_peer *) data;
+	OSMO_ASSERT(peer);
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	osmo_clock_gettime(CLOCK_MONOTONIC, &ts);
 	now = ts.tv_sec;
 	gbproxy_remove_stale_link_infos(peer, now);
-	if (peer->cfg->clean_stale_timer_freq != 0)
+	if (cfg->clean_stale_timer_freq != 0)
 		osmo_timer_schedule(&peer->clean_stale_timer,
-					peer->cfg->clean_stale_timer_freq, 0);
+					cfg->clean_stale_timer_freq, 0);
 }
 
-struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_config *cfg, uint16_t bvci)
+struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_nse *nse, uint16_t bvci)
 {
 	struct gbproxy_peer *peer;
+	OSMO_ASSERT(nse);
+	struct gbproxy_config *cfg = nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	peer = talloc_zero(tall_sgsn_ctx, struct gbproxy_peer);
 	if (!peer)
@@ -200,22 +225,24 @@
 		talloc_free(peer);
 		return NULL;
 	}
-	peer->cfg = cfg;
+	peer->nse = nse;
 
-	llist_add(&peer->list, &cfg->bts_peers);
+	llist_add(&peer->list, &nse->bts_peers);
 
 	INIT_LLIST_HEAD(&peer->patch_state.logical_links);
 
 	osmo_timer_setup(&peer->clean_stale_timer, clean_stale_timer_cb, peer);
-	if (peer->cfg->clean_stale_timer_freq != 0)
+	if (cfg->clean_stale_timer_freq != 0)
 		osmo_timer_schedule(&peer->clean_stale_timer,
-					peer->cfg->clean_stale_timer_freq, 0);
+					cfg->clean_stale_timer_freq, 0);
 
 	return peer;
 }
 
 void gbproxy_peer_free(struct gbproxy_peer *peer)
 {
+	OSMO_ASSERT(peer);
+
 	llist_del(&peer->list);
 	osmo_timer_del(&peer->clean_stale_timer);
 	gbproxy_delete_link_infos(peer);
@@ -229,17 +256,78 @@
 int gbproxy_cleanup_peers(struct gbproxy_config *cfg, uint16_t nsei, uint16_t bvci)
 {
 	int counter = 0;
-	struct gbproxy_peer *peer, *tmp;
+	struct gbproxy_nse *nse, *ntmp;
+	OSMO_ASSERT(cfg);
 
-	llist_for_each_entry_safe(peer, tmp, &cfg->bts_peers, list) {
-		if (peer->nsei != nsei)
+	llist_for_each_entry_safe(nse, ntmp, &cfg->nse_peers, list) {
+		struct gbproxy_peer *peer, *tmp;
+		if (nse->nsei != nsei)
 			continue;
-		if (bvci && peer->bvci != bvci)
-			continue;
+		llist_for_each_entry_safe(peer, tmp, &nse->bts_peers, list) {
+			if (bvci && peer->bvci != bvci)
+				continue;
 
-		gbproxy_peer_free(peer);
-		counter += 1;
+			gbproxy_peer_free(peer);
+			counter += 1;
+		}
 	}
 
 	return counter;
 }
+
+struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei)
+{
+	struct gbproxy_nse *nse;
+	OSMO_ASSERT(cfg);
+
+	nse = talloc_zero(tall_sgsn_ctx, struct gbproxy_nse);
+	if (!nse)
+		return NULL;
+
+	nse->nsei = nsei;
+	nse->cfg = cfg;
+
+	llist_add(&nse->list, &cfg->nse_peers);
+
+	INIT_LLIST_HEAD(&nse->bts_peers);
+
+	return nse;
+}
+
+void gbproxy_nse_free(struct gbproxy_nse *nse)
+{
+	struct gbproxy_peer *peer, *tmp;
+	OSMO_ASSERT(nse);
+
+	llist_del(&nse->list);
+
+	llist_for_each_entry_safe(peer, tmp, &nse->bts_peers, list)
+		gbproxy_peer_free(peer);
+
+	talloc_free(nse);
+}
+
+struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei)
+{
+	struct gbproxy_nse *nse;
+	OSMO_ASSERT(cfg);
+
+	llist_for_each_entry(nse, &cfg->nse_peers, list) {
+		if (nse->nsei == nsei)
+			return nse;
+	}
+
+	return NULL;
+}
+
+struct gbproxy_nse *gbproxy_nse_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei)
+{
+	struct gbproxy_nse *nse;
+	OSMO_ASSERT(cfg);
+
+	nse = gbproxy_nse_by_nsei(cfg, nsei);
+	if (!nse)
+		nse = gbproxy_nse_alloc(cfg, nsei);
+
+	return nse;
+}
\ No newline at end of file
diff --git a/src/gbproxy/gb_proxy_tlli.c b/src/gbproxy/gb_proxy_tlli.c
index e9271c2..9487459 100644
--- a/src/gbproxy/gb_proxy_tlli.c
+++ b/src/gbproxy/gb_proxy_tlli.c
@@ -183,12 +183,15 @@
 	int exceeded_max_len = 0;
 	int deleted_count = 0;
 	int check_for_age;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
-	if (peer->cfg->tlli_max_len > 0)
+	if (cfg->tlli_max_len > 0)
 		exceeded_max_len =
-			state->logical_link_count - peer->cfg->tlli_max_len;
+			state->logical_link_count - cfg->tlli_max_len;
 
-	check_for_age = peer->cfg->tlli_max_age > 0;
+	check_for_age = cfg->tlli_max_age > 0;
 
 	for (; exceeded_max_len > 0; exceeded_max_len--) {
 		struct gbproxy_link_info *link_info;
@@ -213,7 +216,7 @@
 					list);
 		age = now - link_info->timestamp;
 		/* age < 0 only happens after system time jumps, discard entry */
-		if (age <= peer->cfg->tlli_max_age && age >= 0) {
+		if (age <= cfg->tlli_max_age && age >= 0) {
 			check_for_age = 0;
 			continue;
 		}
@@ -395,6 +398,9 @@
 	int imsi_matches;
 	struct gbproxy_link_info *other_link_info;
 	enum gbproxy_match_id match_id;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	/* Make sure that there is a second entry with the same IMSI */
 	other_link_info = gbproxy_link_info_by_imsi(
@@ -419,11 +425,11 @@
 
 	/* Check, whether the IMSI matches */
 	OSMO_ASSERT(ARRAY_SIZE(link_info->is_matching) ==
-		    ARRAY_SIZE(peer->cfg->matches));
+		    ARRAY_SIZE(cfg->matches));
 	for (match_id = 0; match_id < ARRAY_SIZE(link_info->is_matching);
 	     ++match_id) {
 		imsi_matches = gbproxy_check_imsi(
-			&peer->cfg->matches[match_id],
+			&cfg->matches[match_id],
 			parse_ctx->imsi, parse_ctx->imsi_len);
 		if (imsi_matches >= 0)
 			link_info->is_matching[match_id] = imsi_matches ? true : false;
@@ -590,6 +596,9 @@
 	struct gprs_gb_parse_context *parse_ctx)
 {
 	struct gbproxy_link_info *link_info = NULL;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
 
 	link_info = gbproxy_get_link_info_dl(peer, parse_ctx);
 
@@ -613,7 +622,7 @@
 		link_info->sgsn_tlli.ptmsi = new_sgsn_ptmsi;
 		link_info->tlli.ptmsi = new_bss_ptmsi;
 	} else if (parse_ctx->tlli_enc && parse_ctx->new_ptmsi_enc && !link_info &&
-		   !peer->cfg->patch_ptmsi) {
+		   !cfg->patch_ptmsi) {
 		/* A new P-TMSI has been signalled in the message with an unknown
 		 * TLLI, create a new link_info */
 		/* TODO: Add a test case for this branch */
@@ -631,7 +640,7 @@
 		link_info->tlli.ptmsi = new_ptmsi;
 		gbproxy_attach_link_info(peer, now, link_info);
 	} else if (parse_ctx->tlli_enc && parse_ctx->llc && !link_info &&
-		   !peer->cfg->patch_ptmsi) {
+		   !cfg->patch_ptmsi) {
 		/* Unknown SGSN TLLI, create a new link_info */
 		uint32_t new_ptmsi;
 		link_info = gbproxy_link_info_alloc(peer);
@@ -677,12 +686,16 @@
 	struct gprs_gb_parse_context *parse_ctx)
 {
 	int rc = 0;
+	OSMO_ASSERT(peer->nse);
+	struct gbproxy_config *cfg = peer->nse->cfg;
+	OSMO_ASSERT(cfg);
+
 	if (parse_ctx->invalidate_tlli && link_info) {
 		int keep_info =
-			peer->cfg->keep_link_infos == GBPROX_KEEP_ALWAYS ||
-			(peer->cfg->keep_link_infos == GBPROX_KEEP_REATTACH &&
+			cfg->keep_link_infos == GBPROX_KEEP_ALWAYS ||
+			(cfg->keep_link_infos == GBPROX_KEEP_REATTACH &&
 			 parse_ctx->await_reattach) ||
-			(peer->cfg->keep_link_infos == GBPROX_KEEP_IDENTIFIED &&
+			(cfg->keep_link_infos == GBPROX_KEEP_IDENTIFIED &&
 			 link_info->imsi_len > 0);
 		if (keep_info) {
 			LOGP(DGPRS, LOGL_INFO, "Unregistering TLLI %08x\n",
diff --git a/src/gbproxy/gb_proxy_vty.c b/src/gbproxy/gb_proxy_vty.c
index 236d5d3..caad52e 100644
--- a/src/gbproxy/gb_proxy_vty.c
+++ b/src/gbproxy/gb_proxy_vty.c
@@ -73,7 +73,7 @@
 	gsm48_parse_ra(&raid, peer->ra);
 
 	vty_out(vty, "NSEI %5u, PTP-BVCI %5u, "
-		"RAI %s", peer->nsei, peer->bvci, osmo_rai_name(&raid));
+		"RAI %s", peer->nse->nsei, peer->bvci, osmo_rai_name(&raid));
 	if (peer->blocked)
 		vty_out(vty, " [BVC-BLOCKED]");
 
@@ -420,16 +420,19 @@
       GBPROXY_LINK_LIST_STR GBPROXY_CLEAN_STALE_TIMER_STR
       "Frequency at which the periodic timer is fired (in seconds)\n")
 {
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse;
 	g_cfg->clean_stale_timer_freq = (unsigned int) atoi(argv[0]);
 
 	/* Re-schedule running timers soon in case prev frequency was really big
 	   and new frequency is desired to be lower. After initial run, periodic
 	   time is used. Use random() to avoid firing timers for all peers at
 	   the same time */
-	llist_for_each_entry(peer, &g_cfg->bts_peers, list)
-		osmo_timer_schedule(&peer->clean_stale_timer,
-					random() % 5, random() % 1000000);
+	llist_for_each_entry(nse, &g_cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list)
+			osmo_timer_schedule(&peer->clean_stale_timer,
+						random() % 5, random() % 1000000);
+	}
 
 	return CMD_SUCCESS;
 }
@@ -440,11 +443,14 @@
       NO_STR GBPROXY_LINK_LIST_STR GBPROXY_CLEAN_STALE_TIMER_STR)
 
 {
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse;
 	g_cfg->clean_stale_timer_freq = 0;
 
-	llist_for_each_entry(peer, &g_cfg->bts_peers, list)
-		osmo_timer_del(&peer->clean_stale_timer);
+	llist_for_each_entry(nse, &g_cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list)
+			osmo_timer_del(&peer->clean_stale_timer);
+	}
 
 	return CMD_SUCCESS;
 }
@@ -536,17 +542,20 @@
 DEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy [stats]",
        SHOW_STR "Display information about the Gb proxy\n" "Show statistics\n")
 {
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse;
 	int show_stats = argc >= 1;
 
 	if (show_stats)
 		vty_out_rate_ctr_group(vty, "", g_cfg->ctrg);
 
-	llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
-		gbprox_vty_print_peer(vty, peer);
+	llist_for_each_entry(nse, &g_cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			gbprox_vty_print_peer(vty, peer);
 
-		if (show_stats)
-			vty_out_rate_ctr_group(vty, "  ", peer->ctrg);
+			if (show_stats)
+				vty_out_rate_ctr_group(vty, "  ", peer->ctrg);
+		}
 	}
 	return CMD_SUCCESS;
 }
@@ -554,49 +563,52 @@
 DEFUN(show_gbproxy_links, show_gbproxy_links_cmd, "show gbproxy links",
        SHOW_STR "Display information about the Gb proxy\n" "Show logical links\n")
 {
-	struct gbproxy_peer *peer;
+	struct gbproxy_nse *nse;
 	time_t now;
 	struct timespec ts = {0,};
 
 	osmo_clock_gettime(CLOCK_MONOTONIC, &ts);
 	now = ts.tv_sec;
 
-	llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
-		struct gbproxy_link_info *link_info;
-		struct gbproxy_patch_state *state = &peer->patch_state;
+	llist_for_each_entry(nse, &g_cfg->nse_peers, list) {
+		struct gbproxy_peer *peer;
+		llist_for_each_entry(peer, &nse->bts_peers, list) {
+			struct gbproxy_link_info *link_info;
+			struct gbproxy_patch_state *state = &peer->patch_state;
 
-		gbprox_vty_print_peer(vty, peer);
+			gbprox_vty_print_peer(vty, peer);
 
-		llist_for_each_entry(link_info, &state->logical_links, list) {
-			time_t age = now - link_info->timestamp;
-			struct osmo_mobile_identity mi;
-			const char *imsi_str;
+			llist_for_each_entry(link_info, &state->logical_links, list) {
+				time_t age = now - link_info->timestamp;
+				struct osmo_mobile_identity mi;
+				const char *imsi_str;
 
-			if (link_info->imsi > 0) {
-				if (osmo_mobile_identity_decode(&mi, link_info->imsi, link_info->imsi_len, false)
-				    || mi.type != GSM_MI_TYPE_IMSI)
-					imsi_str = "(invalid)";
-				else
-					imsi_str = mi.imsi;
-			} else {
-				imsi_str = "(none)";
+				if (link_info->imsi > 0) {
+					if (osmo_mobile_identity_decode(&mi, link_info->imsi, link_info->imsi_len, false)
+					    || mi.type != GSM_MI_TYPE_IMSI)
+						imsi_str = "(invalid)";
+					else
+						imsi_str = mi.imsi;
+				} else {
+					imsi_str = "(none)";
+				}
+				vty_out(vty, "  TLLI %08x, IMSI %s, AGE %d",
+					link_info->tlli.current, imsi_str, (int)age);
+
+				if (link_info->stored_msgs_len)
+					vty_out(vty, ", STORED %"PRIu32"/%"PRIu32,
+						link_info->stored_msgs_len,
+						g_cfg->stored_msgs_max_len);
+
+				if (g_cfg->route_to_sgsn2)
+					vty_out(vty, ", SGSN NSEI %d",
+						link_info->sgsn_nsei);
+
+				if (link_info->is_deregistered)
+					vty_out(vty, ", DE-REGISTERED");
+
+				vty_out(vty, "%s", VTY_NEWLINE);
 			}
-			vty_out(vty, "  TLLI %08x, IMSI %s, AGE %d",
-				link_info->tlli.current, imsi_str, (int)age);
-
-			if (link_info->stored_msgs_len)
-				vty_out(vty, ", STORED %"PRIu32"/%"PRIu32,
-					link_info->stored_msgs_len,
-					g_cfg->stored_msgs_max_len);
-
-			if (g_cfg->route_to_sgsn2)
-				vty_out(vty, ", SGSN NSEI %d",
-					link_info->sgsn_nsei);
-
-			if (link_info->is_deregistered)
-				vty_out(vty, ", DE-REGISTERED");
-
-			vty_out(vty, "%s", VTY_NEWLINE);
 		}
 	}
 	return CMD_SUCCESS;
@@ -651,15 +663,17 @@
 		if (!dry_run)
 			counter = gbproxy_cleanup_peers(g_cfg, nsei, 0);
 		else {
+			struct gbproxy_nse *nse;
 			struct gbproxy_peer *peer;
 			counter = 0;
-			llist_for_each_entry(peer, &g_cfg->bts_peers, list) {
-				if (peer->nsei != nsei)
+			llist_for_each_entry(nse, &g_cfg->nse_peers, list) {
+				if (nse->nsei != nsei)
 					continue;
-
-				vty_out(vty, "BVC: ");
-				gbprox_vty_print_peer(vty, peer);
-				counter += 1;
+				llist_for_each_entry(peer, &nse->bts_peers, list) {
+					vty_out(vty, "BVC: ");
+					gbprox_vty_print_peer(vty, peer);
+					counter += 1;
+				}
 			}
 		}
 		vty_out(vty, "%sDeleted %d BVC%s",