gprs: Track IMSI/TLLI to control APN patching

This patch adds IMSI/TLLI connection tracking and uses it to control
APN patching based on the IMSI. TLLI entries can expire based on age
and/or by limiting the TLLI list size.

VTY config-gbproxy:
  no core-access-point-name                   disable APN patching
  core-access-point-name none                 remove APN if present
  core-access-point-name APN                  replace APN if present
  core-access-point-name none match-imsi RE   remove if IMSI matches
  core-access-point-name APN match-imsi RE    replace if IMSI matches
  tlli-list max-age SECONDS                   expire after SECONDS
  no tlli-list max-age                        don't expire by age
  tlli-list max-length N                      keep N entries only
  no tlli-list max-length                     don't limit list length

RE is an extended regular expression, e.g. ^12345|^23456

Ticket: OW#1192
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
index 4f6ac24..8fee4ac 100644
--- a/openbsc/src/gprs/gb_proxy.c
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -29,6 +29,7 @@
 #include <sys/fcntl.h>
 #include <sys/stat.h>
 #include <arpa/inet.h>
+#include <time.h>
 
 #include <osmocom/core/talloc.h>
 #include <osmocom/core/select.h>
@@ -39,6 +40,8 @@
 #include <osmocom/gprs/gprs_ns.h>
 #include <osmocom/gprs/gprs_bssgp.h>
 
+#include <osmocom/gsm/gsm_utils.h>
+
 #include <openbsc/signal.h>
 #include <openbsc/debug.h>
 #include <openbsc/gb_proxy.h>
@@ -128,9 +131,18 @@
 	.ctr_desc = peer_ctr_description,
 };
 
+struct {
+	int check_imsi;
+	regex_t imsi_re_comp;
+} gbprox_global_patch_state = {0,};
+
 struct gbprox_patch_state {
 	int local_mnc;
 	int local_mcc;
+
+	/* List of TLLIs for which patching is enabled */
+	struct llist_head enabled_tllis;
+	int enabled_tllis_count;
 };
 
 struct gbprox_peer {
@@ -155,6 +167,8 @@
 /* Linked list of all Gb peers (except SGSN) */
 static LLIST_HEAD(gbprox_bts_peers);
 
+static void gbprox_delete_tllis(struct gbprox_peer *peer);
+
 /* Find the gbprox_peer by its BVCI */
 static struct gbprox_peer *peer_by_bvci(uint16_t bvci)
 {
@@ -237,6 +251,8 @@
 
 	llist_add(&peer->list, &gbprox_bts_peers);
 
+	INIT_LLIST_HEAD(&peer->patch_state.enabled_tllis);
+
 	return peer;
 }
 
@@ -244,6 +260,9 @@
 {
 	rate_ctr_group_free(peer->ctrg);
 	llist_del(&peer->list);
+
+	gbprox_delete_tllis(peer);
+
 	talloc_free(peer);
 }
 
@@ -378,6 +397,275 @@
 	return len;
 }
 
