[nat] Prepare more sophisicated filtering and patching

Introduce a bsc_nat_parse method to parse a IP Access method
into various parts. Write out the IPA Proto, in case SCCP is used,
store the msg type, pointers to the source/dest local reference and
other information.

Use the result of bsc_nat_parse inside the bsc_nat_filter method
to decide if the message should be dropped or not.

In the future the bsc_nat_parse result will be used for patching
SCCP references and other parts of the message.

The filter language should be able to filter the msg type of SCCP
messages and gain the "NOT" word in the filter language.
diff --git a/openbsc/src/nat/bsc_filter.c b/openbsc/src/nat/bsc_filter.c
index 5c59f39..0727b33 100644
--- a/openbsc/src/nat/bsc_filter.c
+++ b/openbsc/src/nat/bsc_filter.c
@@ -22,13 +22,169 @@
  */
 
 #include <openbsc/bsc_nat.h>
+#include <openbsc/bssap.h>
 #include <openbsc/ipaccess.h>
+#include <openbsc/talloc.h>
+#include <openbsc/debug.h>
 
-int bsc_nat_filter_ipa(struct msgb *msg)
+#include <sccp/sccp.h>
+
+/*
+ * The idea is to have a simple struct describing a IPA packet with
+ * SCCP SSN and the GSM 08.08 payload and decide. We will both have
+ * a white and a blacklist of packets we want to handle.
+ *
+ * TODO: Implement a "NOT" in the filter language.
+ */
+
+#define ALLOW_ANY -1
+
+struct bsc_pkt_filter {
+	int ipa_proto;
+	int dest_ssn;
+	int bssap;
+	int gsm;
+	int filter_dir;
+};
+
+static struct bsc_pkt_filter black_list[] = {
+	/* filter reset messages to the MSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET, FILTER_TO_MSC },
+
+	/* filter reset ack messages to the BSC */
+	{ IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET_ACKNOWLEDGE, FILTER_TO_BSC },
+
+	/* filter ip access */
+	{ IPAC_PROTO_IPACCESS, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_MSC },
+};
+
+static struct bsc_pkt_filter white_list[] = {
+	/* allow IPAC_PROTO_SCCP messages to both sides */
+	{ IPAC_PROTO_SCCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_NONE },
+};
+
+struct bsc_nat_parsed* bsc_nat_parse(struct msgb *msg)
 {
+	struct sccp_parse_result result;
+	struct bsc_nat_parsed *parsed;
 	struct ipaccess_head *hh;
 
-	/* handle base message handling */
+	/* quick fail */
+	if (msg->len < 4)
+		return NULL;
+
+	parsed = talloc_zero(msg, struct bsc_nat_parsed);
+	if (!parsed)
+		return NULL;
+
+	/* more init */
+	parsed->ipa_proto = parsed->called_ssn = parsed->calling_ssn = -1;
+	parsed->sccp_type = parsed->bssap = parsed->gsm_type = -1;
+
+	/* start parsing */
 	hh = (struct ipaccess_head *) msg->data;
-	return hh->proto == IPAC_PROTO_IPACCESS;
+	parsed->ipa_proto = hh->proto;
+
+	msg->l2h = &hh->data[0];
+
+	/* analyze sccp down here */
+	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		memset(&result, 0, sizeof(result));
+		if (sccp_parse_header(msg, &result) != 0) {
+			talloc_free(parsed);
+			return 0;
+		}
+
+		if (msg->l3h && msgb_l3len(msg) < 3) {
+			LOGP(DNAT, LOGL_ERROR, "Not enough space or GSM payload\n");
+			talloc_free(parsed);
+			return 0;
+		}
+
+		parsed->sccp_type = sccp_determine_msg_type(msg);
+		parsed->src_local_ref = result.source_local_reference;
+		parsed->dest_local_ref = result.destination_local_reference;
+		parsed->called_ssn = result.called.ssn;
+		parsed->calling_ssn = result.calling.ssn;
+
+		/* in case of connection confirm we have no payload */
+		if (msg->l3h) {
+			parsed->bssap = msg->l3h[0];
+			parsed->gsm_type = msg->l3h[2];
+		}
+	}
+
+	return parsed;
+}
+
+int bsc_nat_filter_ipa(struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+	int i;
+
+	/* go through the blacklist now */
+	for (i = 0; i < ARRAY_SIZE(black_list); ++i) {
+		/* the proto is not blacklisted */
+		if (black_list[i].ipa_proto != ALLOW_ANY
+		    && black_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not blacklisted */
+			if (black_list[i].dest_ssn != ALLOW_ANY
+			    && black_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (black_list[i].bssap != ALLOW_ANY
+			    && black_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (black_list[i].gsm != ALLOW_ANY
+			    && black_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* blacklisted */
+			LOGP(DNAT, LOGL_NOTICE, "Blacklisted with rule %d\n", i);
+			return black_list[i].filter_dir;
+		} else {
+			/* blacklisted, we have no content sniffing yet */
+			LOGP(DNAT, LOGL_NOTICE, "Blacklisted with rule %d\n", i);
+			return black_list[i].filter_dir;
+		}
+	}
+
+	/* go through the whitelust now */
+	for (i = 0; i < ARRAY_SIZE(white_list); ++i) {
+		/* the proto is not whitelisted */
+		if (white_list[i].ipa_proto != ALLOW_ANY
+		    && white_list[i].ipa_proto != parsed->ipa_proto)
+			continue;
+
+		if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+			/* the SSN is not whitelisted */
+			if (white_list[i].dest_ssn != ALLOW_ANY
+			    && white_list[i].dest_ssn != parsed->called_ssn)
+				continue;
+
+			/* bssap */
+			if (white_list[i].bssap != ALLOW_ANY
+			    && white_list[i].bssap != parsed->bssap)
+				continue;
+
+			/* gsm */
+			if (white_list[i].gsm != ALLOW_ANY
+			    && white_list[i].gsm != parsed->gsm_type)
+				continue;
+
+			/* whitelisted */
+			LOGP(DNAT, LOGL_NOTICE, "Whitelisted with rule %d\n", i);
+			return FILTER_NONE;
+		} else {
+			/* whitelisted */
+			return FILTER_NONE;
+		}
+	}
+
+	return FILTER_TO_BOTH;
 }
diff --git a/openbsc/src/nat/bsc_nat.c b/openbsc/src/nat/bsc_nat.c
index 9644488..609a17d 100644
--- a/openbsc/src/nat/bsc_nat.c
+++ b/openbsc/src/nat/bsc_nat.c
@@ -102,13 +102,18 @@
 static void forward_sccp_to_bts(struct msgb *msg)
 {
 	struct bsc_connection *bsc;
+	struct bsc_nat_parsed *parsed;
 	int rc;
 
 	/* filter, drop, patch the message? */
-
-	/* drop packets with the wrong IPA header */
-	if (bsc_nat_filter_ipa(msg))
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
 		return;
+	}
+
+	if (bsc_nat_filter_ipa(msg, parsed))
+		goto exit;
 
 	/* currently send this to every BSC connected */
 	llist_for_each_entry(bsc, &bsc_connections, list_entry) {
@@ -118,6 +123,9 @@
 		if (rc < msg->len)
 			LOGP(DNAT, LOGL_ERROR, "Failed to write message to BTS: %d\n", rc);
 	}
+
+exit:
+	talloc_free(parsed);
 }
 
 static int ipaccess_msc_cb(struct bsc_fd *bfd, unsigned int what)
@@ -171,14 +179,25 @@
 
 static int forward_sccp_to_msc(struct msgb *msg)
 {
-	/* FIXME: We need to filter out certain messages */
+	struct bsc_nat_parsed *parsed;
+	int rc = -1;
 
-	/* drop packets with the wrong IPA header */
-	if (bsc_nat_filter_ipa(msg))
-		return 0;
+	/* Parse and filter messages */
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+		return -1;
+	}
+
+	if (bsc_nat_filter_ipa(msg, parsed))
+		goto exit;
 
 	/* send the non-filtered but maybe modified msg */
-	return write(msc_connection.fd, msg->data, msg->len);
+	rc = write(msc_connection.fd, msg->data, msg->len);
+
+exit:
+	talloc_free(parsed);
+	return rc;
 }
 
 static int ipaccess_bsc_cb(struct bsc_fd *bfd, unsigned int what)