diff options
-rw-r--r-- | openbsc/configure.in | 1 | ||||
-rw-r--r-- | openbsc/include/openbsc/bsc_nat.h | 46 | ||||
-rw-r--r-- | openbsc/src/nat/bsc_filter.c | 162 | ||||
-rw-r--r-- | openbsc/src/nat/bsc_nat.c | 35 | ||||
-rw-r--r-- | openbsc/tests/Makefile.am | 2 | ||||
-rw-r--r-- | openbsc/tests/bsc-nat/bsc_nat_test.c | 170 |
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; +} |