+struct gbprox_tlli_info {
+	struct llist_head list;
+
+	uint32_t tlli;
+	time_t timestamp;
+	uint8_t *mi_data;
+	size_t mi_data_len;
+};
+
+static struct gbprox_tlli_info *gbprox_find_tlli(struct gbprox_peer *peer,
+						 uint32_t tlli)
+{
+	struct gbprox_tlli_info *tlli_info;
+	struct gbprox_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list)
+		if (tlli_info->tlli == tlli)
+			return tlli_info;
+
+	return NULL;
+}
+
+static struct gbprox_tlli_info *gbprox_find_tlli_by_mi(
+	struct gbprox_peer *peer,
+	const uint8_t *mi_data,
+	size_t mi_data_len)
+{
+	struct gbprox_tlli_info *tlli_info;
+	struct gbprox_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry(tlli_info, &state->enabled_tllis, list) {
+		if (tlli_info->mi_data_len != mi_data_len)
+			continue;
+		if (memcmp(tlli_info->mi_data, mi_data, mi_data_len) != 0)
+			continue;
+
+		return tlli_info;
+	}
+
+	return NULL;
+}
+
+static void gbprox_delete_tlli(struct gbprox_peer *peer,
+			       struct gbprox_tlli_info *tlli_info)
+{
+	struct gbprox_patch_state *state = &peer->patch_state;
+
+	llist_del(&tlli_info->list);
+	talloc_free(tlli_info);
+	state->enabled_tllis_count -= 1;
+}
+
+static void gbprox_delete_tllis(struct gbprox_peer *peer)
+{
+	struct gbprox_tlli_info *tlli_info, *nxt;
+	struct gbprox_patch_state *state = &peer->patch_state;
+
+	llist_for_each_entry_safe(tlli_info, nxt, &state->enabled_tllis, list) {
+		llist_del(&tlli_info->list);
+		talloc_free(tlli_info);
+	}
+
+	OSMO_ASSERT(llist_empty(&state->enabled_tllis));
+}
+
+int gbprox_set_patch_filter(const char *filter, const char **err_msg)
+{
+	static char err_buf[300];
+	int rc;
+
+	if (gbprox_global_patch_state.check_imsi) {
+		regfree(&gbprox_global_patch_state.imsi_re_comp);
+		gbprox_global_patch_state.check_imsi = 0;
+	}
+
+	if (!filter)
+		return 0;
+
+	rc = regcomp(&gbprox_global_patch_state.imsi_re_comp, filter,
+		     REG_EXTENDED | REG_NOSUB | REG_ICASE);
+
+	if (rc == 0) {
+		gbprox_global_patch_state.check_imsi = 1;
+		return 0;
+	}
+
+	if (err_msg) {
+		regerror(rc, &gbprox_global_patch_state.imsi_re_comp,
+			 err_buf, sizeof(err_buf));
+		*err_msg = err_buf;
+	}
+
+	return -1;
+}
+
+static int gbprox_check_imsi(struct gbprox_peer *peer,
+			     const uint8_t *imsi, size_t imsi_len)
+{
+	char mi_buf[200];
+	int rc;
+
+	if (!gbprox_global_patch_state.check_imsi)
+		return 1;
+
+	rc = gsm48_mi_to_string(mi_buf, sizeof(mi_buf), imsi, imsi_len);
+	if (rc < 1) {
+		LOGP(DGPRS, LOGL_NOTICE, "Invalid IMSI %s\n",
+		     osmo_hexdump(imsi, imsi_len));
+		return -1;
+	}
+
+	LOGP(DGPRS, LOGL_DEBUG, "Checking IMSI '%s' (%d)\n", mi_buf, rc);
+
+	rc = regexec(&gbprox_global_patch_state.imsi_re_comp, mi_buf, 0, NULL, 0);
+	if (rc == REG_NOMATCH) {
+		LOGP(DGPRS, LOGL_INFO,
+		       "IMSI '%s' doesn't match pattern '%s'\n",
+		       mi_buf, gbcfg.match_re);
+		return 0;
+	}
+
+	return 1;
+}
+
+static int gbprox_remove_stale_ttlis(struct gbprox_peer *peer, time_t now)
+{
+	struct gbprox_patch_state *state = &peer->patch_state;
+	struct gbprox_tlli_info *tlli_info = NULL, *nxt;
+	int count = 0;
+	int deleted_count = 0;
+
+	llist_for_each_entry_safe(tlli_info, nxt, &state->enabled_tllis, list) {
+		int is_stale = 0;
+		time_t age = now - tlli_info->timestamp;
+
+		count += 1;
+
+		if (gbcfg.tlli_max_len > 0)
+			is_stale = is_stale || count > gbcfg.tlli_max_len;
+
+		if (gbcfg.tlli_max_age > 0)
+			is_stale = is_stale || age > gbcfg.tlli_max_age;
+
+		if (!is_stale)
+			continue;
+
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list (stale)\n",
+		     tlli_info->tlli);
+
+		gbprox_delete_tlli(peer, tlli_info);
+		tlli_info = NULL;
+
+		deleted_count += 1;
+	}
+
+	return deleted_count;
+}
+
+static void gbprox_register_tlli(struct gbprox_peer *peer, uint32_t tlli,
+				 const uint8_t *imsi, size_t imsi_len)
+{
+	struct gbprox_patch_state *state = &peer->patch_state;
+	struct gbprox_tlli_info *tlli_info;
+	int enable_patching;
+	time_t now = 0;
+
+	if (gprs_tlli_type(tlli) != TLLI_LOCAL)
+		return;
+
+	if (!imsi || (imsi[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI)
+		return;
+
+	if (!gbprox_global_patch_state.check_imsi)
+		return;
+
+	tlli_info = gbprox_find_tlli(peer, tlli);
+
+	/* Check, whether the IMSI matches */
+	enable_patching = gbprox_check_imsi(peer, imsi, imsi_len);
+
+	if (enable_patching < 0)
+		return;
+
+	if (!tlli_info) {
+		tlli_info = gbprox_find_tlli_by_mi(peer, imsi, imsi_len);
+
+		if (tlli_info) {
+			/* TLLI has changed somehow, adjust it */
+			LOGP(DGPRS, LOGL_INFO,
+			     "The TLLI has changed from %08x to %08x\n",
+			     tlli_info->tlli, tlli);
+			tlli_info->tlli = tlli;
+		}
+	}
+
+	if (!tlli_info) {
+		if (!enable_patching)
+			return;
+
+		LOGP(DGPRS, LOGL_INFO, "Adding TLLI %08x to list\n", tlli);
+		tlli_info = talloc_zero(peer, struct gbprox_tlli_info);
+		tlli_info->tlli = tlli;
+	} else {
+		llist_del(&tlli_info->list);
+		OSMO_ASSERT(state->enabled_tllis_count > 0);
+		state->enabled_tllis_count -= 1;
+	}
+
+	OSMO_ASSERT(tlli_info != NULL);
+
+	if (enable_patching) {
+		now = time(NULL);
+
+		tlli_info->timestamp = now;
+		llist_add(&tlli_info->list, &state->enabled_tllis);
+		state->enabled_tllis_count += 1;
+
+		gbprox_remove_stale_ttlis(peer, now);
+
+		if (tlli_info != llist_entry(state->enabled_tllis.next,
+					     struct gbprox_tlli_info, list)) {
+			LOGP(DGPRS, LOGL_ERROR,
+			     "Unexpectedly removed new TLLI entry as stale, "
+			     "TLLI %08x\n", tlli);
+			tlli_info = NULL;
+		}
+	} else {
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list (patching no longer enabled)\n",
+		     tlli);
+		talloc_free(tlli_info);
+		tlli_info = NULL;
+	}
+
+	if (tlli_info) {
+		tlli_info->mi_data_len = imsi_len;
+		tlli_info->mi_data =
+			talloc_realloc_size(tlli_info, tlli_info->mi_data, imsi_len);
+		OSMO_ASSERT(tlli_info->mi_data != NULL);
+		memcpy(tlli_info->mi_data, imsi, imsi_len);
+	}
+}
+
+static void gbprox_unregister_tlli(struct gbprox_peer *peer, uint32_t tlli)
+{
+	struct gbprox_tlli_info *tlli_info;
+
+	tlli_info = gbprox_find_tlli(peer, tlli);
+	if (tlli_info) {
+		LOGP(DGPRS, LOGL_INFO,
+		     "Removing TLLI %08x from list\n",
+		     tlli);
+		llist_del(&tlli_info->list);
+		talloc_free(tlli_info);
+	}
+}
+
+static int gbprox_check_tlli(struct gbprox_peer *peer, uint32_t tlli)
+{
+	LOGP(DGPRS, LOGL_INFO, "Checking TLLI %08x, class: %d\n",
+	     tlli, gprs_tlli_type(tlli));
+	if (gprs_tlli_type(tlli) != TLLI_LOCAL)
+		return 0;
+
+	return !gbprox_global_patch_state.check_imsi ||
+		gbprox_find_tlli(peer, tlli) != NULL;
+}
+
 /* check whether patching is enabled at this level */
 static int patching_is_enabled(enum gbproxy_patch_mode need_at_least)
 {
@@ -715,7 +1003,7 @@
 
 static int gbprox_patch_dtap(struct msgb *msg, uint8_t *data, size_t data_len,
 			     struct gbprox_peer *peer, int to_bss,
-			     int *len_change)
+			     uint32_t tlli, int *len_change)
 {
 	struct gsm48_hdr *g48h;
 
@@ -767,8 +1055,16 @@
 			break;
 		if (gbcfg.core_apn == NULL)
 			break;
+		if (!gbprox_check_tlli(peer, tlli))
+			break;
 		return gbprox_patch_gsm_act_pdp_req(msg, data, data_len,
 						    peer, to_bss, len_change);
+
+	case GSM48_MT_GMM_DETACH_ACK:
+	case GSM48_MT_GMM_DETACH_REQ:
+		gbprox_unregister_tlli(peer, tlli);
+		break;
+
 	default:
 		break;
 	};
@@ -777,7 +1073,9 @@
 }
 
 static void gbprox_patch_llc(struct msgb *msg, uint8_t *llc, size_t llc_len,
-			     struct gbprox_peer *peer, int to_bss)
+			     struct gbprox_peer *peer, int to_bss,
+			     struct bssgp_ud_hdr *budh,
+			     struct tlv_parsed *bssgp_tp)
 {
 	struct gprs_llc_hdr_parsed ghp = {0};
 	int rc;
@@ -787,6 +1085,7 @@
 	int len_change = 0;
 	const char *err_info = NULL;
 	int err_ctr = -1;
+	uint32_t tlli = budh ? ntohl(budh->tlli) : 0;
 
 	/* parse LLC */
 	rc = gprs_llc_hdr_parse(&ghp, llc, llc_len);
@@ -806,6 +1105,12 @@
 	if (ghp.sapi != GPRS_SAPI_GMM)
 		return;
 
+	if (gbcfg.core_apn && to_bss && tlli &&
+	    TLVP_PRESENT(bssgp_tp, BSSGP_IE_IMSI))
+		gbprox_register_tlli(peer, tlli,
+				     TLVP_VAL(bssgp_tp, BSSGP_IE_IMSI),
+				     TLVP_LEN(bssgp_tp, BSSGP_IE_IMSI));
+
 	if (ghp.cmd != GPRS_LLC_UI)
 		return;
 
@@ -829,7 +1134,7 @@
 	data_len = ghp.data_len;
 
 	rc = gbprox_patch_dtap(msg, data, data_len, peer, to_bss,
-			       &len_change);
+			       tlli, &len_change);
 
 	if (rc > 0) {
 		llc_len += len_change;
@@ -875,10 +1180,9 @@
 				       struct gbprox_peer *peer, int to_bss)
 {
 	struct bssgp_normal_hdr *bgph;
-	struct bssgp_ud_hdr *budh;
+	struct bssgp_ud_hdr *budh = NULL;
 	struct tlv_parsed tp;
 	uint8_t pdu_type;
-	struct gbprox_patch_state *state = NULL;
 	uint8_t *data;
 	size_t data_len;
 
@@ -886,11 +1190,12 @@
 		return;
 
 	bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
-	budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
 	pdu_type = bgph->pdu_type;
 
 	if (pdu_type == BSSGP_PDUT_UL_UNITDATA ||
 	    pdu_type == BSSGP_PDUT_DL_UNITDATA) {
+		budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
+		bgph = NULL;
 		data = budh->data;
 		data_len = msgb_bssgp_len(msg) - sizeof(*budh);
 	} else {
@@ -919,11 +1224,6 @@
 		return;
 	}
 
-	state = &peer->patch_state;
-
-	if (to_bss && !state->local_mcc && !state->local_mnc)
-		return;
-
 	if (TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) {
 		gbprox_patch_raid((uint8_t *)TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
 				  peer, to_bss, "ROUTING_AREA");
@@ -937,7 +1237,7 @@
 	    patching_is_enabled(GBPROX_PATCH_LLC_ATTACH_REQ)) {
 		uint8_t *llc = (uint8_t *)TLVP_VAL(&tp, BSSGP_IE_LLC_PDU);
 		size_t llc_len = TLVP_LEN(&tp, BSSGP_IE_LLC_PDU);
-		gbprox_patch_llc(msg, llc, llc_len, peer, to_bss);
+		gbprox_patch_llc(msg, llc, llc_len, peer, to_bss, budh, &tp);
 		/* Note that the tp struct might contain invalid pointers here
 		 * if the LLC field has changed its size */
 	}
diff --git a/openbsc/src/gprs/gb_proxy_vty.c b/openbsc/src/gprs/gb_proxy_vty.c
index 6d24165..36d959a 100644
--- a/openbsc/src/gprs/gb_proxy_vty.c
+++ b/openbsc/src/gprs/gb_proxy_vty.c
@@ -72,15 +72,24 @@
 	if (g_cfg->core_apn != NULL) {
 	       if (g_cfg->core_apn_size > 0) {
 		       char str[500] = {0};
-		       vty_out(vty, " core-access-point-name %s%s",
+		       vty_out(vty, " core-access-point-name %s",
 			       gbprox_apn_to_str(str, g_cfg->core_apn,
-						 g_cfg->core_apn_size),
-			       VTY_NEWLINE);
+						 g_cfg->core_apn_size));
 	       } else {
-		       vty_out(vty, " core-access-point-name%s",
-			       VTY_NEWLINE);
+		       vty_out(vty, " core-access-point-name none");
 	       }
+	       if (g_cfg->match_re)
+		       vty_out(vty, " match-imsi %s%s",
+			       g_cfg->match_re, VTY_NEWLINE);
+	       else
+		       vty_out(vty, "%s", VTY_NEWLINE);
 	}
