nat: Allow to specify multiple entries in the access-list...

Inside the access-list we have a list of entries that have
either one allow or one deny rule... we do not allow to remove
a single rule but one has to remove the whole list, in that case
talloc will handle cleaning all entries.

Right now the matching is O(n*m) as we traverse the list
(multiple times) and run the regexp multiple times. One
way to make it faster would be to concat all regexps into
one.
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index 43b6600..cb0f761 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -209,6 +209,11 @@
 
 	/* the name of the list */
 	const char *name;
+	struct llist_head fltr_list;
+};
+
+struct bsc_nat_acc_lst_entry {
+	struct llist_head list;
 
 	/* the filter */
 	char *imsi_allow;
@@ -328,4 +333,6 @@
 struct bsc_nat_acc_lst *bsc_nat_acc_lst_get(struct bsc_nat *nat, const char *name);
 void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst);
 
+struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *);
+
 #endif
diff --git a/openbsc/src/nat/bsc_nat_utils.c b/openbsc/src/nat/bsc_nat_utils.c
index 663d8bc..5bb8fb9 100644
--- a/openbsc/src/nat/bsc_nat_utils.c
+++ b/openbsc/src/nat/bsc_nat_utils.c
@@ -195,6 +195,34 @@
 	return 0;
 }
 
+static int lst_check_allow(struct bsc_nat_acc_lst *lst, const char *mi_string)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_allow)
+			continue;
+		if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
+static int lst_check_deny(struct bsc_nat_acc_lst *lst, const char *mi_string)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (!entry->imsi_deny)
+			continue;
+		if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0)
+			return 0;
+	}
+
+	return 1;
+}
+
 /* apply white/black list */
 static int auth_imsi(struct bsc_connection *bsc, const char *mi_string)
 {
@@ -212,24 +240,22 @@
 	nat_lst = bsc_nat_acc_lst_find(bsc->nat, bsc->nat->acc_lst_name);
 
 
-	/* 1. BSC deny */
-	if (bsc_lst && bsc_lst->imsi_deny) {
-		if (regexec(&bsc_lst->imsi_deny_re, mi_string, 0, NULL, 0) == 0) {
+	if (bsc_lst) {
+		/* 1. BSC deny */
+		if (lst_check_deny(bsc_lst, mi_string) == 0) {
 			LOGP(DNAT, LOGL_ERROR,
 			     "Filtering %s by imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
 			return -2;
 		}
-	}
 
-	/* 2. BSC allow */
-	if (bsc_lst && bsc_lst->imsi_allow) {
-		if (regexec(&bsc_lst->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
+		/* 2. BSC allow */
+		if (lst_check_allow(bsc_lst, mi_string) == 0)
 			return 0;
 	}
 
 	/* 3. NAT deny */
-	if (nat_lst && nat_lst->imsi_deny) {
-		if (regexec(&nat_lst->imsi_deny_re, mi_string, 0, NULL, 0) == 0) {
+	if (nat_lst) {
+		if (lst_check_deny(nat_lst, mi_string) == 0) {
 			LOGP(DNAT, LOGL_ERROR,
 			     "Filtering %s by nat imsi_deny on bsc nr: %d.\n", mi_string, bsc->cfg->nr);
 			return -3;
@@ -440,6 +466,7 @@
 		return NULL;
 	}
 
+	INIT_LLIST_HEAD(&lst->fltr_list);
 	lst->name = talloc_strdup(lst, name);
 	llist_add(&lst->list, &nat->access_lists);
 	return lst;
@@ -449,4 +476,16 @@
 {
 	llist_del(&lst->list);
 	talloc_free(lst);
+}
+
+struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *lst)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	entry = talloc_zero(lst, struct bsc_nat_acc_lst_entry);
+	if (!entry)
+		return NULL;
+
+	llist_add(&entry->list, &lst->fltr_list);
+	return entry;
 }
\ No newline at end of file
diff --git a/openbsc/src/nat/bsc_nat_vty.c b/openbsc/src/nat/bsc_nat_vty.c
index fdd7886..22375a8 100644
--- a/openbsc/src/nat/bsc_nat_vty.c
+++ b/openbsc/src/nat/bsc_nat_vty.c
@@ -49,6 +49,20 @@
 	1,
 };
 
+static void write_acc_lst(struct vty *vty, struct bsc_nat_acc_lst *lst)
+{
+	struct bsc_nat_acc_lst_entry *entry;
+
+	llist_for_each_entry(entry, &lst->fltr_list, list) {
+		if (entry->imsi_allow)
+			vty_out(vty, " access-list %s imsi-allow %s%s",
+				lst->name, entry->imsi_allow, VTY_NEWLINE);
+		if (entry->imsi_deny)
+			vty_out(vty, " access-list %s imsi-deny %s%s",
+				lst->name, entry->imsi_deny, VTY_NEWLINE);
+	}
+}
+
 static int config_write_nat(struct vty *vty)
 {
 	struct bsc_nat_acc_lst *lst;
@@ -66,12 +80,7 @@
 		vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE);
 
 	llist_for_each_entry(lst, &_nat->access_lists, list) {
-		if (lst->imsi_allow)
-			vty_out(vty, " access-list %s imsi-allow %s%s",
-				lst->name, lst->imsi_allow, VTY_NEWLINE);
-		if (lst->imsi_deny)
-			vty_out(vty, " access-list %s imsi-deny %s%s",
-				lst->name, lst->imsi_deny, VTY_NEWLINE);
+		write_acc_lst(vty, lst);
 	}
 
 	return CMD_SUCCESS;
@@ -387,12 +396,17 @@
       "The regexp of allowed IMSIs\n")
 {
 	struct bsc_nat_acc_lst *acc;
+	struct bsc_nat_acc_lst_entry *entry;
 
 	acc = bsc_nat_acc_lst_get(_nat, argv[0]);
 	if (!acc)
 		return CMD_WARNING;
 
-	bsc_parse_reg(acc, &acc->imsi_allow_re, &acc->imsi_allow, argc - 1, &argv[1]);
+	entry = bsc_nat_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	bsc_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]);
 	return CMD_SUCCESS;
 }
 
@@ -404,12 +418,17 @@
       "The regexp of to be denied IMSIs\n")
 {
 	struct bsc_nat_acc_lst *acc;
+	struct bsc_nat_acc_lst_entry *entry;
 
 	acc = bsc_nat_acc_lst_get(_nat, argv[0]);
 	if (!acc)
 		return CMD_WARNING;
 
-	bsc_parse_reg(acc, &acc->imsi_deny_re, &acc->imsi_deny, argc - 1, &argv[1]);
+	entry = bsc_nat_acc_lst_entry_create(acc);
+	if (!entry)
+		return CMD_WARNING;
+
+	bsc_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]);
 	return CMD_SUCCESS;
 }
 
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
index 4a43119..224406e 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.c
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -641,6 +641,7 @@
 	struct msgb *msg = msgb_alloc(4096, "test_cr_filter");
 	struct bsc_nat_parsed *parsed;
 	struct bsc_nat_acc_lst *nat_lst, *bsc_lst;
