nat: Introduce a global IMSI barr list using red-black trees
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index 71cd121..1698fa4 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -287,6 +287,10 @@
 	/* filter */
 	char *acc_lst_name;
 
+	/* Barring of subscribers with a rb tree */
+	struct rb_root imsi_black_list;
+	char *imsi_black_list_fn;
+
 	/* number rewriting */
 	char *num_rewr_name;
 	struct llist_head num_rewr;
@@ -448,6 +452,17 @@
 
 void bsc_nat_num_rewr_entry_adapt(void *ctx, struct llist_head *head, const struct osmo_config_list *);
 
+struct bsc_nat_barr_entry {
+	struct rb_node node;
+
+	char *imsi;
+	int cm_reject_cause;
+	int lu_reject_cause;
+};
+
+int bsc_nat_barr_adapt(void *ctx, struct rb_root *rbtree, const struct osmo_config_list *);
+int bsc_nat_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu);
+
 struct ctrl_handle *bsc_nat_controlif_setup(struct bsc_nat *nat, int port);
 void bsc_nat_ctrl_del_pending(struct bsc_cmd_list *pending);
 int bsc_nat_handle_ctrlif_msg(struct bsc_connection *bsc, struct msgb *msg);
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c b/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c
index e3e63d1..5ab3a97 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_filter.c
@@ -37,6 +37,91 @@
 
 #include <osmocom/sccp/sccp.h>
 
+int bsc_nat_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu)
+{
+	struct bsc_nat_barr_entry *n;
+	n = rb_entry(root->rb_node, struct bsc_nat_barr_entry, node);
+
+	while (n) {
+		int rc = strcmp(imsi, n->imsi);
+		if (rc == 0) {
+			*cm = n->cm_reject_cause;
+			*lu = n->lu_reject_cause;
+			return 1;
+		}
+
+		n = rb_entry(
+			(rc < 0) ? n->node.rb_left : n->node.rb_right,
+			struct bsc_nat_barr_entry, node);
+	};
+
+	return 0;
+}
+
+static int insert_barr_node(struct bsc_nat_barr_entry *entry, struct rb_root *root)
+{
+	struct rb_node **new = &root->rb_node, *parent = NULL;
+
+	while (*new) {
+		int rc;
+		struct bsc_nat_barr_entry *this;
+		this = rb_entry(*new, struct bsc_nat_barr_entry, node);
+		parent = *new;
+
+		rc = strcmp(entry->imsi, this->imsi);
+		if (rc < 0)
+			new = &((*new)->rb_left);
+		else if (rc > 0)
+			new = &((*new)->rb_right);
+		else {
+			LOGP(DNAT, LOGL_ERROR,
+				"Duplicate entry for IMSI(%s)\n", entry->imsi);
+			talloc_free(entry);
+			return -1;
+		}
+	}
+
+	rb_link_node(&entry->node, parent, new);
+	rb_insert_color(&entry->node, root);
+	return 0;
+}
+
+int bsc_nat_barr_adapt(void *ctx, struct rb_root *root,
+			const struct osmo_config_list *list)
+{
+	struct osmo_config_entry *cfg_entry;
+	int err = 0;
+
+	/* free the old data */
+	while (!RB_EMPTY_ROOT(root)) {
+		struct rb_node *node = rb_first(root);
+		rb_erase(node, root);
+		talloc_free(node);
+	}
+
+	if (!list)
+		return 0;
+
+	/* now adapt the new list */
+	llist_for_each_entry(cfg_entry, &list->entry, list) {
+		struct bsc_nat_barr_entry *entry;
+		entry = talloc_zero(ctx, struct bsc_nat_barr_entry);
+		if (!entry) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Allocation of the barr entry failed.\n");
+			continue;
+		}
+
+		entry->imsi = talloc_strdup(entry, cfg_entry->mcc);
+		entry->cm_reject_cause = atoi(cfg_entry->mnc);
+		entry->lu_reject_cause = atoi(cfg_entry->option);
+		err |= insert_barr_node(entry, root);
+	}
+
+	return err;
+}
+
+
 static int lst_check_deny(struct bsc_nat_acc_lst *lst, const char *mi_string)
 {
 	struct bsc_nat_acc_lst_entry *entry;
@@ -52,43 +137,56 @@
 }
 
 /* apply white/black list */