+	if (g_cfg->tlli_max_age > 0)
+		vty_out(vty, " tlli-list max-age %d%s",
+			g_cfg->tlli_max_age, VTY_NEWLINE);
+	if (g_cfg->tlli_max_len > 0)
+		vty_out(vty, " tlli-list max-length %d%s",
+			g_cfg->tlli_max_len, VTY_NEWLINE);
 
 	if (g_cfg->patch_mode != GBPROX_PATCH_DEFAULT)
 		vty_out(vty, " patch-mode %s%s",
@@ -153,50 +162,140 @@
 }
 
 #define GBPROXY_CORE_APN_STR "Use this access point name (APN) for the backbone\n"
+#define GBPROXY_CORE_APN_ARG_STR "Replace APN by this string\n" "Remove APN\n"
 
-DEFUN(cfg_gbproxy_core_apn_remove,
-      cfg_gbproxy_core_apn_remove_cmd,
-      "core-access-point-name",
-      GBPROXY_CORE_APN_STR)
+static int set_core_apn(struct vty *vty, const char *apn, const char *filter)
 {
-	talloc_free(g_cfg->core_apn);
-	/* TODO: replace NULL */
-	g_cfg->core_apn = talloc_zero_size(NULL, 2);
-	g_cfg->core_apn_size = 0;
-	return CMD_SUCCESS;
-}
+	const char *err_msg = NULL;
+	int apn_len;
 
