gbproxy: Keep tlli_info after detach

Currently a tlli_info entry is deleted when the TLLI gets invalidated
by a Detach message.

This patch introduces the possibility to keep tlli_info entries in
the list. Those entries then have cleared TLLI fields, are marked as
de-registered, and can only be retrieved by a message containing an
IMSI or a P-TMSI.

The following VTY configuration commands are added to the gbproxy
node:
  - tlli-list keep-mode never : Don't keep the entries (default)
  - tlli-list keep-mode re-attach : Only keep them, when a Detach
    message with re-attach required has been received
  - tlli-list keep-mode identified : Only keep entries which are
    associated with an IMSI
  - tlli-list keep-mode always : Keep all entries

Note that at least one of max-length or max-age should be set when
this feature is used to limit the number of entries.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy_tlli.c b/openbsc/src/gprs/gb_proxy_tlli.c
index 78459b7..509e489 100644
--- a/openbsc/src/gprs/gb_proxy_tlli.c
+++ b/openbsc/src/gprs/gb_proxy_tlli.c
@@ -316,17 +316,28 @@
 	gbproxy_attach_tlli_info(peer, now, tlli_info);
 }
 
-static void gbproxy_unregister_tlli(struct gbproxy_peer *peer, uint32_t tlli)
+static void gbproxy_unregister_tlli(struct gbproxy_peer *peer,
+				    struct gbproxy_tlli_info *tlli_info)
 {
-	struct gbproxy_tlli_info *tlli_info;
+	if (!tlli_info)
+		return;
 
-	tlli_info = gbproxy_find_tlli(peer, tlli);
-	if (tlli_info) {
+	if (tlli_info->tlli.ptmsi == GSM_RESERVED_TMSI && !tlli_info->imsi_len) {
 		LOGP(DGPRS, LOGL_INFO,
-		     "Removing TLLI %08x from list\n",
-		     tlli);
+		     "Removing TLLI %08x from list (P-TMSI or IMSI are not set)\n",
+		     tlli_info->tlli.current);
 		gbproxy_delete_tlli(peer, tlli_info);
+		return;
 	}
+
+	tlli_info->tlli.current = 0;
+	tlli_info->tlli.assigned = 0;
+	tlli_info->sgsn_tlli.current = 0;
+	tlli_info->sgsn_tlli.assigned = 0;
+
+	tlli_info->is_deregistered = 1;
+
+	return;
 }
 
 int gbproxy_check_tlli(struct gbproxy_peer *peer,
@@ -338,20 +349,44 @@
 	return tlli_info != NULL && tlli_info->enable_patching;
 }
 