-static int auth_imsi(struct bsc_connection *bsc, const char *mi_string,
+static int auth_imsi(struct bsc_connection *bsc, const char *imsi,
 		struct bsc_nat_reject_cause *cause)
 {
 	/*
 	 * Now apply blacklist/whitelist of the BSC and the NAT.
-	 * 1.) Allow directly if the IMSI is allowed at the BSC
-	 * 2.) Reject if the IMSI is not allowed at the BSC
-	 * 3.) Reject if the IMSI not allowed at the global level.
-	 * 4.) Allow directly if the IMSI is allowed at the global level
+	 * 1.) Check the global IMSI barr list
+	 * 2.) Allow directly if the IMSI is allowed at the BSC
+	 * 3.) Reject if the IMSI is not allowed at the BSC
+	 * 4.) Reject if the IMSI not allowed at the global level.
+	 * 5.) Allow directly if the IMSI is allowed at the global level
 	 */
+	int cm, lu;
 	struct bsc_nat_acc_lst *nat_lst = NULL;
 	struct bsc_nat_acc_lst *bsc_lst = NULL;
 
+	/* 1. global check for barred imsis */
+	if (bsc_nat_barr_find(&bsc->nat->imsi_black_list, imsi, &cm, &lu)) {
+		cause->cm_reject_cause = cm;
+		cause->lu_reject_cause = lu;
+		LOGP(DNAT, LOGL_DEBUG,
+			"Blocking subscriber IMSI %s with CM: %d LU: %d\n",
+			imsi, cm, lu);
+		return -1;
+	}
+
+
 	bsc_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->cfg->acc_lst_name);
 	nat_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->nat->acc_lst_name);
 
 
 	if (bsc_lst) {
-		/* 1. BSC allow */
-		if (bsc_nat_lst_check_allow(bsc_lst, mi_string) == 0)
+		/* 2. BSC allow */
+		if (bsc_nat_lst_check_allow(bsc_lst, imsi) == 0)
 			return 1;
 
-		/* 2. BSC deny */
-		if (lst_check_deny(bsc_lst, mi_string) == 0) {
+		/* 3. BSC deny */
+		if (lst_check_deny(bsc_lst, imsi) == 0) {
 			LOGP(DNAT, LOGL_ERROR,
-			     "Filtering %s by imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
+			     "Filtering %s by imsi_deny on bsc nr: %d.\n", imsi, bsc->cfg->nr);
 			rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_BSC_FILTER]);
 			return -2;
 		}
 
 	}
 
-	/* 3. NAT deny */
+	/* 4. NAT deny */
 	if (nat_lst) {
-		if (lst_check_deny(nat_lst, mi_string) == 0) {
+		if (lst_check_deny(nat_lst, imsi) == 0) {
 			LOGP(DNAT, LOGL_ERROR,
-			     "Filtering %s by nat imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
+			     "Filtering %s by nat imsi_deny on bsc nr: %d.\n", imsi, bsc->cfg->nr);
 			rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_NAT_FILTER]);
 			return -3;
 		}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
index 220e960..7bbc890 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
@@ -1,6 +1,6 @@
 /* OpenBSC NAT interface to quagga VTY */
