aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--openbsc/configure.in1
-rw-r--r--openbsc/include/openbsc/bsc_nat.h46
-rw-r--r--openbsc/src/nat/bsc_filter.c162
-rw-r--r--openbsc/src/nat/bsc_nat.c35
-rw-r--r--openbsc/tests/Makefile.am2
-rw-r--r--openbsc/tests/bsc-nat/bsc_nat_test.c170
6 files changed, 403 insertions, 13 deletions
diff --git a/openbsc/configure.in b/openbsc/configure.in
index 9dcda3633..c89bc8208 100644
--- a/openbsc/configure.in
+++ b/openbsc/configure.in
@@ -51,4 +51,5 @@ AC_OUTPUT(
tests/db/Makefile
tests/channel/Makefile
tests/sccp/Makefile
+ tests/bsc-nat/Makefile
Makefile)
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index ea30cae22..b8a533fff 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -23,11 +23,55 @@
#define BSC_NAT_H
#include <sys/types.h>
+#include <sccp/sccp_types.h>
#include "msgb.h"
+#define FILTER_NONE 0
+#define FILTER_TO_BSC 1
+#define FILTER_TO_MSC 2
+#define FILTER_TO_BOTH 3
+
+/*
+ * For the NAT we will need to analyze and later patch
+ * the received message. This would require us to parse
+ * the IPA and SCCP header twice. Instead of doing this
+ * we will have one analyze structure and have the patching
+ * and filter operate on the same structure.
+ */
+struct bsc_nat_parsed {
+ /* ip access prototype */
+ int ipa_proto;
+
+ /* source local reference */
+ struct sccp_source_reference *src_local_ref;
+
+ /* destination local reference */
+ struct sccp_source_reference *dest_local_ref;
+
+ /* called ssn number */
+ int called_ssn;
+
+ /* calling ssn number */
+ int calling_ssn;
+
+ /* sccp message type */
+ int sccp_type;
+
+ /* bssap type, e.g. 0 for BSS Management */
+ int bssap;
+
+ /* the gsm0808 message type */
+ int gsm_type;
+};
+
+/**
+ * parse the given message into the above structure
+ */
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg);
+
/**
* filter based on IP Access header in both directions
*/
-int bsc_nat_filter_ipa(struct msgb *msg);
+int bsc_nat_filter_ipa(struct msgb *msg, struct bsc_nat_parsed *parsed);
#endif
diff --git a/openbsc/src/nat/bsc_filter.c b/openbsc/src/nat/bsc_filter.c
index 5c59f39a5..0727b33e6 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 96444884c..609a17d96 100644
--- a/openbsc/src/nat/bsc_nat.c
+++ b/openbsc/src/nat/bsc_nat.c
@@ -102,13 +102,18 @@ static void initialize_msc_if_needed()
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 @@ static void forward_sccp_to_bts(struct msgb *msg)
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 void remove_bsc_connection(struct bsc_connection *connection)
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)
diff --git a/openbsc/tests/Makefile.am b/openbsc/tests/Makefile.am
index f40105fbf..d867ec6bb 100644
--- a/openbsc/tests/Makefile.am
+++ b/openbsc/tests/Makefile.am
@@ -1 +1 @@
-SUBDIRS = debug timer sms gsm0408 db channel sccp
+SUBDIRS = debug timer sms gsm0408 db channel sccp bsc-nat
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
new file mode 100644
index 000000000..282f2515b
--- /dev/null
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -0,0 +1,170 @@
+/*
+ * BSC NAT Message filtering
+ *
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by on-waves.com
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_nat.h>
+
+#include <stdio.h>
+
+/* test messages for ipa */
+static u_int8_t ipa_id[] = {
+ 0x00, 0x01, 0xfe, 0x06,
+};
+
+/* SCCP messages are below */
+static u_int8_t gsm_reset[] = {
+ 0x00, 0x12, 0xfd,
+ 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+ 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+ 0x01, 0x20,
+};
+
+static const u_int8_t gsm_reset_ack[] = {
+ 0x00, 0x13, 0xfd,
+ 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+ 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+ 0x00, 0x01, 0x31,
+};
+
+static const u_int8_t gsm_paging[] = {
+ 0x00, 0x20, 0xfd,
+ 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+ 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x10,
+ 0x00, 0x0e, 0x52, 0x08, 0x08, 0x29, 0x47, 0x10,
+ 0x02, 0x01, 0x31, 0x97, 0x61, 0x1a, 0x01, 0x06,
+};
+
+/* BSC -> MSC connection open */
+static const u_int8_t bssmap_cr[] = {
+ 0x00, 0x2c, 0xfd,
+ 0x01, 0x01, 0x02, 0x03, 0x02, 0x02, 0x04, 0x02,
+ 0x42, 0xfe, 0x0f, 0x1f, 0x00, 0x1d, 0x57, 0x05,
+ 0x08, 0x00, 0x72, 0xf4, 0x80, 0x20, 0x12, 0xc3,
+ 0x50, 0x17, 0x10, 0x05, 0x24, 0x11, 0x03, 0x33,
+ 0x19, 0xa2, 0x08, 0x29, 0x47, 0x10, 0x02, 0x01,
+ 0x31, 0x97, 0x61, 0x00
+};
+
+/* MSC -> BSC connection confirm */
+static const u_int8_t bssmap_cc[] = {
+ 0x00, 0x0a, 0xfd,
+ 0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00,
+};
+
+/* MSC -> BSC released */
+static const u_int8_t bssmap_released[] = {
+ 0x00, 0x0e, 0xfd,
+ 0x04, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03, 0x00, 0x01, 0x0f,
+ 0x02, 0x23, 0x42, 0x00,
+};
+
+/* BSC -> MSC released */
+static const u_int8_t bssmap_release_complete[] = {
+ 0x00, 0x07, 0xfd,
+ 0x05, 0x01, 0x02, 0x03, 0x00, 0x00, 0x03
+};
+
+struct filter_result {
+ const u_int8_t *data;
+ const u_int16_t length;
+ const int result;
+};
+
+static const struct filter_result results[] = {
+ {
+ .data = ipa_id,
+ .length = ARRAY_SIZE(ipa_id),
+ .result = FILTER_TO_MSC,
+ },
+ {
+ .data = gsm_reset,
+ .length = ARRAY_SIZE(gsm_reset),
+ .result = FILTER_TO_MSC,
+ },
+ {
+ .data = gsm_reset_ack,
+ .length = ARRAY_SIZE(gsm_reset_ack),
+ .result = FILTER_TO_BSC,
+ },
+ {
+ .data = gsm_paging,
+ .length = ARRAY_SIZE(gsm_paging),
+ .result = FILTER_NONE,
+ },
+ {
+ .data = bssmap_cr,
+ .length = ARRAY_SIZE(bssmap_cr),
+ .result = FILTER_NONE,
+ },
+ {
+ .data = bssmap_cc,
+ .length = ARRAY_SIZE(bssmap_cc),
+ .result = FILTER_NONE,
+ },
+ {
+ .data = bssmap_released,
+ .length = ARRAY_SIZE(bssmap_released),
+ .result = FILTER_NONE,
+ },
+ {
+ .data = bssmap_release_complete,
+ .length = ARRAY_SIZE(bssmap_release_complete),
+ .result = FILTER_NONE,
+ },
+};
+
+int main(int argc, char **argv)
+{
+ int i;
+
+
+ /* start testinh with proper messages */
+ for (i = 0; i < ARRAY_SIZE(results); ++i) {
+ int result;
+ struct bsc_nat_parsed *parsed;
+ struct msgb *msg = msgb_alloc(4096, "test-message");
+
+ fprintf(stderr, "Going to test item: %d\n", i);
+ memcpy(msg->data, results[i].data, results[i].length);
+ msg->l2h = msgb_put(msg, results[i].length);
+
+ parsed = bsc_nat_parse(msg);
+ if (!parsed) {
+ fprintf(stderr, "FAIL: Failed to parse the message\n");
+ continue;
+ }
+
+ result = bsc_nat_filter_ipa(msg, parsed);
+ if (result != results[i].result) {
+ fprintf(stderr, "FAIL: Not the expected result got: %d wanted: %d\n",
+ result, results[i].result);
+ }
+
+ msgb_free(msg);
+ }
+
+ return 0;
+}