+struct gbproxy_tlli_info *gbproxy_get_tlli_info_ul(
+	struct gbproxy_peer *peer,
+	struct gprs_gb_parse_context *parse_ctx)
+{
+	struct gbproxy_tlli_info *tlli_info = NULL;
+
+	if (parse_ctx->tlli_enc)
+		tlli_info = gbproxy_find_tlli(peer, parse_ctx->tlli);
+
+	if (!tlli_info && parse_ctx->imsi)
+		tlli_info = gbproxy_find_tlli_by_imsi(
+			peer, parse_ctx->imsi, parse_ctx->imsi_len);
+
+	if (!tlli_info && parse_ctx->ptmsi_enc && !parse_ctx->old_raid_is_foreign) {
+		uint32_t bss_ptmsi;
+		if (!gprs_parse_mi_tmsi(parse_ctx->ptmsi_enc, GSM48_TMSI_LEN,
+					&bss_ptmsi))
+			LOGP(DGPRS, LOGL_ERROR,
+			     "Failed to parse P-TMSI (TLLI is %08x)\n",
+			     parse_ctx->tlli);
+		else
+			tlli_info = gbproxy_find_tlli_by_ptmsi(peer, bss_ptmsi);
+	}
+
+	if (tlli_info)
+		tlli_info->is_deregistered = 0;
+
+	return tlli_info;
+}
+
 struct gbproxy_tlli_info *gbproxy_update_tlli_state_ul(
 	struct gbproxy_peer *peer,
 	time_t now,
 	struct gprs_gb_parse_context *parse_ctx)
 {
-	struct gbproxy_tlli_info *tlli_info = NULL;
+	struct gbproxy_tlli_info *tlli_info;
 
-	if (parse_ctx->tlli_enc) {
-		tlli_info = gbproxy_find_tlli(peer, parse_ctx->tlli);
-
-		if (!tlli_info && parse_ctx->imsi)
-			tlli_info = gbproxy_find_tlli_by_imsi(
-				peer, parse_ctx->imsi, parse_ctx->imsi_len);
-	}
+	tlli_info = gbproxy_get_tlli_info_ul(peer, parse_ctx);
 
 	if (parse_ctx->tlli_enc && parse_ctx->llc) {
 		uint32_t sgsn_tlli;
@@ -366,6 +401,13 @@
 							   parse_ctx->tlli);
 			tlli_info->sgsn_tlli.current = sgsn_tlli;
 			tlli_info->tlli.current = parse_ctx->tlli;;
+		} else if (!tlli_info->tlli.current) {
+			/* New TLLI (info found by IMSI or P-TMSI) */
+			tlli_info->tlli.current = parse_ctx->tlli;
+			tlli_info->sgsn_tlli.current =
+				gbproxy_make_sgsn_tlli(peer, tlli_info,
+						       parse_ctx->tlli);
+			gbproxy_touch_tlli(peer, tlli_info, now);
 		} else {
 			sgsn_tlli = gbproxy_map_tlli(parse_ctx->tlli, tlli_info, 0);
 			if (!sgsn_tlli)
@@ -512,8 +554,22 @@
 	time_t now,
 	struct gprs_gb_parse_context *parse_ctx)
 {
-	if (parse_ctx->invalidate_tlli) {
-		gbproxy_unregister_tlli(peer, parse_ctx->tlli);
+	if (parse_ctx->invalidate_tlli && tlli_info) {
+		int keep_info =
+			peer->cfg->keep_tlli_infos == GBPROX_KEEP_ALWAYS ||
+			(peer->cfg->keep_tlli_infos == GBPROX_KEEP_REATTACH &&
+			 parse_ctx->await_reattach) ||
+			(peer->cfg->keep_tlli_infos == GBPROX_KEEP_IDENTIFIED &&
+			 tlli_info->imsi_len > 0);
+		if (keep_info) {
+			LOGP(DGPRS, LOGL_INFO, "Unregistering TLLI %08x\n",
+			     tlli_info->tlli.current);
+			gbproxy_unregister_tlli(peer, tlli_info);
+		} else {
+			LOGP(DGPRS, LOGL_INFO, "Removing TLLI %08x from list\n",
+			     tlli_info->tlli.current);
+			gbproxy_delete_tlli(peer, tlli_info);
+		}
 	} else if (parse_ctx->to_bss && parse_ctx->tlli_enc &&
 		   parse_ctx->new_ptmsi_enc && tlli_info) {
 		/* A new PTMSI has been signaled in the message,
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
index efdf751..ee39289 100644
--- a/openbsc/src/gprs/gb_proxy_vty.c
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -50,6 +50,14 @@
 	1,
 };
 
+static const struct value_string keep_modes[] = {
+	{GBPROX_KEEP_NEVER, "never"},
+	{GBPROX_KEEP_REATTACH, "re-attach"},
+	{GBPROX_KEEP_IDENTIFIED, "identified"},
+	{GBPROX_KEEP_ALWAYS, "always"},
+	{0, NULL}
+};
+
 static void gbprox_vty_print_peer(struct vty *vty, struct gbproxy_peer *peer)
 {
 	struct gprs_ra_id raid;
@@ -106,6 +114,10 @@
 	if (g_cfg->tlli_max_len > 0)
 		vty_out(vty, " tlli-list max-length %d%s",
 			g_cfg->tlli_max_len, VTY_NEWLINE);
+	vty_out(vty, " tlli-list keep-mode %s%s",
+		get_value_string(keep_modes, g_cfg->keep_tlli_infos),
+		VTY_NEWLINE);
+
 
 	return CMD_SUCCESS;
 }
@@ -398,6 +410,22 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_gbproxy_tlli_list_keep_mode,
+      cfg_gbproxy_tlli_list_keep_mode_cmd,
+      "tlli-list keep-mode (never|re-attach|identified|always)",
+      GBPROXY_TLLI_LIST_STR "How to keep entries for detached TLLIs\n"
+      "Discard entry immediately after detachment\n"
+      "Keep entry if a re-attachment has be requested\n"
+      "Keep entry if it associated with an IMSI\n"
+      "Don't discard entries after detachment\n")
+{
+	int val = get_string_value(keep_modes, argv[0]);
+	OSMO_ASSERT(val >= GBPROX_KEEP_NEVER && val <= GBPROX_KEEP_ALWAYS);
+	g_cfg->keep_tlli_infos = val;
+
+	return CMD_SUCCESS;
+}
+
 
 DEFUN(show_gbproxy, show_gbproxy_cmd, "show gbproxy [stats]",
        SHOW_STR "Display information about the Gb proxy\n" "Show statistics\n")
@@ -654,6 +682,7 @@
 	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_tlli_list_keep_mode_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_match_imsi_cmd);
diff --git a/openbsc/src/gprs/gprs_gb_parse.c b/openbsc/src/gprs/gprs_gb_parse.c
index f361951..7ae792a 100644
--- a/openbsc/src/gprs/gprs_gb_parse.c
+++ b/openbsc/src/gprs/gprs_gb_parse.c
@@ -224,6 +224,7 @@
 {
 	uint8_t *value;
 	size_t value_len;
+	int detach_type;
 	int power_off;
 
 	parse_ctx->llc_msg_name = "DETACH_REQ";
@@ -234,9 +235,14 @@
 		/* invalid */
 		return 0;
 
+	detach_type = *value & 0x07;
 	power_off = *value & 0x08 ? 1 : 0;
 
-	if (!parse_ctx->to_bss) {
+	if (parse_ctx->to_bss) {
+		/* Network originated */
+		if (detach_type == GPRS_DET_T_MT_REATT_REQ)
+			parse_ctx->await_reattach = 1;
+	} else {
 		/* Mobile originated */
 
 		if (power_off)
@@ -651,6 +657,10 @@
 		LOGP(DGPRS, LOGL_DEBUG, "%s invalidate", sep);
 		sep = ",";
 	}
+	if (parse_ctx->await_reattach) {
+		LOGP(DGPRS, LOGL_DEBUG, "%s re-attach", sep);
+		sep = ",";
+	}
 
 	LOGP(DGPRS, LOGL_DEBUG, "\n");
 }