-DEFUN(cfg_gbproxy_core_apn,
-      cfg_gbproxy_core_apn_cmd,
-      "core-access-point-name APN",
-      GBPROXY_CORE_APN_STR "Replacement APN\n")
-{
-	int apn_len = strlen(argv[0]) + 1;
+	if (!apn) {
+		talloc_free(g_cfg->core_apn);
+		g_cfg->core_apn = NULL;
+		g_cfg->core_apn_size = 0;
+		gbprox_set_patch_filter(NULL, NULL);
+		return CMD_SUCCESS;
+	}
 
-	if (apn_len > 100) {
+	apn_len = strlen(apn);
+
+	if (apn_len >= 100) {
 		vty_out(vty, "APN string too long (max 99 chars)%s",
 			VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	/* TODO: replace NULL */
-	g_cfg->core_apn = talloc_realloc_size(NULL, g_cfg->core_apn, apn_len);
-	g_cfg->core_apn_size = gbprox_str_to_apn(g_cfg->core_apn, argv[0], apn_len);
+	if (!filter) {
+		gbprox_set_patch_filter(NULL, NULL);
+	} else if (gbprox_set_patch_filter(filter, &err_msg) != 0) {
+		vty_out(vty, "Match expression invalid: %s%s",
+			err_msg, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	talloc_free(g_cfg->match_re);
+	if (filter)
+		/* TODO: replace NULL */
+		g_cfg->match_re = talloc_strdup(NULL, filter);
+	else
+		g_cfg->match_re = NULL;
+
+	if (apn_len == 0) {
+		talloc_free(g_cfg->core_apn);
+		/* TODO: replace NULL */
+		g_cfg->core_apn = talloc_zero_size(NULL, 2);
+		g_cfg->core_apn_size = 0;
+	} else {
+		/* TODO: replace NULL */
+		g_cfg->core_apn =
+			talloc_realloc_size(NULL, g_cfg->core_apn, apn_len + 1);
+		g_cfg->core_apn_size =
+			gbprox_str_to_apn(g_cfg->core_apn, apn, apn_len + 1);
+	}
 
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_gbproxy_core_apn,
+      cfg_gbproxy_core_apn_cmd,
+      "core-access-point-name (APN|none)",
+      GBPROXY_CORE_APN_STR GBPROXY_CORE_APN_ARG_STR)
+{
+	if (strcmp(argv[0], "none") == 0)
+		return set_core_apn(vty, "", NULL);
+	else
+		return set_core_apn(vty, argv[0], NULL);
+}
+
+DEFUN(cfg_gbproxy_core_apn_match,
+      cfg_gbproxy_core_apn_match_cmd,
+      "core-access-point-name (APN|none) match-imsi .REGEXP",
+      GBPROXY_CORE_APN_STR GBPROXY_CORE_APN_ARG_STR
+      "Only modify if the IMSI matches\n"
+      "Regular expression for the match\n")
+{
+	if (strcmp(argv[0], "none") == 0)
+		return set_core_apn(vty, "", argv[1]);
+	else
+		return set_core_apn(vty, argv[0], argv[1]);
+}
+
 DEFUN(cfg_gbproxy_no_core_apn,
       cfg_gbproxy_no_core_apn_cmd,
       "no core-access-point-name",
       NO_STR GBPROXY_CORE_APN_STR)
 {
-	talloc_free(g_cfg->core_apn);
-	g_cfg->core_apn = NULL;
-	g_cfg->core_apn_size = 0;
+	return set_core_apn(vty, NULL, NULL);
+}
+
+#define GBPROXY_TLLI_LIST_STR "Set TLLI list parameters\n"
+#define GBPROXY_MAX_AGE_STR "Limit maximum age\n"
+
+DEFUN(cfg_gbproxy_tlli_list_max_age,
+      cfg_gbproxy_tlli_list_max_age_cmd,
+      "tlli-list max-age <1-999999>",
+      GBPROXY_TLLI_LIST_STR GBPROXY_MAX_AGE_STR
+      "Maximum age in seconds\n")
+{
+	g_cfg->tlli_max_age = atoi(argv[0]);
+
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_gbproxy_tlli_list_no_max_age,
+      cfg_gbproxy_tlli_list_no_max_age_cmd,
+      "no tlli-list max-age",
+      NO_STR GBPROXY_TLLI_LIST_STR GBPROXY_MAX_AGE_STR)
+{
+	g_cfg->tlli_max_age = 0;
+
+	return CMD_SUCCESS;
+}
+
+#define GBPROXY_MAX_LEN_STR "Limit list length\n"
+
+DEFUN(cfg_gbproxy_tlli_list_max_len,
+      cfg_gbproxy_tlli_list_max_len_cmd,
+      "tlli-list max-length <1-99999>",
+      GBPROXY_TLLI_LIST_STR GBPROXY_MAX_LEN_STR
+      "Maximum number of TLLIs in the list\n")
+{
+	g_cfg->tlli_max_len = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_gbproxy_tlli_list_no_max_len,
+      cfg_gbproxy_tlli_list_no_max_len_cmd,
+      "no tlli-list max-length",
+      NO_STR GBPROXY_TLLI_LIST_STR GBPROXY_MAX_LEN_STR)
+{
+	g_cfg->tlli_max_len = 0;
+
+	return CMD_SUCCESS;
+}
+
+
 DEFUN(cfg_gbproxy_patch_mode,
       cfg_gbproxy_patch_mode_cmd,
       "patch-mode (default|bssgp|llc-attach-req|llc-attach|llc-gmm|llc-gsm|llc)",
@@ -231,11 +330,15 @@
 	install_element(GBPROXY_NODE, &cfg_nsip_sgsn_nsei_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_core_mcc_cmd);
 	install_element(GBPROXY_NODE, &cfg_gbproxy_core_mnc_cmd);
-	install_element(GBPROXY_NODE, &cfg_gbproxy_core_apn_remove_cmd);
 	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_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_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);
 
 	return 0;