+	struct bsc_nat_acc_lst_entry *nat_entry, *bsc_entry;
 
 	struct bsc_nat *nat = bsc_nat_alloc();
 	struct bsc_connection *bsc = bsc_connection_alloc(nat);
@@ -648,6 +649,12 @@
 	bsc->cfg->acc_lst_name = "bsc";
 	nat->acc_lst_name = "nat";
 
+	nat_lst = bsc_nat_acc_lst_get(nat, "nat");
+	bsc_lst = bsc_nat_acc_lst_get(nat, "bsc");
+
+	bsc_entry = bsc_nat_acc_lst_entry_create(bsc_lst);
+	nat_entry = bsc_nat_acc_lst_entry_create(nat_lst);
+
 	for (i = 0; i < ARRAY_SIZE(cr_filter); ++i) {
 		msgb_reset(msg);
 		copy_to_msg(msg, cr_filter[i].data, cr_filter[i].length);
@@ -655,13 +662,13 @@
 		nat_lst = bsc_nat_acc_lst_get(nat, "nat");
 		bsc_lst = bsc_nat_acc_lst_get(nat, "bsc");
 
-		bsc_parse_reg(nat_lst, &nat_lst->imsi_deny_re, &nat_lst->imsi_deny,
+		bsc_parse_reg(nat_entry, &nat_entry->imsi_deny_re, &nat_entry->imsi_deny,
 			      cr_filter[i].nat_imsi_deny ? 1 : 0,
 			      &cr_filter[i].nat_imsi_deny);
-		bsc_parse_reg(bsc_lst, &bsc_lst->imsi_allow_re, &bsc_lst->imsi_allow,
+		bsc_parse_reg(bsc_entry, &bsc_entry->imsi_allow_re, &bsc_entry->imsi_allow,
 			      cr_filter[i].bsc_imsi_allow ? 1 : 0,
 			      &cr_filter[i].bsc_imsi_allow);
-		bsc_parse_reg(bsc_lst, &bsc_lst->imsi_deny_re, &bsc_lst->imsi_deny,
+		bsc_parse_reg(bsc_entry, &bsc_entry->imsi_deny_re, &bsc_entry->imsi_deny,
 			      cr_filter[i].bsc_imsi_deny ? 1 : 0,
 			      &cr_filter[i].bsc_imsi_deny);