-/* (C) 2010-2011 by Holger Hans Peter Freyther
- * (C) 2010-2011 by On-Waves
+/* (C) 2010-2012 by Holger Hans Peter Freyther
+ * (C) 2010-2012 by On-Waves
  * All Rights Reserved
  *
  * This program is free software; you can redistribute it and/or modify
@@ -112,6 +112,9 @@
 	vty_out(vty, " ip-dscp %d%s", _nat->bsc_ip_dscp, VTY_NEWLINE);
 	if (_nat->acc_lst_name)
 		vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE);
+	if (_nat->imsi_black_list_fn)
+		vty_out(vty, " imsi-black-list-file-name %s%s",
+			_nat->imsi_black_list_fn, VTY_NEWLINE);
 	if (_nat->ussd_lst_name)
 		vty_out(vty, " ussd-list-name %s%s", _nat->ussd_lst_name, VTY_NEWLINE);
 	if (_nat->ussd_query)
@@ -486,6 +489,42 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_nat_imsi_black_list_fn,
+      cfg_nat_imsi_black_list_fn_cmd,
+      "imsi-black-list-file-name NAME",
+      "IMSI black listing\n" "Filename IMSI and reject-cause\n")
+{
+
+	bsc_replace_string(_nat, &_nat->imsi_black_list_fn, argv[0]);
+	if (_nat->imsi_black_list_fn) {
+		int rc;
+		struct osmo_config_list *rewr = NULL;
+		rewr = osmo_config_list_parse(_nat, _nat->imsi_black_list_fn);
+		rc = bsc_nat_barr_adapt(_nat, &_nat->imsi_black_list, rewr);
+		if (rc != 0) {
+			vty_out(vty, "%%There was an error parsing the list."
+				" Please see the error log.%s", VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+
+		return CMD_SUCCESS;
+	}
+
+	bsc_nat_barr_adapt(_nat, &_nat->imsi_black_list, NULL);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_imsi_black_list_fn,
+      cfg_nat_no_imsi_black_list_fn_cmd,
+      "no imsi-black-list-file-name",
+      NO_STR "Remove the imsi-black-list\n")
+{
+	talloc_free(_nat->imsi_black_list_fn);
+	_nat->imsi_black_list_fn = NULL;
+	bsc_nat_barr_adapt(_nat, &_nat->imsi_black_list, NULL);
+	return CMD_SUCCESS;
+}
+
 static int replace_rules(struct bsc_nat *nat, char **name,
 			 struct llist_head *head, const char *file)
 {
@@ -774,6 +813,27 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(show_bar_lst,
+      show_bar_lst_cmd,
+      "show imsi-black-list",
+      SHOW_STR "IMSIs barred from the network\n")
+{
+	struct rb_node *node;
+
+	vty_out(vty, "IMSIs barred from the network:%s", VTY_NEWLINE);
+
+	for (node = rb_first(&_nat->imsi_black_list); node; node = rb_next(node)) {
+		struct bsc_nat_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_nat_barr_entry, node);
+
+		vty_out(vty, " IMSI(%s) CM-Reject-Cause(%d) LU-Reject-Cause(%d)%s",
+			entry->imsi, entry->cm_reject_cause, entry->lu_reject_cause,
+			VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
 
 DEFUN(cfg_bsc_acc_lst_name,
       cfg_bsc_acc_lst_name_cmd,
@@ -999,6 +1059,7 @@
 	install_element_ve(&test_regex_cmd);
 	install_element_ve(&show_bsc_mgcp_cmd);
 	install_element_ve(&show_acc_lst_cmd);
+	install_element_ve(&show_bar_lst_cmd);
 
 	install_element(ENABLE_NODE, &set_last_endp_cmd);
 	install_element(ENABLE_NODE, &block_new_conn_cmd);
@@ -1019,6 +1080,8 @@
 	install_element(NAT_NODE, &cfg_nat_bsc_ip_tos_cmd);
 	install_element(NAT_NODE, &cfg_nat_acc_lst_name_cmd);
 	install_element(NAT_NODE, &cfg_nat_no_acc_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_imsi_black_list_fn_cmd);
+	install_element(NAT_NODE, &cfg_nat_no_imsi_black_list_fn_cmd);
 	install_element(NAT_NODE, &cfg_nat_ussd_lst_name_cmd);
 	install_element(NAT_NODE, &cfg_nat_ussd_query_cmd);
 	install_element(NAT_NODE, &cfg_nat_ussd_token_cmd);
diff --git a/openbsc/tests/bsc-nat/Makefile.am b/openbsc/tests/bsc-nat/Makefile.am
index bd3d33f..e284851 100644
--- a/openbsc/tests/bsc-nat/Makefile.am
+++ b/openbsc/tests/bsc-nat/Makefile.am
@@ -2,7 +2,7 @@
 AM_CFLAGS=-Wall -ggdb3 $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS)
 AM_LDFLAGS = $(COVERAGE_LDFLAGS)
 
-EXTRA_DIST = bsc_nat_test.ok bsc_data.c
+EXTRA_DIST = bsc_nat_test.ok bsc_data.c barr.cfg barr_dup.cfg
 
 noinst_PROGRAMS = bsc_nat_test
 
diff --git a/openbsc/tests/bsc-nat/barr.cfg b/openbsc/tests/bsc-nat/barr.cfg
new file mode 100644
index 0000000..a9a4a2b
--- /dev/null
+++ b/openbsc/tests/bsc-nat/barr.cfg
@@ -0,0 +1,12 @@
+12123124:3:2:
+12123123:3:1:
+12123128:3:6:
+12123125:3:3:
+12123127:3:5:
+12123126:3:4:
+12123120:3:4:
+12123119:3:4:
+12123118:3:4:
+12123117:3:4:
+12123116:3:4:
+12123115:3:4:
diff --git a/openbsc/tests/bsc-nat/barr_dup.cfg b/openbsc/tests/bsc-nat/barr_dup.cfg
new file mode 100644
index 0000000..ea94631
--- /dev/null
+++ b/openbsc/tests/bsc-nat/barr_dup.cfg
@@ -0,0 +1,2 @@
+12123124:3:2:
+12123124:3:2:
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
index 59d92bd..66b4ff5 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.c
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -1153,6 +1153,72 @@
 	msgb_free(out);
 }
 
+static void test_barr_list_parsing(void)
+{
+	int rc;
+	int cm, lu;
+	struct rb_node *node;
+	struct rb_root root = RB_ROOT;
+	struct osmo_config_list *lst = osmo_config_list_parse(NULL, "barr.cfg");
+	if (lst == NULL)
+		abort();
+
+	rc = bsc_nat_barr_adapt(NULL, &root, lst);
+	if (rc != 0)
+		abort();
+	talloc_free(lst);
+
+
+	for (node = rb_first(&root); node; node = rb_next(node)) {
+		struct bsc_nat_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_nat_barr_entry, node);
+		printf("IMSI: %s CM: %d LU: %d\n", entry->imsi,
+			entry->cm_reject_cause, entry->lu_reject_cause);
+	}
+
+	/* do the look up now.. */
+	rc = bsc_nat_barr_find(&root, "12123119", &cm, &lu);
+	if (!rc) {
+		printf("Failed to find the IMSI.\n");
+		abort();
+	}
+
+	if (cm != 3 || lu != 4) {
+		printf("Found CM(%d) and LU(%d)\n", cm, lu);
+		abort();
+	}
+
+	/* empty and check that it is empty */
+	bsc_nat_barr_adapt(NULL, &root, NULL);
+	if (!RB_EMPTY_ROOT(&root)) {
+		printf("Failed to empty the list.\n");
+		abort();
+	}
+
+	/* check that dup results in an error */
+	lst = osmo_config_list_parse(NULL, "barr_dup.cfg");
+	if (lst == NULL) {
+		printf("Failed to parse list with dups\n");
+		abort();
+	}
+
+	rc = bsc_nat_barr_adapt(NULL, &root, lst);
+	if (rc != -1) {
+		printf("It should have failed due dup\n");
+		abort();
+	}
+	talloc_free(lst);
+
+	/* dump for reference */
+	for (node = rb_first(&root); node; node = rb_next(node)) {
+		struct bsc_nat_barr_entry *entry;
+		entry = rb_entry(node, struct bsc_nat_barr_entry, node);
+		printf("IMSI: %s CM: %d LU: %d\n", entry->imsi,
+			entry->cm_reject_cause, entry->lu_reject_cause);
+
+	}
+}
+
 int main(int argc, char **argv)
 {
 	sccp_set_log_area(DSCCP);
@@ -1171,6 +1237,7 @@
 	test_sms_smsc_rewrite();
 	test_sms_number_rewrite();
 	test_mgcp_allocations();
+	test_barr_list_parsing();
 
 	printf("Testing execution completed.\n");
 	return 0;
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.ok b/openbsc/tests/bsc-nat/bsc_nat_test.ok
index db37ffa..cbedc85 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.ok
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.ok
@@ -22,4 +22,17 @@
 Attempting to only rewrite the HDR
 Attempting to change nothing.
 Testing SMS TP-DA rewriting.
+IMSI: 12123115 CM: 3 LU: 4
+IMSI: 12123116 CM: 3 LU: 4
+IMSI: 12123117 CM: 3 LU: 4
+IMSI: 12123118 CM: 3 LU: 4
+IMSI: 12123119 CM: 3 LU: 4
+IMSI: 12123120 CM: 3 LU: 4
+IMSI: 12123123 CM: 3 LU: 1
+IMSI: 12123124 CM: 3 LU: 2
+IMSI: 12123125 CM: 3 LU: 3
+IMSI: 12123126 CM: 3 LU: 4
+IMSI: 12123127 CM: 3 LU: 5
+IMSI: 12123128 CM: 3 LU: 6
+IMSI: 12123124 CM: 3 LU: 2
 Testing execution completed.
diff --git a/openbsc/tests/testsuite.at b/openbsc/tests/testsuite.at
index 4c5de8d..e649f03 100644
--- a/openbsc/tests/testsuite.at
+++ b/openbsc/tests/testsuite.at
@@ -34,6 +34,8 @@
 AT_SETUP([bsc-nat])
 AT_KEYWORDS([bsc-nat])
 AT_CHECK([test "$enable_nat_test" != no || exit 77])
+cp $abs_srcdir/bsc-nat/barr.cfg .
+cp $abs_srcdir/bsc-nat/barr_dup.cfg .
 cat $abs_srcdir/bsc-nat/bsc_nat_test.ok > expout
 AT_CHECK([$abs_top_builddir/tests/bsc-nat/bsc_nat_test], [], [expout], [ignore])
 AT_CLEANUP