aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc_nat
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc_nat')
-rw-r--r--src/osmo-bsc_nat/Makefile.am58
-rw-r--r--src/osmo-bsc_nat/bsc_filter.c218
-rw-r--r--src/osmo-bsc_nat/bsc_mgcp_utils.c1152
-rw-r--r--src/osmo-bsc_nat/bsc_nat.c1736
-rw-r--r--src/osmo-bsc_nat/bsc_nat_ctrl.c524
-rw-r--r--src/osmo-bsc_nat/bsc_nat_filter.c119
-rw-r--r--src/osmo-bsc_nat/bsc_nat_rewrite.c714
-rw-r--r--src/osmo-bsc_nat/bsc_nat_rewrite_trie.c259
-rw-r--r--src/osmo-bsc_nat/bsc_nat_utils.c535
-rw-r--r--src/osmo-bsc_nat/bsc_nat_vty.c1336
-rw-r--r--src/osmo-bsc_nat/bsc_sccp.c247
-rw-r--r--src/osmo-bsc_nat/bsc_ussd.c453
12 files changed, 7351 insertions, 0 deletions
diff --git a/src/osmo-bsc_nat/Makefile.am b/src/osmo-bsc_nat/Makefile.am
new file mode 100644
index 000000000..be33d289d
--- /dev/null
+++ b/src/osmo-bsc_nat/Makefile.am
@@ -0,0 +1,58 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOSCCP_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBCRYPTO_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-bsc_nat \
+ $(NULL)
+
+osmo_bsc_nat_SOURCES = \
+ bsc_filter.c \
+ bsc_mgcp_utils.c \
+ bsc_nat.c \
+ bsc_nat_utils.c \
+ bsc_nat_vty.c \
+ bsc_sccp.c \
+ bsc_ussd.c \
+ bsc_nat_ctrl.c \
+ bsc_nat_rewrite.c \
+ bsc_nat_rewrite_trie.c \
+ bsc_nat_filter.c \
+ $(NULL)
+
+osmo_bsc_nat_LDADD = \
+ $(top_builddir)/src/libmgcp/libmgcp.a \
+ $(top_builddir)/src/libfilter/libfilter.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+ $(top_builddir)/src/libtrau/libtrau.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(LIBOSMOSCCP_LIBS) \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(LIBCRYPTO_LIBS) \
+ -lrt \
+ $(NULL)
diff --git a/src/osmo-bsc_nat/bsc_filter.c b/src/osmo-bsc_nat/bsc_filter.c
new file mode 100644
index 000000000..6a9e99fb8
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_filter.c
@@ -0,0 +1,218 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/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
+
+#define FILTER_TO_BSC 1
+#define FILTER_TO_MSC 2
+#define FILTER_TO_BOTH 3
+
+
+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_TO_BOTH },
+
+ /* allow MGCP messages to both sides */
+ { IPAC_PROTO_MGCP_OLD, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH },
+};
+
+struct bsc_nat_parsed *bsc_nat_parse(struct msgb *msg)
+{
+ struct sccp_parse_result result;
+ struct bsc_nat_parsed *parsed;
+ struct ipaccess_head *hh;
+
+ /* 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;
+ parsed->ipa_proto = hh->proto;
+
+ msg->l2h = &hh->data[0];
+
+ /* do a size check on the input */
+ if (ntohs(hh->len) != msgb_l2len(msg)) {
+ LOGP(DLINP, LOGL_ERROR, "Wrong input length?\n");
+ talloc_free(parsed);
+ return NULL;
+ }
+
+ /* 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;
+ if (parsed->dest_local_ref)
+ parsed->original_dest_ref = *parsed->dest_local_ref;
+ 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(int dir, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+ int i;
+
+ /* go through the blacklist now */
+ for (i = 0; i < ARRAY_SIZE(black_list); ++i) {
+ /* ignore the rule? */
+ if (black_list[i].filter_dir != FILTER_TO_BOTH
+ && black_list[i].filter_dir != dir)
+ continue;
+
+ /* 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_INFO, "Blacklisted with rule %d\n", i);
+ return 1;
+ } else {
+ /* blacklisted, we have no content sniffing yet */
+ LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i);
+ return 1;
+ }
+ }
+
+ /* go through the whitelust now */
+ for (i = 0; i < ARRAY_SIZE(white_list); ++i) {
+ /* ignore the rule? */
+ if (white_list[i].filter_dir != FILTER_TO_BOTH
+ && white_list[i].filter_dir != dir)
+ continue;
+
+ /* 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_INFO, "Whitelisted with rule %d\n", i);
+ return 0;
+ } else {
+ /* whitelisted */
+ return 0;
+ }
+ }
+
+ return 1;
+}
diff --git a/src/osmo-bsc_nat/bsc_mgcp_utils.c b/src/osmo-bsc_nat/bsc_mgcp_utils.c
new file mode 100644
index 000000000..48847865c
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_mgcp_utils.c
@@ -0,0 +1,1152 @@
+/**
+ * This file contains helper routines for MGCP Gateway handling.
+ *
+ * The first thing to remember is that each BSC has its own namespace/range
+ * of endpoints. Whenever a BSSMAP ASSIGNMENT REQUEST is received this code
+ * will be called to select an endpoint on the BSC. The mapping from original
+ * multiplex/timeslot to BSC multiplex'/timeslot' will be stored.
+ *
+ * The second part is to take messages on the public MGCP GW interface
+ * and forward them to the right BSC. This requires the MSC to first
+ * assign the timeslot. This assumption has been true so far. We are using
+ * the policy_cb of the MGCP protocol code to decide if the request should
+ * be immediately answered or delayed. An extension "Z: noanswer" is used
+ * to request the BSC to not respond. This is saving some bytes of bandwidth
+ * and as we are using TCP to forward the message we know it will arrive.
+ * The mgcp_do_read method reads these messages and hands them to the protocol
+ * parsing code which will call the mentioned policy_cb. The bsc_mgcp_forward
+ * method is used on the way back from the BSC to the network.
+ *
+ * The third part is to patch messages forwarded to the BSC. This includes
+ * the endpoint number, the ports to be used inside the SDP file and maybe
+ * some other bits.
+ *
+ */
+/*
+ * (C) 2010-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_callstats.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/mgcp_internal.h>
+#include <openbsc/osmux.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+static void send_direct(struct bsc_nat *nat, struct msgb *output)
+{
+ if (osmo_wqueue_enqueue(&nat->mgcp_cfg->gw_fd, output) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n");
+ msgb_free(output);
+ }
+}
+
+static void mgcp_queue_for_call_agent(struct bsc_nat *nat, struct msgb *output)
+{
+ if (nat->mgcp_ipa)
+ bsc_nat_send_mgcp_to_msc(nat, output);
+ else
+ send_direct(nat, output);
+}
+
+int bsc_mgcp_nr_multiplexes(int max_endpoints)
+{
+ int div = max_endpoints / 32;
+
+ if ((max_endpoints % 32) != 0)
+ div += 1;
+
+ return div;
+}
+
+static int bsc_init_endps_if_needed(struct bsc_connection *con)
+{
+ int multiplexes;
+
+ /* we have done that */
+ if (con->_endpoint_status)
+ return 0;
+
+ /* we have no config... */
+ if (!con->cfg)
+ return -1;
+
+ multiplexes = bsc_mgcp_nr_multiplexes(con->cfg->max_endpoints);
+ con->number_multiplexes = multiplexes;
+ con->max_endpoints = con->cfg->max_endpoints;
+ con->_endpoint_status = talloc_zero_array(con, char, 32 * multiplexes + 1);
+ return con->_endpoint_status == NULL;
+}
+
+static int bsc_assign_endpoint(struct bsc_connection *bsc, struct nat_sccp_connection *con)
+{
+ int multiplex;
+ int timeslot;
+ const int number_endpoints = bsc->max_endpoints;
+ int i;
+
+ mgcp_endpoint_to_timeslot(bsc->last_endpoint, &multiplex, &timeslot);
+ timeslot += 1;
+
+ for (i = 0; i < number_endpoints; ++i) {
+ int endpoint;
+
+ /* Wrap around timeslots */
+ if (timeslot == 0)
+ timeslot = 1;
+
+ if (timeslot == 0x1f) {
+ timeslot = 1;
+ multiplex += 1;
+ }
+
+ /* Wrap around the multiplex */
+ if (multiplex >= bsc->number_multiplexes)
+ multiplex = 0;
+
+ endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+ /* Now check if we are allowed to assign this one */
+ if (endpoint >= bsc->max_endpoints) {
+ multiplex = 0;
+ timeslot = 1;
+ endpoint = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+ }
+
+
+ if (bsc->_endpoint_status[endpoint] == 0) {
+ bsc->_endpoint_status[endpoint] = 1;
+ con->bsc_endp = endpoint;
+ bsc->last_endpoint = endpoint;
+ return 0;
+ }
+
+ timeslot += 1;
+ }
+
+ return -1;
+}
+
+static uint16_t create_cic(int endpoint)
+{
+ int timeslot, multiplex;
+
+ mgcp_endpoint_to_timeslot(endpoint, &multiplex, &timeslot);
+ return (multiplex << 5) | (timeslot & 0x1f);
+}
+
+int bsc_mgcp_assign_patch(struct nat_sccp_connection *con, struct msgb *msg)
+{
+ struct nat_sccp_connection *mcon;
+ struct tlv_parsed tp;
+ uint16_t cic;
+ uint8_t timeslot;
+ uint8_t multiplex;
+ unsigned int endp;
+
+ if (!msg->l3h) {
+ LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n");
+ return -1;
+ }
+
+ if (msgb_l3len(msg) < 3) {
+ LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n");
+ return -1;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+ LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n");
+ return -1;
+ }
+
+ cic = ntohs(tlvp_val16_unal(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+ timeslot = cic & 0x1f;
+ multiplex = (cic & ~0x1f) >> 5;
+
+
+ endp = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+
+ if (endp >= con->bsc->nat->mgcp_cfg->trunk.number_endpoints) {
+ LOGP(DNAT, LOGL_ERROR,
+ "MSC attempted to assign bad endpoint 0x%x\n",
+ endp);
+ return -1;
+ }
+
+ /* find stale connections using that endpoint */
+ llist_for_each_entry(mcon, &con->bsc->nat->sccp_connections, list_entry) {
+ if (mcon->msc_endp == endp) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Endpoint %d was assigned to 0x%x and now 0x%x\n",
+ endp,
+ sccp_src_ref_to_int(&mcon->patched_ref),
+ sccp_src_ref_to_int(&con->patched_ref));
+ bsc_mgcp_dlcx(mcon);
+ }
+ }
+
+ con->msc_endp = endp;
+ if (bsc_init_endps_if_needed(con->bsc) != 0)
+ return -1;
+ if (bsc_assign_endpoint(con->bsc, con) != 0)
+ return -1;
+
+ /*
+ * now patch the message for the new CIC...
+ * still assumed to be one multiplex only
+ */
+ cic = htons(create_cic(con->bsc_endp));
+ memcpy((uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE),
+ &cic, sizeof(cic));
+
+ return 0;
+}
+
+static void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i)
+{
+ if (nat->bsc_endpoints[i].transaction_id) {
+ talloc_free(nat->bsc_endpoints[i].transaction_id);
+ nat->bsc_endpoints[i].transaction_id = NULL;
+ }
+
+ nat->bsc_endpoints[i].transaction_state = 0;
+ nat->bsc_endpoints[i].bsc = NULL;
+}
+
+void bsc_mgcp_free_endpoints(struct bsc_nat *nat)
+{
+ int i;
+
+ for (i = 1; i < nat->mgcp_cfg->trunk.number_endpoints; ++i){
+ bsc_mgcp_free_endpoint(nat, i);
+ mgcp_release_endp(&nat->mgcp_cfg->trunk.endpoints[i]);
+ }
+}
+
+/* send a MDCX where we do not want a response */
+static void bsc_mgcp_send_mdcx(struct bsc_connection *bsc, int port, struct mgcp_endpoint *endp)
+{
+ char buf[2096];
+ int len;
+
+ len = snprintf(buf, sizeof(buf),
+ "MDCX 23 %x@mgw MGCP 1.0\r\n"
+ "Z: noanswer\r\n"
+ "\r\n"
+ "c=IN IP4 %s\r\n"
+ "m=audio %d RTP/AVP 255\r\n",
+ port, mgcp_bts_src_addr(endp),
+ endp->bts_end.local_port);
+ if (len < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "snprintf for MDCX failed.\n");
+ return;
+ }
+
+ bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+static void bsc_mgcp_send_dlcx(struct bsc_connection *bsc, int endpoint, int trans)
+{
+ char buf[2096];
+ int len;
+
+ /*
+ * The following is a bit of a spec violation. According to the
+ * MGCP grammar the transaction id is are upto 9 digits but we
+ * prefix it with an alpha numeric value so we can easily recognize
+ * it as a response.
+ */
+ len = snprintf(buf, sizeof(buf),
+ "DLCX nat-%u %x@mgw MGCP 1.0\r\n",
+ trans, endpoint);
+ if (len < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "snprintf for DLCX failed.\n");
+ return;
+ }
+
+ bsc_write_mgcp(bsc, (uint8_t *) buf, len);
+}
+
+void bsc_mgcp_init(struct nat_sccp_connection *con)
+{
+ con->msc_endp = -1;
+ con->bsc_endp = -1;
+}
+
+/**
+ * This code will remember the network side of the audio statistics and
+ * once the internal DLCX response arrives this can be combined with the
+ * the BSC side and forwarded as a trap.
+ */
+static void remember_pending_dlcx(struct nat_sccp_connection *con, uint32_t transaction)
+{
+ struct bsc_nat_call_stats *stats;
+ struct bsc_connection *bsc = con->bsc;
+ struct mgcp_endpoint *endp;
+
+ stats = talloc_zero(bsc, struct bsc_nat_call_stats);
+ if (!stats) {
+ LOGP(DNAT, LOGL_NOTICE,
+ "Failed to allocate statistics for endpoint 0x%x\n",
+ con->msc_endp);
+ return;
+ }
+
+ /* take the endpoint here */
+ endp = &bsc->nat->mgcp_cfg->trunk.endpoints[con->msc_endp];
+
+ stats->remote_ref = con->remote_ref;
+ stats->src_ref = con->patched_ref;
+
+ stats->ci = endp->ci;
+ stats->bts_rtp_port = endp->bts_end.rtp_port;
+ stats->bts_addr = endp->bts_end.addr;
+ stats->net_rtp_port = endp->net_end.rtp_port;
+ stats->net_addr = endp->net_end.addr;
+
+ stats->net_ps = endp->net_end.packets;
+ stats->net_os = endp->net_end.octets;
+ stats->bts_pr = endp->bts_end.packets;
+ stats->bts_or = endp->bts_end.octets;
+ mgcp_state_calc_loss(&endp->bts_state, &endp->bts_end,
+ &stats->bts_expected, &stats->bts_loss);
+ stats->bts_jitter = mgcp_state_calc_jitter(&endp->bts_state);
+
+ stats->trans_id = transaction;
+ stats->msc_endpoint = con->msc_endp;
+
+ /*
+ * Too many pending requests.. let's remove the first two items.
+ */
+ if (!llist_empty(&bsc->pending_dlcx) &&
+ bsc->pending_dlcx_count >= bsc->cfg->max_endpoints * 3) {
+ struct bsc_nat_call_stats *tmp;
+ LOGP(DNAT, LOGL_ERROR,
+ "Too many(%d) pending DLCX responses on BSC: %d\n",
+ bsc->pending_dlcx_count, bsc->cfg->nr);
+ bsc->pending_dlcx_count -= 1;
+ tmp = (struct bsc_nat_call_stats *) bsc->pending_dlcx.next;
+ llist_del(&tmp->entry);
+ talloc_free(tmp);
+ }
+
+ bsc->pending_dlcx_count += 1;
+ llist_add_tail(&stats->entry, &bsc->pending_dlcx);
+}
+
+void bsc_mgcp_dlcx(struct nat_sccp_connection *con)
+{
+ /* send a DLCX down the stream */
+ if (con->bsc_endp != -1 && con->bsc->_endpoint_status) {
+ LOGP(DNAT, LOGL_NOTICE,
+ "Endpoint 0x%x was allocated for bsc: %d. Freeing it.\n",
+ con->bsc_endp, con->bsc->cfg->nr);
+ if (con->bsc->_endpoint_status[con->bsc_endp] != 1)
+ LOGP(DNAT, LOGL_ERROR, "Endpoint 0x%x was not in use\n", con->bsc_endp);
+ remember_pending_dlcx(con, con->bsc->next_transaction);
+ con->bsc->_endpoint_status[con->bsc_endp] = 0;
+ bsc_mgcp_send_dlcx(con->bsc, con->bsc_endp, con->bsc->next_transaction++);
+ bsc_mgcp_free_endpoint(con->bsc->nat, con->msc_endp);
+ }
+
+ bsc_mgcp_init(con);
+
+}
+
+/*
+ * Search for the pending request
+ */
+static void handle_dlcx_response(struct bsc_connection *bsc, struct msgb *msg,
+ int code, const char *transaction)
+{
+ uint32_t trans_id = UINT32_MAX;
+ uint32_t b_ps, b_os, n_pr, n_or, jitter;
+ int loss;
+ struct bsc_nat_call_stats *tmp, *stat = NULL;
+ struct ctrl_cmd *cmd;
+
+ /* parse the transaction identifier */
+ int rc = sscanf(transaction, "nat-%u", &trans_id);
+ if (rc != 1) {
+ LOGP(DNAT, LOGL_ERROR, "Can not parse transaction id: '%s'\n",
+ transaction);
+ return;
+ }
+
+ /* find the answer for the request we made */
+ llist_for_each_entry(tmp, &bsc->pending_dlcx, entry) {
+ if (trans_id != tmp->trans_id)
+ continue;
+
+ stat = tmp;
+ break;
+ }
+
+ if (!stat) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Can not find transaction for: %u\n", trans_id);
+ return;
+ }
+
+ /* attempt to parse the data now */
+ rc = mgcp_parse_stats(msg, &b_ps, &b_os, &n_pr, &n_or, &loss, &jitter);
+ if (rc != 0)
+ LOGP(DNAT, LOGL_ERROR,
+ "Can not parse connection statistics: %d\n", rc);
+
+ /* send a trap now */
+ cmd = ctrl_cmd_create(bsc, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Creating a ctrl cmd failed.\n");
+ goto free_stat;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "net.0.bsc.%d.call_stats.v2",
+ bsc->cfg->nr);
+ cmd->reply = talloc_asprintf(cmd,
+ "mg_ip_addr=%s,mg_port=%d,",
+ inet_ntoa(stat->net_addr),
+ stat->net_rtp_port);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "endpoint_ip_addr=%s,endpoint_port=%d,",
+ inet_ntoa(stat->bts_addr),
+ stat->bts_rtp_port);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "nat_pkt_in=%u,nat_pkt_out=%u,"
+ "nat_bytes_in=%u,nat_bytes_out=%u,"
+ "nat_jitter=%u,nat_pkt_lost=%d,",
+ stat->bts_pr, stat->net_ps,
+ stat->bts_or, stat->net_os,
+ stat->bts_jitter, stat->bts_loss);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "bsc_pkt_in=%u,bsc_pkt_out=%u,"
+ "bsc_bytes_in=%u,bsc_bytes_out=%u,"
+ "bsc_jitter=%u,bsc_pkt_lost=%d,",
+ n_pr, b_ps,
+ n_or, b_os,
+ jitter, loss);
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "sccp_src_ref=%u,sccp_dst_ref=%u",
+ sccp_src_ref_to_int(&stat->src_ref),
+ sccp_src_ref_to_int(&stat->remote_ref));
+
+ /* send it and be done */
+ ctrl_cmd_send_to_all(bsc->nat->ctrl, cmd);
+ talloc_free(cmd);
+
+free_stat:
+ bsc->pending_dlcx_count -= 1;
+ llist_del(&stat->entry);
+ talloc_free(stat);
+}
+
+
+struct nat_sccp_connection *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint)
+{
+ struct nat_sccp_connection *con = NULL;
+ struct nat_sccp_connection *sccp;
+
+ llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) {
+ if (sccp->msc_endp == -1)
+ continue;
+ if (sccp->msc_endp != endpoint)
+ continue;
+
+ con = sccp;
+ }
+
+ if (con)
+ return con;
+
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to find the connection for endpoint: 0x%x\n", endpoint);
+ return NULL;
+}
+
+static int nat_osmux_only(struct mgcp_config *mgcp_cfg, struct bsc_config *bsc_cfg)
+{
+ if (mgcp_cfg->osmux == OSMUX_USAGE_ONLY)
+ return 1;
+ if (bsc_cfg->osmux == OSMUX_USAGE_ONLY)
+ return 1;
+ return 0;
+}
+
+static int bsc_mgcp_policy_cb(struct mgcp_trunk_config *tcfg, int endpoint, int state, const char *transaction_id)
+{
+ struct bsc_nat *nat;
+ struct bsc_endpoint *bsc_endp;
+ struct nat_sccp_connection *sccp;
+ struct mgcp_endpoint *mgcp_endp;
+ struct msgb *bsc_msg;
+
+ nat = tcfg->cfg->data;
+ bsc_endp = &nat->bsc_endpoints[endpoint];
+ mgcp_endp = &nat->mgcp_cfg->trunk.endpoints[endpoint];
+
+ if (bsc_endp->transaction_id) {
+ LOGP(DMGCP, LOGL_ERROR, "Endpoint 0x%x had pending transaction: '%s'\n",
+ endpoint, bsc_endp->transaction_id);
+ talloc_free(bsc_endp->transaction_id);
+ bsc_endp->transaction_id = NULL;
+ bsc_endp->transaction_state = 0;
+ }
+ bsc_endp->bsc = NULL;
+
+ sccp = bsc_mgcp_find_con(nat, endpoint);
+
+ if (!sccp) {
+ LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for change on endpoint: 0x%x state: %d\n", endpoint, state);
+
+ switch (state) {
+ case MGCP_ENDP_CRCX:
+ return MGCP_POLICY_REJECT;
+ break;
+ case MGCP_ENDP_DLCX:
+ return MGCP_POLICY_CONT;
+ break;
+ case MGCP_ENDP_MDCX:
+ return MGCP_POLICY_CONT;
+ break;
+ default:
+ LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state);
+ return MGCP_POLICY_CONT;
+ break;
+ }
+ }
+
+ /* Allocate a Osmux circuit ID */
+ if (state == MGCP_ENDP_CRCX) {
+ if (nat->mgcp_cfg->osmux && sccp->bsc->cfg->osmux) {
+ osmux_allocate_cid(mgcp_endp);
+ if (mgcp_endp->osmux.allocated_cid < 0 &&
+ nat_osmux_only(nat->mgcp_cfg, sccp->bsc->cfg)) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Rejecting usage of endpoint\n");
+ return MGCP_POLICY_REJECT;
+ }
+ }
+ }
+
+ /* we need to generate a new and patched message */
+ bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length,
+ sccp->bsc_endp, mgcp_bts_src_addr(mgcp_endp),
+ mgcp_endp->bts_end.local_port,
+ mgcp_endp->osmux.allocated_cid,
+ &mgcp_endp->net_end.codec.payload_type,
+ nat->sdp_ensure_amr_mode_set);
+ if (!bsc_msg) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n");
+ return MGCP_POLICY_CONT;
+ }
+
+
+ bsc_endp->transaction_id = talloc_strdup(nat, transaction_id);
+ bsc_endp->transaction_state = state;
+ bsc_endp->bsc = sccp->bsc;
+
+ /* we need to update some bits */
+ if (state == MGCP_ENDP_CRCX) {
+ struct sockaddr_in sock;
+
+ /* Annotate the allocated Osmux CID until the bsc confirms that
+ * it agrees to use Osmux for this voice flow.
+ */
+ if (mgcp_endp->osmux.allocated_cid >= 0 &&
+ mgcp_endp->osmux.state != OSMUX_STATE_ENABLED) {
+ mgcp_endp->osmux.state = OSMUX_STATE_NEGOTIATING;
+ mgcp_endp->osmux.cid = mgcp_endp->osmux.allocated_cid;
+ }
+
+ socklen_t len = sizeof(sock);
+ if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n",
+ errno, strerror(errno));
+ } else {
+ mgcp_endp->bts_end.addr = sock.sin_addr;
+ }
+
+ /* send the message and a fake MDCX to force sending of a dummy packet */
+ bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+ bsc_mgcp_send_mdcx(sccp->bsc, sccp->bsc_endp, mgcp_endp);
+ return MGCP_POLICY_DEFER;
+ } else if (state == MGCP_ENDP_DLCX) {
+ /* we will free the endpoint now and send a DLCX to the BSC */
+ msgb_free(bsc_msg);
+ bsc_mgcp_dlcx(sccp);
+
+ /* libmgcp clears the MGCP endpoint for us */
+ if (mgcp_endp->osmux.state == OSMUX_STATE_ENABLED)
+ osmux_release_cid(mgcp_endp);
+
+ return MGCP_POLICY_CONT;
+ } else {
+ bsc_write(sccp->bsc, bsc_msg, IPAC_PROTO_MGCP_OLD);
+ return MGCP_POLICY_DEFER;
+ }
+}
+
+/*
+ * We do have a failure, free data downstream..
+ */
+static void free_chan_downstream(struct mgcp_endpoint *endp, struct bsc_endpoint *bsc_endp,
+ struct bsc_connection *bsc)
+{
+ LOGP(DMGCP, LOGL_ERROR, "No CI, freeing endpoint 0x%x in state %d\n",
+ ENDPOINT_NUMBER(endp), bsc_endp->transaction_state);
+
+ /* if a CRCX failed... send a DLCX down the stream */
+ if (bsc_endp->transaction_state == MGCP_ENDP_CRCX) {
+ struct nat_sccp_connection *con;
+ con = bsc_mgcp_find_con(bsc->nat, ENDPOINT_NUMBER(endp));
+ if (!con) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "No SCCP connection for endp 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ } else {
+ if (con->bsc == bsc) {
+ bsc_mgcp_send_dlcx(bsc, con->bsc_endp, con->bsc->next_transaction++);
+ } else {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Endpoint belongs to a different BSC\n");
+ }
+ }
+ }
+
+ bsc_mgcp_free_endpoint(bsc->nat, ENDPOINT_NUMBER(endp));
+ mgcp_release_endp(endp);
+}
+
+static void bsc_mgcp_osmux_confirm(struct mgcp_endpoint *endp, const char *str)
+{
+ unsigned int osmux_cid;
+ char *res;
+
+ res = strstr(str, "X-Osmux: ");
+ if (!res) {
+ LOGP(DMGCP, LOGL_INFO,
+ "BSC doesn't want to use Osmux, failing back to RTP\n");
+ goto err;
+ }
+
+ if (sscanf(res, "X-Osmux: %u", &osmux_cid) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse Osmux CID '%s'\n",
+ str);
+ goto err;
+ }
+
+ if (endp->osmux.cid != osmux_cid) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "BSC sent us wrong CID %u, we expected %u",
+ osmux_cid, endp->osmux.cid);
+ goto err;
+ }
+
+ LOGP(DMGCP, LOGL_NOTICE, "bsc accepted to use Osmux (cid=%u)\n",
+ osmux_cid);
+ endp->osmux.state = OSMUX_STATE_ACTIVATING;
+ return;
+err:
+ osmux_release_cid(endp);
+ endp->osmux.state = OSMUX_STATE_DISABLED;
+}
+
+/*
+ * We have received a msg from the BSC. We will see if we know
+ * this transaction and if it belongs to the BSC. Then we will
+ * need to patch the content to point to the local network and we
+ * need to update the I: that was assigned by the BSS.
+ *
+ * Only responses to CRCX and DLCX should arrive here. The DLCX
+ * needs to be handled specially to combine the two statistics.
+ */
+void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg)
+{
+ struct msgb *output;
+ struct bsc_endpoint *bsc_endp = NULL;
+ struct mgcp_endpoint *endp = NULL;
+ int i, code;
+ char transaction_id[60];
+
+ /* Some assumption that our buffer is big enough.. and null terminate */
+ if (msgb_l2len(msg) > 2000) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n");
+ return;
+ }
+
+ msg->l2h[msgb_l2len(msg)] = '\0';
+
+ if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n");
+ return;
+ }
+
+ for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+ if (bsc->nat->bsc_endpoints[i].bsc != bsc)
+ continue;
+ /* no one listening? a bug? */
+ if (!bsc->nat->bsc_endpoints[i].transaction_id)
+ continue;
+ if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0)
+ continue;
+
+ endp = &bsc->nat->mgcp_cfg->trunk.endpoints[i];
+ bsc_endp = &bsc->nat->bsc_endpoints[i];
+ break;
+ }
+
+ if (!bsc_endp && strncmp("nat-", transaction_id, 4) == 0) {
+ handle_dlcx_response(bsc, msg, code, transaction_id);
+ return;
+ }
+
+ if (!bsc_endp) {
+ LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n",
+ transaction_id, (const char *) msg->l2h);
+ return;
+ }
+
+ endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h);
+ if (endp->ci == CI_UNUSED) {
+ free_chan_downstream(endp, bsc_endp, bsc);
+ return;
+ }
+
+ if (endp->osmux.state == OSMUX_STATE_NEGOTIATING)
+ bsc_mgcp_osmux_confirm(endp, (const char *) msg->l2h);
+
+ /* If we require osmux and it is disabled.. fail */
+ if (nat_osmux_only(bsc->nat->mgcp_cfg, bsc->cfg) &&
+ endp->osmux.state == OSMUX_STATE_DISABLED) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to activate osmux endpoint 0x%x\n",
+ ENDPOINT_NUMBER(endp));
+ free_chan_downstream(endp, bsc_endp, bsc);
+ return;
+ }
+
+ /* free some stuff */
+ talloc_free(bsc_endp->transaction_id);
+ bsc_endp->transaction_id = NULL;
+ bsc_endp->transaction_state = 0;
+
+ /*
+ * rewrite the information. In case the endpoint was deleted
+ * there should be nothing for us to rewrite so putting endp->rtp_port
+ * with the value of 0 should be no problem.
+ */
+ output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), -1,
+ mgcp_net_src_addr(endp),
+ endp->net_end.local_port, -1,
+ &endp->bts_end.codec.payload_type,
+ bsc->nat->sdp_ensure_amr_mode_set);
+ if (!output) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n");
+ return;
+ }
+
+ mgcp_queue_for_call_agent(bsc->nat, output);
+}
+
+int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60])
+{
+ int rc;
+ /* we want to parse two strings */
+ rc = sscanf(str, "%3d %59s\n", code, transaction) != 2;
+ transaction[59] = '\0';
+ return rc;
+}
+
+uint32_t bsc_mgcp_extract_ci(const char *str)
+{
+ unsigned int ci;
+ char *res = strstr(str, "I: ");
+ if (!res) {
+ LOGP(DMGCP, LOGL_ERROR, "No CI in msg '%s'\n", str);
+ return CI_UNUSED;
+ }
+
+ if (sscanf(res, "I: %u", &ci) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to parse CI in msg '%s'\n", str);
+ return CI_UNUSED;
+ }
+
+ return ci;
+}
+
+/**
+ * Create a new MGCPCommand based on the input and endpoint from a message
+ */
+static void patch_mgcp(struct msgb *output, const char *op, const char *tok,
+ int endp, int len, int cr, int osmux_cid)
+{
+ int slen;
+ int ret;
+ char buf[40];
+ char osmux_extension[strlen("\nX-Osmux: 255") + 1];
+
+ buf[0] = buf[39] = '\0';
+ ret = sscanf(tok, "%*s %s", buf);
+ if (ret != 1) {
+ LOGP(DMGCP, LOGL_ERROR,
+ "Failed to find Endpoint in: %s\n", tok);
+ return;
+ }
+
+ if (osmux_cid >= 0)
+ sprintf(osmux_extension, "\nX-Osmux: %u", osmux_cid & 0xff);
+ else
+ osmux_extension[0] = '\0';
+
+ slen = sprintf((char *) output->l3h, "%s %s %x@mgw MGCP 1.0%s%s",
+ op, buf, endp, osmux_extension, cr ? "\r\n" : "\n");
+ output->l3h = msgb_put(output, slen);
+}
+
+/* we need to replace some strings... */
+struct msgb *bsc_mgcp_rewrite(char *input, int length, int endpoint,
+ const char *ip, int port, int osmux_cid,
+ int *first_payload_type, int ensure_mode_set)
+{
+ static const char crcx_str[] = "CRCX ";
+ static const char dlcx_str[] = "DLCX ";
+ static const char mdcx_str[] = "MDCX ";
+
+ static const char ip_str[] = "c=IN IP4 ";
+ static const char aud_str[] = "m=audio ";
+ static const char fmt_str[] = "a=fmtp:";
+
+ char buf[128];
+ char *running, *token;
+ struct msgb *output;
+
+ /* keep state to add the a=fmtp line */
+ int found_fmtp = 0;
+ int payload = -1;
+ int cr = 1;
+
+ if (length > 4096 - 256) {
+ LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n");
+ return NULL;
+ }
+
+ output = msgb_alloc_headroom(4096, 128, "MGCP rewritten");
+ if (!output) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n");
+ return NULL;
+ }
+
+ running = input;
+ output->l2h = output->data;
+ output->l3h = output->l2h;
+ for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) {
+ int len = strlen(token);
+ cr = len > 0 && token[len - 1] == '\r';
+
+ if (strncmp(crcx_str, token, (sizeof crcx_str) - 1) == 0) {
+ patch_mgcp(output, "CRCX", token, endpoint, len, cr, osmux_cid);
+ } else if (strncmp(dlcx_str, token, (sizeof dlcx_str) - 1) == 0) {
+ patch_mgcp(output, "DLCX", token, endpoint, len, cr, -1);
+ } else if (strncmp(mdcx_str, token, (sizeof mdcx_str) - 1) == 0) {
+ patch_mgcp(output, "MDCX", token, endpoint, len, cr, -1);
+ } else if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) {
+ output->l3h = msgb_put(output, strlen(ip_str));
+ memcpy(output->l3h, ip_str, strlen(ip_str));
+ output->l3h = msgb_put(output, strlen(ip));
+ memcpy(output->l3h, ip, strlen(ip));
+
+ if (cr) {
+ output->l3h = msgb_put(output, 2);
+ output->l3h[0] = '\r';
+ output->l3h[1] = '\n';
+ } else {
+ output->l3h = msgb_put(output, 1);
+ output->l3h[0] = '\n';
+ }
+ } else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) {
+ int offset;
+ if (sscanf(token, "m=audio %*d RTP/AVP %n%d", &offset, &payload) != 1) {
+ LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n");
+ msgb_free(output);
+ return NULL;
+ }
+
+ snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %s\n",
+ port, &token[offset]);
+ buf[sizeof(buf)-1] = '\0';
+
+ output->l3h = msgb_put(output, strlen(buf));
+ memcpy(output->l3h, buf, strlen(buf));
+ } else if (strncmp(fmt_str, token, (sizeof fmt_str) - 1) == 0) {
+ found_fmtp = 1;
+ goto copy;
+ } else {
+copy:
+ output->l3h = msgb_put(output, len + 1);
+ memcpy(output->l3h, token, len);
+ output->l3h[len] = '\n';
+ }
+ }
+
+ /*
+ * the above code made sure that we have 128 bytes lefts. So we can
+ * safely append another line.
+ */
+ if (ensure_mode_set && !found_fmtp && payload != -1) {
+ snprintf(buf, sizeof(buf) - 1, "a=fmtp:%d mode-set=2%s",
+ payload, cr ? "\r\n" : "\n");
+ buf[sizeof(buf) - 1] = '\0';
+ output->l3h = msgb_put(output, strlen(buf));
+ memcpy(output->l3h, buf, strlen(buf));
+ }
+
+ if (payload != -1 && first_payload_type)
+ *first_payload_type = payload;
+
+ return output;
+}
+
+/*
+ * This comes from the MSC and we will now parse it. The caller needs
+ * to free the msgb.
+ */
+void bsc_nat_handle_mgcp(struct bsc_nat *nat, struct msgb *msg)
+{
+ struct msgb *resp;
+
+ if (!nat->mgcp_ipa) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP message not allowed on IPA.\n");
+ return;
+ }
+
+ if (msgb_l2len(msg) > sizeof(nat->mgcp_msg) - 1) {
+ LOGP(DMGCP, LOGL_ERROR, "MGCP msg too big for handling.\n");
+ return;
+ }
+
+ memcpy(nat->mgcp_msg, msg->l2h, msgb_l2len(msg));
+ nat->mgcp_length = msgb_l2len(msg);
+ nat->mgcp_msg[nat->mgcp_length] = '\0';
+
+ /* now handle the message */
+ resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+
+ /* we do have a direct answer... e.g. AUEP */
+ if (resp)
+ mgcp_queue_for_call_agent(nat, resp);
+
+ return;
+}
+
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+ struct bsc_nat *nat;
+ struct msgb *msg, *resp;
+ int rc;
+
+ nat = fd->data;
+
+ rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1);
+ if (rc <= 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno);
+ return -1;
+ }
+
+ nat->mgcp_msg[rc] = '\0';
+ nat->mgcp_length = rc;
+
+ msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read");
+ if (!msg) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n");
+ return -1;
+ }
+
+ msg->l2h = msgb_put(msg, rc);
+ memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg));
+ resp = mgcp_handle_message(nat->mgcp_cfg, msg);
+ msgb_free(msg);
+
+ /* we do have a direct answer... e.g. AUEP */
+ if (resp)
+ mgcp_queue_for_call_agent(nat, resp);
+
+ return 0;
+}
+
+static int mgcp_do_write(struct osmo_fd *bfd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(bfd->fd, msg->data, msg->len);
+
+ if (rc != msg->len) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n");
+ return -1;
+ }
+
+ return rc;
+}
+
+static int init_mgcp_socket(struct bsc_nat *nat, struct mgcp_config *cfg)
+{
+ struct sockaddr_in addr;
+ int on;
+
+ cfg->gw_fd.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (cfg->gw_fd.bfd.fd < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno);
+ return -1;
+ }
+
+ on = 1;
+ setsockopt(cfg->gw_fd.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(cfg->source_port);
+ inet_aton(cfg->source_addr, &addr.sin_addr);
+
+ if (bind(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to bind on %s:%d errno: %d\n",
+ cfg->source_addr, cfg->source_port, errno);
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ addr.sin_port = htons(2727);
+ inet_aton(cfg->call_agent_addr, &addr.sin_addr);
+ if (connect(cfg->gw_fd.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n",
+ cfg->call_agent_addr, errno);
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ osmo_wqueue_init(&cfg->gw_fd, 10);
+ cfg->gw_fd.bfd.when = BSC_FD_READ;
+ cfg->gw_fd.bfd.data = nat;
+ cfg->gw_fd.read_cb = mgcp_do_read;
+ cfg->gw_fd.write_cb = mgcp_do_write;
+
+ if (osmo_fd_register(&cfg->gw_fd.bfd) != 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n");
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int bsc_mgcp_nat_init(struct bsc_nat *nat)
+{
+ struct mgcp_config *cfg = nat->mgcp_cfg;
+
+ if (!cfg->call_agent_addr) {
+ LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n");
+ return -1;
+ }
+
+ if (cfg->bts_ip) {
+ LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n");
+ return -1;
+ }
+
+ /* initialize the MGCP socket */
+ if (!nat->mgcp_ipa) {
+ int rc = init_mgcp_socket(nat, cfg);
+ if (rc != 0)
+ return rc;
+ }
+
+
+ /* some more MGCP config handling */
+ cfg->data = nat;
+ cfg->policy_cb = bsc_mgcp_policy_cb;
+ cfg->trunk.force_realloc = 1;
+
+ if (cfg->bts_ip)
+ talloc_free(cfg->bts_ip);
+ cfg->bts_ip = "";
+
+ nat->bsc_endpoints = talloc_zero_array(nat,
+ struct bsc_endpoint,
+ cfg->trunk.number_endpoints + 1);
+ if (!nat->bsc_endpoints) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to allocate nat endpoints\n");
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ if (mgcp_reset_transcoder(cfg) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to send packet to the transcoder.\n");
+ talloc_free(nat->bsc_endpoints);
+ nat->bsc_endpoints = NULL;
+ close(cfg->gw_fd.bfd.fd);
+ cfg->gw_fd.bfd.fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc)
+{
+ struct rate_ctr *ctr = NULL;
+ int i;
+
+ if (bsc->cfg)
+ ctr = &bsc->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_CALLS];
+
+ for (i = 1; i < bsc->nat->mgcp_cfg->trunk.number_endpoints; ++i) {
+ struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i];
+
+ if (bsc_endp->bsc != bsc)
+ continue;
+
+ if (ctr)
+ rate_ctr_inc(ctr);
+
+ bsc_mgcp_free_endpoint(bsc->nat, i);
+ mgcp_release_endp(&bsc->nat->mgcp_cfg->trunk.endpoints[i]);
+ }
+}
diff --git a/src/osmo-bsc_nat/bsc_nat.c b/src/osmo-bsc_nat/bsc_nat.c
new file mode 100644
index 000000000..daa066d05
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat.c
@@ -0,0 +1,1736 @@
+/* BSC Multiplexer/NAT */
+
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <libgen.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/socket.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+
+#include <osmocom/crypt/auth.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <openssl/rand.h>
+
+#include "../../bscconfig.h"
+
+#define SCCP_CLOSE_TIME 20
+#define SCCP_CLOSE_TIME_TIMEOUT 19
+
+static const char *config_file = "bsc-nat.cfg";
+static struct in_addr local_addr;
+static struct osmo_fd bsc_listen;
+static const char *msc_ip = NULL;
+static struct osmo_timer_list sccp_close;
+static int daemonize = 0;
+
+const char *openbsc_copyright =
+ "Copyright (C) 2010 Holger Hans Peter Freyther and On-Waves\r\n"
+ "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static struct bsc_nat *nat;
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int);
+static void msc_send_reset(struct bsc_msc_connection *con);
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal);
+
+struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num)
+{
+ struct bsc_config *conf;
+
+ llist_for_each_entry(conf, &nat->bsc_configs, entry)
+ if (conf->nr == num)
+ return conf;
+
+ return NULL;
+}
+
+static void queue_for_msc(struct bsc_msc_connection *con, struct msgb *msg)
+{
+ if (!con) {
+ LOGP(DLINP, LOGL_ERROR, "No MSC Connection assigned. Check your code.\n");
+ msgb_free(msg);
+ return;
+ }
+
+
+ if (osmo_wqueue_enqueue(&con->write_queue, msg) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+ msgb_free(msg);
+ }
+}
+
+static void send_reset_ack(struct bsc_connection *bsc)
+{
+ static const uint8_t gsm_reset_ack[] = {
+ 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01,
+ 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03,
+ 0x00, 0x01, 0x31,
+ };
+
+ bsc_send_data(bsc, gsm_reset_ack, sizeof(gsm_reset_ack), IPAC_PROTO_SCCP);
+}
+
+static void send_ping(struct bsc_connection *bsc)
+{
+ static const uint8_t id_ping[] = {
+ IPAC_MSGT_PING,
+ };
+
+ bsc_send_data(bsc, id_ping, sizeof(id_ping), IPAC_PROTO_IPACCESS);
+}
+
+static void send_pong(struct bsc_connection *bsc)
+{
+ static const uint8_t id_pong[] = {
+ IPAC_MSGT_PONG,
+ };
+
+ bsc_send_data(bsc, id_pong, sizeof(id_pong), IPAC_PROTO_IPACCESS);
+}
+
+static void bsc_pong_timeout(void *_bsc)
+{
+ struct bsc_connection *bsc = _bsc;
+
+ LOGP(DNAT, LOGL_ERROR, "BSC Nr: %d PONG timeout.\n", bsc->cfg->nr);
+ bsc_close_connection(bsc);
+}
+
+static void bsc_ping_timeout(void *_bsc)
+{
+ struct bsc_connection *bsc = _bsc;
+
+ if (bsc->nat->ping_timeout < 0)
+ return;
+
+ send_ping(bsc);
+
+ /* send another ping in 20 seconds */
+ osmo_timer_schedule(&bsc->ping_timeout, bsc->nat->ping_timeout, 0);
+
+ /* also start a pong timer */
+ osmo_timer_schedule(&bsc->pong_timeout, bsc->nat->pong_timeout, 0);
+}
+
+static void start_ping_pong(struct bsc_connection *bsc)
+{
+ osmo_timer_setup(&bsc->pong_timeout, bsc_pong_timeout, bsc);
+ osmo_timer_setup(&bsc->ping_timeout, bsc_ping_timeout, bsc);
+
+ bsc_ping_timeout(bsc);
+}
+
+static void send_id_ack(struct bsc_connection *bsc)
+{
+ static const uint8_t id_ack[] = {
+ IPAC_MSGT_ID_ACK
+ };
+
+ bsc_send_data(bsc, id_ack, sizeof(id_ack), IPAC_PROTO_IPACCESS);
+}
+
+static void send_id_req(struct bsc_nat *nat, struct bsc_connection *bsc)
+{
+ static const uint8_t s_id_req[] = {
+ IPAC_MSGT_ID_GET,
+ 0x01, IPAC_IDTAG_UNIT,
+ 0x01, IPAC_IDTAG_MACADDR,
+ 0x01, IPAC_IDTAG_LOCATION1,
+ 0x01, IPAC_IDTAG_LOCATION2,
+ 0x01, IPAC_IDTAG_EQUIPVERS,
+ 0x01, IPAC_IDTAG_SWVERSION,
+ 0x01, IPAC_IDTAG_UNITNAME,
+ 0x01, IPAC_IDTAG_SERNR,
+ };
+
+ uint8_t *mrand;
+ uint8_t id_req[sizeof(s_id_req) + (2+16)];
+ uint8_t *buf = &id_req[sizeof(s_id_req)];
+
+ /* copy the static data */
+ memcpy(id_req, s_id_req, sizeof(s_id_req));
+
+ /* put the RAND with length, tag, value */
+ buf = v_put(buf, 0x11);
+ buf = v_put(buf, 0x23);
+ mrand = bsc->last_rand;
+
+ if (RAND_bytes(mrand, 16) != 1)
+ goto failed_random;
+
+ memcpy(buf, mrand, 16);
+ buf += 16;
+
+ bsc_send_data(bsc, id_req, sizeof(id_req), IPAC_PROTO_IPACCESS);
+ return;
+
+failed_random:
+ /* the timeout will trigger and close this connection */
+ LOGP(DNAT, LOGL_ERROR, "Failed to read from urandom.\n");
+ return;
+}
+
+static struct msgb *nat_create_rlsd(struct nat_sccp_connection *conn)
+{
+ struct sccp_connection_released *rel;
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(4096, 128, "rlsd");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate released.\n");
+ return NULL;
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(*rel));
+ rel = (struct sccp_connection_released *) msg->l2h;
+ rel->type = SCCP_MSG_TYPE_RLSD;
+ rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+ rel->destination_local_reference = conn->remote_ref;
+ rel->source_local_reference = conn->patched_ref;
+
+ return msg;
+}
+
+static void nat_send_rlsd_ussd(struct bsc_nat *nat, struct nat_sccp_connection *conn)
+{
+ struct msgb *msg;
+
+ if (!nat->ussd_con)
+ return;
+
+ msg = nat_create_rlsd(conn);
+ if (!msg)
+ return;
+
+ bsc_do_write(&nat->ussd_con->queue, msg, IPAC_PROTO_SCCP);
+}
+
+static void nat_send_rlsd_msc(struct nat_sccp_connection *conn)
+{
+ struct msgb *msg;
+
+ msg = nat_create_rlsd(conn);
+ if (!msg)
+ return;
+
+ ipa_prepend_header(msg, IPAC_PROTO_SCCP);
+ queue_for_msc(conn->msc_con, msg);
+}
+
+static void nat_send_rlsd_bsc(struct nat_sccp_connection *conn)
+{
+ struct msgb *msg;
+ struct sccp_connection_released *rel;
+
+ msg = msgb_alloc_headroom(4096, 128, "rlsd");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(*rel));
+ rel = (struct sccp_connection_released *) msg->l2h;
+ rel->type = SCCP_MSG_TYPE_RLSD;
+ rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE;
+ rel->destination_local_reference = conn->real_ref;
+ rel->source_local_reference = conn->remote_ref;
+
+ bsc_write(conn->bsc, msg, IPAC_PROTO_SCCP);
+}
+
+static struct msgb *nat_creat_clrc(struct nat_sccp_connection *conn, uint8_t cause)
+{
+ struct msgb *msg;
+ struct msgb *sccp;
+
+ msg = gsm0808_create_clear_command(cause);
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n");
+ return NULL;
+ }
+
+ sccp = sccp_create_dt1(&conn->real_ref, msg->data, msg->len);
+ if (!sccp) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate SCCP msg.\n");
+ msgb_free(msg);
+ return NULL;
+ }
+
+ msgb_free(msg);
+ return sccp;
+}
+
+static int nat_send_clrc_bsc(struct nat_sccp_connection *conn)
+{
+ struct msgb *sccp;
+
+ sccp = nat_creat_clrc(conn, 0x20);
+ if (!sccp)
+ return -1;
+ return bsc_write(conn->bsc, sccp, IPAC_PROTO_SCCP);
+}
+
+static void nat_send_rlc(struct bsc_msc_connection *msc_con,
+ struct sccp_source_reference *src,
+ struct sccp_source_reference *dst)
+{
+ struct sccp_connection_release_complete *rlc;
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(4096, 128, "rlc");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to sccp rlc.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(*rlc));
+ rlc = (struct sccp_connection_release_complete *) msg->l2h;
+ rlc->type = SCCP_MSG_TYPE_RLC;
+ rlc->destination_local_reference = *dst;
+ rlc->source_local_reference = *src;
+
+ ipa_prepend_header(msg, IPAC_PROTO_SCCP);
+
+ queue_for_msc(msc_con, msg);
+}
+
+static void send_mgcp_reset(struct bsc_connection *bsc)
+{
+ static const uint8_t mgcp_reset[] = {
+ "RSIP 1 13@mgw MGCP 1.0\r\n"
+ };
+
+ bsc_write_mgcp(bsc, mgcp_reset, sizeof mgcp_reset - 1);
+}
+
+void bsc_nat_send_mgcp_to_msc(struct bsc_nat *nat, struct msgb *msg)
+{
+ ipa_prepend_header(msg, IPAC_PROTO_MGCP_OLD);
+ queue_for_msc(nat->msc_con, msg);
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the MSC and need to be forwarded to
+ * a real BSC.
+ */
+static void initialize_msc_if_needed(struct bsc_msc_connection *msc_con)
+{
+ if (msc_con->first_contact)
+ return;
+
+ msc_con->first_contact = 1;
+ msc_send_reset(msc_con);
+}
+
+static void send_id_get_response(struct bsc_msc_connection *msc_con)
+{
+ struct msgb *msg = bsc_msc_id_get_resp(0, nat->token, NULL, 0);
+ if (!msg)
+ return;
+
+ ipa_prepend_header(msg, IPAC_PROTO_IPACCESS);
+ queue_for_msc(msc_con, msg);
+}
+
+/*
+ * Currently we are lacking refcounting so we need to copy each message.
+ */
+static void bsc_send_data(struct bsc_connection *bsc, const uint8_t *data, unsigned int length, int proto)
+{
+ struct msgb *msg;
+
+ if (length > 4096 - 128) {
+ LOGP(DLINP, LOGL_ERROR, "Can not send message of that size.\n");
+ return;
+ }
+
+ msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+ if (!msg) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, length);
+ memcpy(msg->data, data, length);
+
+ bsc_write(bsc, msg, proto);
+}
+
+/*
+ * Update the release statistics
+ */
+static void bsc_stat_reject(int filter, struct bsc_connection *bsc, int normal)
+{
+ if (!bsc->cfg) {
+ LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.");
+ return;
+ }
+
+ if (filter >= 0) {
+ LOGP(DNAT, LOGL_ERROR, "Connection was not rejected");
+ return;
+ }
+
+ if (filter == -1)
+ rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_ILL_PACKET]);
+ else if (normal)
+ rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_MSG]);
+ else
+ rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_REJECTED_CR]);
+}
+
+/*
+ * Release an established connection. We will have to release it to the BSC
+ * and to the network and we do it the following way.
+ * 1.) Give up on the MSC side
+ * 1.1) Send a RLSD message, it is a bit non standard but should work, we
+ * ignore the RLC... we might complain about it. Other options would
+ * be to send a Release Request, handle the Release Complete..
+ * 1.2) Mark the data structure to be con_local and wait for 2nd
+ *
+ * 2.) Give up on the BSC side
+ * 2.1) Depending on the con type reject the service, or just close it
+ */
+static void bsc_send_con_release(struct bsc_connection *bsc,
+ struct nat_sccp_connection *con,
+ struct bsc_filter_reject_cause *cause)
+{
+ struct msgb *rlsd;
+ /* 1. release the network */
+ rlsd = sccp_create_rlsd(&con->patched_ref, &con->remote_ref,
+ SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+ if (!rlsd)
+ LOGP(DNAT, LOGL_ERROR, "Failed to create RLSD message.\n");
+ else {
+ ipa_prepend_header(rlsd, IPAC_PROTO_SCCP);
+ queue_for_msc(con->msc_con, rlsd);
+ }
+ con->con_local = NAT_CON_END_LOCAL;
+ con->msc_con = NULL;
+
+ /* 2. release the BSC side */
+ if (con->filter_state.con_type == FLT_CON_TYPE_LU) {
+ struct msgb *payload, *udt;
+ payload = gsm48_create_loc_upd_rej(cause->lu_reject_cause);
+
+ if (payload) {
+ gsm0808_prepend_dtap_header(payload, 0);
+ udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+ if (udt)
+ bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+ else
+ LOGP(DNAT, LOGL_ERROR, "Failed to create DT1\n");
+
+ msgb_free(payload);
+ } else {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate LU Reject.\n");
+ }
+ }
+
+ nat_send_clrc_bsc(con);
+
+ rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+ SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+ if (!rlsd) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate RLSD for the BSC.\n");
+ sccp_connection_destroy(con);
+ return;
+ }
+
+ con->filter_state.con_type = FLT_CON_TYPE_LOCAL_REJECT;
+ bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+}
+
+static void bsc_send_con_refuse(struct bsc_connection *bsc,
+ struct bsc_nat_parsed *parsed, int con_type,
+ struct bsc_filter_reject_cause *cause)
+{
+ struct msgb *payload;
+ struct msgb *refuse;
+
+ if (con_type == FLT_CON_TYPE_LU)
+ payload = gsm48_create_loc_upd_rej(cause->lu_reject_cause);
+ else if (con_type == FLT_CON_TYPE_CM_SERV_REQ || con_type == FLT_CON_TYPE_SSA)
+ payload = gsm48_create_mm_serv_rej(cause->cm_reject_cause);
+ else {
+ LOGP(DNAT, LOGL_ERROR, "Unknown connection type: %d\n", con_type);
+ payload = NULL;
+ }
+
+ /*
+ * Some BSCs do not handle the payload inside a SCCP CREF msg
+ * so we will need to:
+ * 1.) Allocate a local connection and mark it as local..
+ * 2.) queue data for downstream.. and the RLC should delete everything
+ */
+ if (payload) {
+ struct msgb *cc, *udt, *clear, *rlsd;
+ struct nat_sccp_connection *con;
+ con = create_sccp_src_ref(bsc, parsed);
+ if (!con)
+ goto send_refuse;
+
+ /* declare it local and assign a unique remote_ref */
+ con->filter_state.con_type = FLT_CON_TYPE_LOCAL_REJECT;
+ con->con_local = NAT_CON_END_LOCAL;
+ con->has_remote_ref = 1;
+ con->remote_ref = con->patched_ref;
+
+ /* 1. create a confirmation */
+ cc = sccp_create_cc(&con->remote_ref, &con->real_ref);
+ if (!cc)
+ goto send_refuse;
+
+ /* 2. create the DT1 */
+ gsm0808_prepend_dtap_header(payload, 0);
+ udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+ if (!udt) {
+ msgb_free(cc);
+ goto send_refuse;
+ }
+
+ /* 3. send a Clear Command */
+ clear = nat_creat_clrc(con, 0x20);
+ if (!clear) {
+ msgb_free(cc);
+ msgb_free(udt);
+ goto send_refuse;
+ }
+
+ /* 4. send a RLSD */
+ rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+ SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+ if (!rlsd) {
+ msgb_free(cc);
+ msgb_free(udt);
+ msgb_free(clear);
+ goto send_refuse;
+ }
+
+ bsc_write(bsc, cc, IPAC_PROTO_SCCP);
+ bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+ bsc_write(bsc, clear, IPAC_PROTO_SCCP);
+ bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+ msgb_free(payload);
+ return;
+ }
+
+
+send_refuse:
+ if (payload)
+ msgb_free(payload);
+
+ refuse = sccp_create_refuse(parsed->src_local_ref,
+ SCCP_REFUSAL_SCCP_FAILURE, NULL, 0);
+ if (!refuse) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Creating refuse msg failed for SCCP 0x%x on BSC Nr: %d.\n",
+ sccp_src_ref_to_int(parsed->src_local_ref), bsc->cfg->nr);
+ return;
+ }
+
+ bsc_write(bsc, refuse, IPAC_PROTO_SCCP);
+}
+
+static void bsc_nat_send_paging(struct bsc_connection *bsc, struct msgb *msg)
+{
+ if (bsc->cfg->forbid_paging) {
+ LOGP(DNAT, LOGL_DEBUG, "Paging forbidden for BTS: %d\n", bsc->cfg->nr);
+ return;
+ }
+
+ bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), IPAC_PROTO_SCCP);
+}
+
+static void bsc_nat_handle_paging(struct bsc_nat *nat, struct msgb *msg)
+{
+ struct bsc_connection *bsc;
+ const uint8_t *paging_start;
+ int paging_length, i, ret;
+
+ ret = bsc_nat_find_paging(msg, &paging_start, &paging_length);
+ if (ret != 0) {
+ LOGP(DNAT, LOGL_ERROR, "Could not parse paging message: %d\n", ret);
+ return;
+ }
+
+ /* This is quite expensive now */
+ for (i = 0; i < paging_length; i += 2) {
+ unsigned int _lac = ntohs(*(unsigned int *) &paging_start[i]);
+ unsigned int paged = 0;
+ llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+ if (!bsc->cfg)
+ continue;
+ if (!bsc->authenticated)
+ continue;
+ if (!bsc_config_handles_lac(bsc->cfg, _lac))
+ continue;
+ bsc_nat_send_paging(bsc, msg);
+ paged += 1;
+ }
+
+ /* highlight a possible config issue */
+ if (paged == 0)
+ LOGP(DNAT, LOGL_ERROR, "No BSC for LAC %d/0x%d\n", _lac, _lac);
+
+ }
+}
+
+
+/*
+ * Update the auth status. This can be either a CIPHER MODE COMMAND or
+ * a CM Serivce Accept. Maybe also LU Accept or such in the future.
+ */
+static void update_con_authorize(struct nat_sccp_connection *con,
+ struct bsc_nat_parsed *parsed,
+ struct msgb *msg)
+{
+ if (!con)
+ return;
+ if (con->authorized)
+ return;
+
+ if (parsed->bssap == BSSAP_MSG_BSS_MANAGEMENT &&
+ parsed->gsm_type == BSS_MAP_MSG_CIPHER_MODE_CMD) {
+ con->authorized = 1;
+ } else if (parsed->bssap == BSSAP_MSG_DTAP) {
+ uint8_t msg_type, proto;
+ uint32_t len;
+ struct gsm48_hdr *hdr48;
+ hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+ if (!hdr48)
+ return;
+
+ proto = gsm48_hdr_pdisc(hdr48);
+ msg_type = gsm48_hdr_msg_type(hdr48);
+ if (proto == GSM48_PDISC_MM &&
+ msg_type == GSM48_MT_MM_CM_SERV_ACC)
+ con->authorized = 1;
+ }
+}
+
+static int forward_sccp_to_bts(struct bsc_msc_connection *msc_con, struct msgb *msg)
+{
+ struct nat_sccp_connection *con = NULL;
+ struct bsc_connection *bsc;
+ struct bsc_nat_parsed *parsed;
+ int proto;
+
+ /* filter, drop, patch the message? */
+ 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(DIR_BSC, msg, parsed))
+ goto exit;
+
+ proto = parsed->ipa_proto;
+
+ /* Route and modify the SCCP packet */
+ if (proto == IPAC_PROTO_SCCP) {
+ switch (parsed->sccp_type) {
+ case SCCP_MSG_TYPE_UDT:
+ /* forward UDT messages to every BSC */
+ goto send_to_all;
+ break;
+ case SCCP_MSG_TYPE_RLSD:
+ case SCCP_MSG_TYPE_CREF:
+ case SCCP_MSG_TYPE_DT1:
+ case SCCP_MSG_TYPE_IT:
+ con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+ if (parsed->gsm_type == BSS_MAP_MSG_ASSIGMENT_RQST) {
+ osmo_counter_inc(nat->stats.sccp.calls);
+
+ if (con) {
+ struct rate_ctr_group *ctrg;
+ ctrg = con->bsc->cfg->stats.ctrg;
+ rate_ctr_inc(&ctrg->ctr[BCFG_CTR_SCCP_CALLS]);
+ if (bsc_mgcp_assign_patch(con, msg) != 0)
+ LOGP(DNAT, LOGL_ERROR, "Failed to assign...\n");
+ } else
+ LOGP(DNAT, LOGL_ERROR, "Assignment command but no BSC.\n");
+ } else if (con && con->con_local == NAT_CON_END_USSD &&
+ parsed->gsm_type == BSS_MAP_MSG_CLEAR_CMD) {
+ LOGP(DNAT, LOGL_NOTICE, "Clear Command for USSD Connection. Ignoring.\n");
+ con = NULL;
+ }
+ break;
+ case SCCP_MSG_TYPE_CC:
+ con = patch_sccp_src_ref_to_bsc(msg, parsed, nat);
+ if (!con || update_sccp_src_ref(con, parsed) != 0)
+ goto exit;
+ break;
+ case SCCP_MSG_TYPE_RLC:
+ LOGP(DNAT, LOGL_ERROR, "Unexpected release complete from MSC.\n");
+ goto exit;
+ break;
+ case SCCP_MSG_TYPE_CR:
+ /* MSC never opens a SCCP connection, fall through */
+ default:
+ goto exit;
+ }
+
+ if (!con && parsed->sccp_type == SCCP_MSG_TYPE_RLSD) {
+ LOGP(DNAT, LOGL_NOTICE, "Sending fake RLC on RLSD message to network.\n");
+ /* Exchange src/dest for the reply */
+ nat_send_rlc(msc_con, &parsed->original_dest_ref,
+ parsed->src_local_ref);
+ } else if (!con)
+ LOGP(DNAT, LOGL_ERROR, "Unknown connection for msg type: 0x%x from the MSC.\n", parsed->sccp_type);
+ }
+
+ if (!con) {
+ talloc_free(parsed);
+ return -1;
+ }
+ if (!con->bsc->authenticated) {
+ talloc_free(parsed);
+ LOGP(DNAT, LOGL_ERROR, "Selected BSC not authenticated.\n");
+ return -1;
+ }
+
+ update_con_authorize(con, parsed, msg);
+ talloc_free(parsed);
+
+ bsc_send_data(con->bsc, msg->l2h, msgb_l2len(msg), proto);
+ return 0;
+
+send_to_all:
+ /*
+ * Filter Paging from the network. We do not want to send a PAGING
+ * Command to every BSC in our network. We will analys the PAGING
+ * message and then send it to the authenticated messages...
+ */
+ if (parsed->ipa_proto == IPAC_PROTO_SCCP && parsed->gsm_type == BSS_MAP_MSG_PAGING) {
+ bsc_nat_handle_paging(nat, msg);
+ goto exit;
+ }
+ /* currently send this to every BSC connected */
+ llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) {
+ if (!bsc->authenticated)
+ continue;
+
+ bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto);
+ }
+
+exit:
+ talloc_free(parsed);
+ return 0;
+}
+
+static void msc_connection_was_lost(struct bsc_msc_connection *con)
+{
+ struct bsc_connection *bsc, *tmp;
+
+ LOGP(DMSC, LOGL_ERROR, "Closing all connections downstream.\n");
+ llist_for_each_entry_safe(bsc, tmp, &nat->bsc_connections, list_entry)
+ bsc_close_connection(bsc);
+
+ bsc_mgcp_free_endpoints(nat);
+ bsc_msc_schedule_connect(con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+ osmo_counter_inc(nat->stats.msc.reconn);
+}
+
+static void msc_send_reset(struct bsc_msc_connection *msc_con)
+{
+ static const uint8_t reset[] = {
+ 0x00, 0x12, 0xfd,
+ 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe,
+ 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04,
+ 0x01, 0x20
+ };
+
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(4096, 128, "08.08 reset");
+ if (!msg) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate reset msg.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, sizeof(reset));
+ memcpy(msg->l2h, reset, msgb_l2len(msg));
+
+ queue_for_msc(msc_con, msg);
+
+ LOGP(DMSC, LOGL_NOTICE, "Scheduled GSM0808 reset msg for the MSC.\n");
+}
+
+static int ipaccess_msc_read_cb(struct osmo_fd *bfd)
+{
+ struct bsc_msc_connection *msc_con;
+ struct msgb *msg = NULL;
+ struct ipaccess_head *hh;
+ int ret;
+
+ msc_con = (struct bsc_msc_connection *) bfd->data;
+
+ ret = ipa_msg_recv_buffered(bfd->fd, &msg, &msc_con->pending_msg);
+ if (ret <= 0) {
+ if (ret == -EAGAIN)
+ return 0;
+ if (ret == 0)
+ LOGP(DNAT, LOGL_FATAL,
+ "The connection the MSC(%s) was lost, exiting\n",
+ msc_con->name);
+ else
+ LOGP(DNAT, LOGL_ERROR,
+ "Failed to parse ip access message on %s: %d\n",
+ msc_con->name, ret);
+
+ bsc_msc_lost(msc_con);
+ return -1;
+ }
+
+ LOGP(DNAT, LOGL_DEBUG,
+ "MSG from MSC(%s): %s proto: %d\n", msc_con->name,
+ osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+
+ /* handle base message handling */
+ hh = (struct ipaccess_head *) msg->data;
+
+ /* initialize the networking. This includes sending a GSM08.08 message */
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ ipa_ccm_rcvmsg_base(msg, bfd);
+ if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+ initialize_msc_if_needed(msc_con);
+ else if (msg->l2h[0] == IPAC_MSGT_ID_GET)
+ send_id_get_response(msc_con);
+ } else if (hh->proto == IPAC_PROTO_SCCP) {
+ forward_sccp_to_bts(msc_con, msg);
+ } else if (hh->proto == IPAC_PROTO_MGCP_OLD) {
+ bsc_nat_handle_mgcp(nat, msg);
+ }
+
+ msgb_free(msg);
+ return 0;
+}
+
+static int ipaccess_msc_write_cb(struct osmo_fd *bfd, struct msgb *msg)
+{
+ int rc;
+ rc = write(bfd->fd, msg->data, msg->len);
+
+ if (rc != msg->len) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to write MSG to MSC.\n");
+ return -1;
+ }
+
+ return rc;
+}
+
+/*
+ * Below is the handling of messages coming
+ * from the BSC and need to be forwarded to
+ * a real BSC.
+ */
+
+/*
+ * Remove the connection from the connections list,
+ * remove it from the patching of SCCP header lists
+ * as well. Maybe in the future even close connection..
+ */
+void bsc_close_connection(struct bsc_connection *connection)
+{
+ struct nat_sccp_connection *sccp_patch, *tmp;
+ struct bsc_cmd_list *cmd_entry, *cmd_tmp;
+ struct rate_ctr *ctr = NULL;
+
+ /* stop the timeout timer */
+ osmo_timer_del(&connection->id_timeout);
+ osmo_timer_del(&connection->ping_timeout);
+ osmo_timer_del(&connection->pong_timeout);
+
+ if (connection->cfg)
+ ctr = &connection->cfg->stats.ctrg->ctr[BCFG_CTR_DROPPED_SCCP];
+
+ /* remove all SCCP connections */
+ llist_for_each_entry_safe(sccp_patch, tmp, &nat->sccp_connections, list_entry) {
+ if (sccp_patch->bsc != connection)
+ continue;
+
+ if (ctr)
+ rate_ctr_inc(ctr);
+ if (sccp_patch->has_remote_ref) {
+ if (sccp_patch->con_local == NAT_CON_END_MSC)
+ nat_send_rlsd_msc(sccp_patch);
+ else if (sccp_patch->con_local == NAT_CON_END_USSD)
+ nat_send_rlsd_ussd(nat, sccp_patch);
+ }
+
+ sccp_connection_destroy(sccp_patch);
+ }
+
+ /* Reply to all outstanding commands */
+ llist_for_each_entry_safe(cmd_entry, cmd_tmp, &connection->cmd_pending, list_entry) {
+ cmd_entry->cmd->type = CTRL_TYPE_ERROR;
+ cmd_entry->cmd->reply = "BSC closed the connection";
+ ctrl_cmd_send(&cmd_entry->ccon->write_queue, cmd_entry->cmd);
+ bsc_nat_ctrl_del_pending(cmd_entry);
+ }
+
+ /* close endpoints allocated by this BSC */
+ bsc_mgcp_clear_endpoints_for(connection);
+
+ osmo_fd_unregister(&connection->write_queue.bfd);
+ close(connection->write_queue.bfd.fd);
+ osmo_wqueue_clear(&connection->write_queue);
+ llist_del(&connection->list_entry);
+
+ if (connection->pending_msg) {
+ LOGP(DNAT, LOGL_ERROR, "Dropping partial message on connection %d.\n",
+ connection->cfg ? connection->cfg->nr : -1);
+ msgb_free(connection->pending_msg);
+ connection->pending_msg = NULL;
+ }
+
+ talloc_free(connection);
+}
+
+static void bsc_maybe_close(struct bsc_connection *bsc)
+{
+ struct nat_sccp_connection *sccp;
+ if (!bsc->nat->blocked)
+ return;
+
+ /* are there any connections left */
+ llist_for_each_entry(sccp, &bsc->nat->sccp_connections, list_entry)
+ if (sccp->bsc == bsc)
+ return;
+
+ /* nothing left, close the BSC */
+ LOGP(DNAT, LOGL_NOTICE, "Cleaning up BSC %d in blocking mode.\n",
+ bsc->cfg ? bsc->cfg->nr : -1);
+ bsc_close_connection(bsc);
+}
+
+static void ipaccess_close_bsc(void *data)
+{
+ struct sockaddr_in sock;
+ socklen_t len = sizeof(sock);
+ struct bsc_connection *conn = data;
+
+
+ getpeername(conn->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+ LOGP(DNAT, LOGL_ERROR, "BSC on %s didn't respond to identity request. Closing.\n",
+ inet_ntoa(sock.sin_addr));
+ bsc_close_connection(conn);
+}
+
+static int verify_key(struct bsc_connection *conn, struct bsc_config *conf, const uint8_t *key, const int keylen)
+{
+ struct osmo_auth_vector vec;
+
+ struct osmo_sub_auth_data auth = {
+ .type = OSMO_AUTH_TYPE_GSM,
+ .algo = OSMO_AUTH_ALG_MILENAGE,
+ };
+
+ /* expect a specific keylen */
+ if (keylen != 8) {
+ LOGP(DNAT, LOGL_ERROR, "Key length is wrong: %d for bsc nr %d\n",
+ keylen, conf->nr);
+ return 0;
+ }
+
+ memcpy(auth.u.umts.opc, conf->key, 16);
+ memcpy(auth.u.umts.k, conf->key, 16);
+ memset(auth.u.umts.amf, 0, 2);
+ auth.u.umts.sqn = 0;
+
+ memset(&vec, 0, sizeof(vec));
+ osmo_auth_gen_vec(&vec, &auth, conn->last_rand);
+
+ if (vec.res_len != 8) {
+ LOGP(DNAT, LOGL_ERROR, "Res length is wrong: %d for bsc nr %d\n",
+ vec.res_len, conf->nr);
+ return 0;
+ }
+
+ return osmo_constant_time_cmp(vec.res, key, 8) == 0;
+}
+
+static void ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection *bsc)
+{
+ struct bsc_config *conf;
+ const char *token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+ int len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+ const uint8_t *xres = TLVP_VAL(tvp, 0x24);
+ const int xlen = TLVP_LEN(tvp, 0x24);
+
+ if (bsc->cfg) {
+ LOGP(DNAT, LOGL_ERROR, "Reauth on fd %d bsc nr %d\n",
+ bsc->write_queue.bfd.fd, bsc->cfg->nr);
+ return;
+ }
+
+ if (len <= 0) {
+ LOGP(DNAT, LOGL_ERROR, "Token with length zero on fd: %d\n",
+ bsc->write_queue.bfd.fd);
+ return;
+ }
+
+ if (token[len - 1] != '\0') {
+ LOGP(DNAT, LOGL_ERROR, "Token not null terminated on fd: %d\n",
+ bsc->write_queue.bfd.fd);
+ return;
+ }
+
+ /*
+ * New systems have fixed the structure of the message but
+ * we need to support old ones too.
+ */
+ if (len >= 2 && token[len - 2] == '\0')
+ len -= 1;
+
+ conf = bsc_config_by_token(bsc->nat, token, len);
+ if (!conf) {
+ LOGP(DNAT, LOGL_ERROR,
+ "No bsc found for token '%s' len %d on fd: %d.\n", token,
+ bsc->write_queue.bfd.fd, len);
+ bsc_close_connection(bsc);
+ return;
+ }
+
+ /* We have set a key and expect it to be present */
+ if (conf->key_present && !verify_key(bsc, conf, xres, xlen - 1)) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Wrong key for bsc nr %d fd: %d.\n", conf->nr,
+ bsc->write_queue.bfd.fd);
+ bsc_close_connection(bsc);
+ return;
+ }
+
+ rate_ctr_inc(&conf->stats.ctrg->ctr[BCFG_CTR_NET_RECONN]);
+ bsc->authenticated = 1;
+ bsc->cfg = conf;
+ osmo_timer_del(&bsc->id_timeout);
+ LOGP(DNAT, LOGL_NOTICE, "Authenticated bsc nr: %d on fd %d\n",
+ conf->nr, bsc->write_queue.bfd.fd);
+ start_ping_pong(bsc);
+}
+
+static void handle_con_stats(struct nat_sccp_connection *con)
+{
+ struct rate_ctr_group *ctrg;
+ int id = bsc_conn_type_to_ctr(con);
+
+ if (id == -1)
+ return;
+
+ if (!con->bsc || !con->bsc->cfg)
+ return;
+
+ ctrg = con->bsc->cfg->stats.ctrg;
+ rate_ctr_inc(&ctrg->ctr[id]);
+}
+
+static int forward_sccp_to_msc(struct bsc_connection *bsc, struct msgb *msg)
+{
+ int con_filter = 0;
+ char *imsi = NULL;
+ struct bsc_msc_connection *con_msc = NULL;
+ struct bsc_connection *con_bsc = NULL;
+ int con_type;
+ struct bsc_nat_parsed *parsed;
+ struct bsc_filter_reject_cause cause;
+
+ /* Parse and filter messages */
+ parsed = bsc_nat_parse(msg);
+ if (!parsed) {
+ LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ if (bsc_nat_filter_ipa(DIR_MSC, msg, parsed))
+ goto exit;
+
+ /*
+ * check authentication after filtering to not reject auth
+ * responses coming from the BSC. We have to make sure that
+ * nothing from the exit path will forward things to the MSC
+ */
+ if (!bsc->authenticated) {
+ LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+
+ /* modify the SCCP entries */
+ if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+ int filter;
+ struct nat_sccp_connection *con;
+ switch (parsed->sccp_type) {
+ case SCCP_MSG_TYPE_CR:
+ memset(&cause, 0, sizeof(cause));
+ filter = bsc_nat_filter_sccp_cr(bsc, msg, parsed,
+ &con_type, &imsi, &cause);
+ if (filter < 0) {
+ if (imsi)
+ bsc_nat_inform_reject(bsc, imsi);
+ bsc_stat_reject(filter, bsc, 0);
+ goto exit3;
+ }
+
+ if (!create_sccp_src_ref(bsc, parsed))
+ goto exit2;
+ con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+ OSMO_ASSERT(con);
+ con->msc_con = bsc->nat->msc_con;
+ con_msc = con->msc_con;
+ con->filter_state.con_type = con_type;
+ con->filter_state.imsi_checked = filter;
+ bsc_nat_extract_lac(bsc, con, parsed, msg);
+ if (imsi)
+ con->filter_state.imsi = talloc_steal(con, imsi);
+ imsi = NULL;
+ con_bsc = con->bsc;
+ handle_con_stats(con);
+ break;
+ case SCCP_MSG_TYPE_RLSD:
+ case SCCP_MSG_TYPE_CREF:
+ case SCCP_MSG_TYPE_DT1:
+ case SCCP_MSG_TYPE_CC:
+ case SCCP_MSG_TYPE_IT:
+ con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+ if (con) {
+ /* only filter non local connections */
+ if (!con->con_local) {
+ memset(&cause, 0, sizeof(cause));
+ filter = bsc_nat_filter_dt(bsc, msg,
+ con, parsed, &cause);
+ if (filter < 0) {
+ if (con->filter_state.imsi)
+ bsc_nat_inform_reject(bsc,
+ con->filter_state.imsi);
+ bsc_stat_reject(filter, bsc, 1);
+ bsc_send_con_release(bsc, con, &cause);
+ con = NULL;
+ goto exit2;
+ }
+
+ /* hand data to a side channel */
+ if (bsc_ussd_check(con, parsed, msg) == 1)
+ con->con_local = NAT_CON_END_USSD;
+
+ /*
+ * Optionally rewrite setup message. This can
+ * replace the msg and the parsed structure becomes
+ * invalid.
+ */
+ msg = bsc_nat_rewrite_msg(bsc->nat, msg, parsed,
+ con->filter_state.imsi);
+ talloc_free(parsed);
+ parsed = NULL;
+ } else if (con->con_local == NAT_CON_END_USSD) {
+ bsc_ussd_check(con, parsed, msg);
+ }
+
+ con_bsc = con->bsc;
+ con_msc = con->msc_con;
+ con_filter = con->con_local;
+ }
+
+ break;
+ case SCCP_MSG_TYPE_RLC:
+ con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
+ if (con) {
+ con_bsc = con->bsc;
+ con_msc = con->msc_con;
+ con_filter = con->con_local;
+ }
+ remove_sccp_src_ref(bsc, msg, parsed);
+ bsc_maybe_close(bsc);
+ break;
+ case SCCP_MSG_TYPE_UDT:
+ /* simply forward everything */
+ con = NULL;
+ break;
+ default:
+ LOGP(DNAT, LOGL_ERROR, "Not forwarding to msc sccp type: 0x%x\n", parsed->sccp_type);
+ con = NULL;
+ goto exit2;
+ break;
+ }
+ } else if (parsed->ipa_proto == IPAC_PROTO_MGCP_OLD) {
+ bsc_mgcp_forward(bsc, msg);
+ goto exit2;
+ } else {
+ LOGP(DNAT, LOGL_ERROR, "Not forwarding unknown stream id: 0x%x\n", parsed->ipa_proto);
+ goto exit2;
+ }
+
+ if (con_msc && con_bsc != bsc) {
+ LOGP(DNAT, LOGL_ERROR, "The connection belongs to a different BTS: input: %d con: %d\n",
+ bsc->cfg->nr, con_bsc->cfg->nr);
+ goto exit2;
+ }
+
+ /* do not forward messages to the MSC */
+ if (con_filter)
+ goto exit2;
+
+ if (!con_msc) {
+ LOGP(DNAT, LOGL_ERROR, "Not forwarding data bsc_nr: %d ipa: %d type: 0x%x\n",
+ bsc->cfg->nr,
+ parsed ? parsed->ipa_proto : -1,
+ parsed ? parsed->sccp_type : -1);
+ goto exit2;
+ }
+
+ /* send the non-filtered but maybe modified msg */
+ queue_for_msc(con_msc, msg);
+ if (parsed)
+ talloc_free(parsed);
+ return 0;
+
+exit:
+ /* if we filter out the reset send an ack to the BSC */
+ if (parsed->bssap == 0 && parsed->gsm_type == BSS_MAP_MSG_RESET) {
+ send_reset_ack(bsc);
+ send_reset_ack(bsc);
+ } else if (parsed->ipa_proto == IPAC_PROTO_IPACCESS) {
+ /* do we know who is handling this? */
+ if (msg->l2h[0] == IPAC_MSGT_ID_RESP && msgb_l2len(msg) > 2) {
+ struct tlv_parsed tvp;
+ int ret;
+ ret = ipa_ccm_idtag_parse_off(&tvp,
+ (unsigned char *) msg->l2h + 2,
+ msgb_l2len(msg) - 2, 0);
+ if (ret < 0) {
+ LOGP(DNAT, LOGL_ERROR, "ignoring IPA response "
+ "message with malformed TLVs\n");
+ return ret;
+ }
+ if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+ ipaccess_auth_bsc(&tvp, bsc);
+ }
+
+ goto exit2;
+ }
+
+exit2:
+ if (imsi)
+ talloc_free(imsi);
+ talloc_free(parsed);
+ msgb_free(msg);
+ return -1;
+
+exit3:
+ /* send a SCCP Connection Refused */
+ if (imsi)
+ talloc_free(imsi);
+ bsc_send_con_refuse(bsc, parsed, con_type, &cause);
+ talloc_free(parsed);
+ msgb_free(msg);
+ return -1;
+}
+
+static int ipaccess_bsc_read_cb(struct osmo_fd *bfd)
+{
+ struct bsc_connection *bsc = bfd->data;
+ struct msgb *msg = NULL;
+ struct ipaccess_head *hh;
+ struct ipaccess_head_ext *hh_ext;
+ int ret;
+
+ ret = ipa_msg_recv_buffered(bfd->fd, &msg, &bsc->pending_msg);
+ if (ret <= 0) {
+ if (ret == -EAGAIN)
+ return 0;
+ if (ret == 0)
+ LOGP(DNAT, LOGL_ERROR,
+ "The connection to the BSC Nr: %d was lost. Cleaning it\n",
+ bsc->cfg ? bsc->cfg->nr : -1);
+ else
+ LOGP(DNAT, LOGL_ERROR,
+ "Stream error on BSC Nr: %d. Failed to parse ip access message: %d (%s)\n",
+ bsc->cfg ? bsc->cfg->nr : -1, ret, strerror(-ret));
+
+ bsc_close_connection(bsc);
+ return -1;
+ }
+
+
+ LOGP(DNAT, LOGL_DEBUG, "MSG from BSC: %s proto: %d\n", osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+
+ /* Handle messages from the BSC */
+ hh = (struct ipaccess_head *) msg->data;
+
+ /* stop the pong timeout */
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ if (msg->l2h[0] == IPAC_MSGT_PONG) {
+ osmo_timer_del(&bsc->pong_timeout);
+ msgb_free(msg);
+ return 0;
+ } else if (msg->l2h[0] == IPAC_MSGT_PING) {
+ send_pong(bsc);
+ msgb_free(msg);
+ return 0;
+ }
+ /* Message contains the ipaccess_head_ext header, investigate further */
+ } else if (hh->proto == IPAC_PROTO_OSMO &&
+ msg->len > sizeof(*hh) + sizeof(*hh_ext)) {
+
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ /* l2h is where the actual command data is expected */
+ msg->l2h = hh_ext->data;
+
+ if (hh_ext->proto == IPAC_PROTO_EXT_CTRL)
+ return bsc_nat_handle_ctrlif_msg(bsc, msg);
+ }
+
+ /* FIXME: Currently no PONG is sent to the BSC */
+ /* FIXME: Currently no ID ACK is sent to the BSC */
+ forward_sccp_to_msc(bsc, msg);
+
+ return 0;
+}
+
+static int ipaccess_listen_bsc_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ struct bsc_connection *bsc;
+ int fd, rc, on;
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+ if (fd < 0) {
+ perror("accept");
+ return fd;
+ }
+
+ /* count the reconnect */
+ osmo_counter_inc(nat->stats.bsc.reconn);
+
+ /*
+ * if we are not connected to a msc... just close the socket
+ */
+ if (!bsc_nat_msc_is_connected(nat)) {
+ LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due lack of MSC connection.\n");
+ close(fd);
+ return 0;
+ }
+
+ if (nat->blocked) {
+ LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due NAT being blocked.\n");
+ close(fd);
+ return 0;
+ }
+
+ on = 1;
+ rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+ if (rc != 0)
+ LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+ rc = setsockopt(fd, IPPROTO_IP, IP_TOS,
+ &nat->bsc_ip_dscp, sizeof(nat->bsc_ip_dscp));
+ if (rc != 0)
+ LOGP(DNAT, LOGL_ERROR, "Failed to set IP_TOS: %s\n", strerror(errno));
+
+ /* todo... do something with the connection */
+ /* todo... use GNUtls to see if we want to trust this as a BTS */
+
+ /*
+ *
+ */
+ bsc = bsc_connection_alloc(nat);
+ if (!bsc) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate BSC struct.\n");
+ close(fd);
+ return -1;
+ }
+
+ bsc->write_queue.bfd.data = bsc;
+ bsc->write_queue.bfd.fd = fd;
+ bsc->write_queue.read_cb = ipaccess_bsc_read_cb;
+ bsc->write_queue.write_cb = bsc_write_cb;
+ bsc->write_queue.bfd.when = BSC_FD_READ;
+ if (osmo_fd_register(&bsc->write_queue.bfd) < 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to register BSC fd.\n");
+ close(fd);
+ talloc_free(bsc);
+ return -2;
+ }
+
+ LOGP(DNAT, LOGL_NOTICE, "BSC connection on %d with IP: %s\n",
+ fd, inet_ntoa(sa.sin_addr));
+
+ llist_add(&bsc->list_entry, &nat->bsc_connections);
+ bsc->last_id = 0;
+
+ send_id_ack(bsc);
+ send_id_req(nat, bsc);
+ send_mgcp_reset(bsc);
+
+ /*
+ * start the hangup timer
+ */
+ osmo_timer_setup(&bsc->id_timeout, ipaccess_close_bsc, bsc);
+ osmo_timer_schedule(&bsc->id_timeout, nat->auth_timeout, 0);
+ return 0;
+}
+
+static void print_usage()
+{
+ printf("Usage: bsc_nat\n");
+}
+
+static void print_help()
+{
+ printf(" Some useful help...\n");
+ printf(" -h --help this text\n");
+ printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+ printf(" -D --daemonize Fork the process into a background daemon\n");
+ printf(" -s --disable-color\n");
+ printf(" -c --config-file filename The config file to use.\n");
+ printf(" -m --msc=IP. The address of the MSC.\n");
+ printf(" -l --local=IP. The local address of this BSC.\n");
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"debug", 1, 0, 'd'},
+ {"daemonize", 0, 0, 'D'},
+ {"config-file", 1, 0, 'c'},
+ {"disable-color", 0, 0, 's'},
+ {"timestamp", 0, 0, 'T'},
+ {"msc", 1, 0, 'm'},
+ {"local", 1, 0, 'l'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hd:sTPc:m:l:D",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'm':
+ msc_ip = optarg;
+ break;
+ case 'l':
+ inet_aton(optarg, &local_addr);
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+}
+
+static void signal_handler(int signal)
+{
+ switch (signal) {
+ case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report
+ * and then return to the caller, who will abort the process */
+ case SIGUSR1:
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void sccp_close_unconfirmed(void *_data)
+{
+ int destroyed = 0;
+ struct bsc_connection *bsc, *bsc_tmp;
+ struct nat_sccp_connection *conn, *tmp1;
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ llist_for_each_entry_safe(conn, tmp1, &nat->sccp_connections, list_entry) {
+ if (conn->has_remote_ref)
+ continue;
+
+ int diff = (now.tv_sec - conn->creation_time.tv_sec) / 60;
+ if (diff < SCCP_CLOSE_TIME_TIMEOUT)
+ continue;
+
+ LOGP(DNAT, LOGL_ERROR,
+ "SCCP connection 0x%x/0x%x was never confirmed on bsc nr. %d\n",
+ sccp_src_ref_to_int(&conn->real_ref),
+ sccp_src_ref_to_int(&conn->patched_ref),
+ conn->bsc->cfg->nr);
+ sccp_connection_destroy(conn);
+ destroyed = 1;
+ }
+
+ if (!destroyed)
+ goto out;
+
+ /* now close out any BSC */
+ llist_for_each_entry_safe(bsc, bsc_tmp, &nat->bsc_connections, list_entry)
+ bsc_maybe_close(bsc);
+
+out:
+ osmo_timer_schedule(&sccp_close, SCCP_CLOSE_TIME, 0);
+}
+
+extern void *tall_ctr_ctx;
+static void talloc_init_ctx()
+{
+ tall_bsc_ctx = talloc_named_const(NULL, 0, "nat");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+ tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoBSCNAT",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ talloc_init_ctx();
+
+ osmo_init_logging(&log_info);
+
+ nat = bsc_nat_alloc();
+ if (!nat) {
+ fprintf(stderr, "Failed to allocate the BSC nat.\n");
+ return -4;
+ }
+
+ nat->mgcp_cfg = mgcp_config_alloc();
+ if (!nat->mgcp_cfg) {
+ fprintf(stderr, "Failed to allocate MGCP cfg.\n");
+ return -5;
+ }
+
+ /* We need to add mode-set for amr codecs */
+ nat->sdp_ensure_amr_mode_set = 1;
+
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ logging_vty_add_cmds(NULL);
+ osmo_stats_vty_add_cmds(&log_info);
+ bsc_nat_vty_init(nat);
+ ctrl_vty_init(tall_bsc_ctx);
+
+
+ /* parse options */
+ local_addr.s_addr = INADDR_ANY;
+ handle_options(argc, argv);
+
+ nat->include_base = dirname(talloc_strdup(tall_bsc_ctx, config_file));
+
+ rate_ctr_init(tall_bsc_ctx);
+ osmo_stats_init(tall_bsc_ctx);
+
+ /* init vty and parse */
+ if (mgcp_parse_config(config_file, nat->mgcp_cfg, MGCP_BSC_NAT) < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+ return -3;
+ }
+
+ /* start telnet after reading config for vty_get_bind_addr() */
+ if (telnet_init_dynif(tall_bsc_ctx, NULL, vty_get_bind_addr(),
+ OSMO_VTY_PORT_BSC_NAT)) {
+ fprintf(stderr, "Creating VTY telnet line failed\n");
+ return -5;
+ }
+
+ /* over rule the VTY config for MSC IP */
+ if (msc_ip)
+ bsc_nat_set_msc_ip(nat, msc_ip);
+
+ /* seed the PRNG */
+ srand(time(NULL));
+
+ LOGP(DNAT, LOGL_NOTICE, "BSCs configured from %s\n", nat->resolved_path);
+
+ /*
+ * Setup the MGCP code..
+ */
+ if (bsc_mgcp_nat_init(nat) != 0)
+ return -4;
+
+ /* connect to the MSC */
+ nat->msc_con = bsc_msc_create(nat, &nat->dests);
+ if (!nat->msc_con) {
+ fprintf(stderr, "Creating a bsc_msc_connection failed.\n");
+ exit(1);
+ }
+
+ /* start control interface after reading config for
+ * ctrl_vty_get_bind_addr() */
+ nat->ctrl = bsc_nat_controlif_setup(nat, ctrl_vty_get_bind_addr(),
+ OSMO_CTRL_PORT_BSC_NAT);
+ if (!nat->ctrl) {
+ fprintf(stderr, "Creating the control interface failed.\n");
+ exit(1);
+ }
+
+ nat->msc_con->name = "main MSC";
+ nat->msc_con->connection_loss = msc_connection_was_lost;
+ nat->msc_con->connected = msc_connection_connected;
+ nat->msc_con->write_queue.read_cb = ipaccess_msc_read_cb;
+ nat->msc_con->write_queue.write_cb = ipaccess_msc_write_cb;;
+ nat->msc_con->write_queue.bfd.data = nat->msc_con;
+ bsc_msc_connect(nat->msc_con);
+
+ /* wait for the BSC */
+ rc = make_sock(&bsc_listen, IPPROTO_TCP, ntohl(local_addr.s_addr),
+ 5000, 0, ipaccess_listen_bsc_cb, nat);
+ if (rc != 0) {
+ fprintf(stderr, "Failed to listen for BSC.\n");
+ exit(1);
+ }
+
+ rc = bsc_ussd_init(nat);
+ if (rc != 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to bind the USSD socket.\n");
+ exit(1);
+ }
+
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ osmo_init_ignore_signals();
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ /* recycle timer */
+ sccp_set_log_area(DSCCP);
+ osmo_timer_setup(&sccp_close, sccp_close_unconfirmed, NULL);
+ osmo_timer_schedule(&sccp_close, SCCP_CLOSE_TIME, 0);
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ return 0;
+}
+
+/* Close all connections handed out to the USSD module */
+int bsc_ussd_close_connections(struct bsc_nat *nat)
+{
+ struct nat_sccp_connection *con;
+ llist_for_each_entry(con, &nat->sccp_connections, list_entry) {
+ if (con->con_local != NAT_CON_END_USSD)
+ continue;
+ if (!con->bsc)
+ continue;
+
+ nat_send_clrc_bsc(con);
+ nat_send_rlsd_bsc(con);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_ctrl.c b/src/osmo-bsc_nat/bsc_nat_ctrl.c
new file mode 100644
index 000000000..128ea6518
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_ctrl.c
@@ -0,0 +1,524 @@
+/*
+ * (C) 2011-2012 by Holger Hans Peter Freyther
+ * (C) 2011-2012 by On-Waves
+ * (C) 2011 by Daniel Willmann
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <osmocom/vty/misc.h>
+
+#include <openbsc/ctrl.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+
+#define NAT_MAX_CTRL_ID 65535
+
+static struct bsc_nat *g_nat;
+
+static int bsc_id_unused(int id, struct bsc_connection *bsc)
+{
+ struct bsc_cmd_list *pending;
+
+ llist_for_each_entry(pending, &bsc->cmd_pending, list_entry) {
+ if (pending->nat_id == id)
+ return 0;
+ }
+ return 1;
+}
+
+static int get_next_free_bsc_id(struct bsc_connection *bsc)
+{
+ int new_id, overflow = 0;
+
+ new_id = bsc->last_id;
+
+ do {
+ new_id++;
+ if (new_id == NAT_MAX_CTRL_ID) {
+ new_id = 1;
+ overflow++;
+ }
+
+ if (bsc_id_unused(new_id, bsc)) {
+ bsc->last_id = new_id;
+ return new_id;
+ }
+ } while (overflow != 2);
+
+ return -1;
+}
+
+void bsc_nat_ctrl_del_pending(struct bsc_cmd_list *pending)
+{
+ llist_del(&pending->list_entry);
+ osmo_timer_del(&pending->timeout);
+ talloc_free(pending->cmd);
+ talloc_free(pending);
+}
+
+static struct bsc_cmd_list *bsc_get_pending(struct bsc_connection *bsc, char *id_str)
+{
+ struct bsc_cmd_list *cmd_entry;
+ int id = atoi(id_str);
+ if (id == 0)
+ return NULL;
+
+ llist_for_each_entry(cmd_entry, &bsc->cmd_pending, list_entry) {
+ if (cmd_entry->nat_id == id) {
+ return cmd_entry;
+ }
+ }
+ return NULL;
+}
+
+int bsc_nat_handle_ctrlif_msg(struct bsc_connection *bsc, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd;
+ struct bsc_cmd_list *pending;
+ char *var, *id;
+
+ cmd = ctrl_cmd_parse(bsc, msg);
+ msgb_free(msg);
+
+ if (!cmd) {
+ cmd = talloc_zero(bsc, struct ctrl_cmd);
+ if (!cmd) {
+ LOGP(DNAT, LOGL_ERROR, "OOM!\n");
+ return -ENOMEM;
+ }
+ cmd->type = CTRL_TYPE_ERROR;
+ cmd->id = "err";
+ cmd->reply = "Failed to parse command.";
+ goto err;
+ }
+
+ if (bsc->cfg && !llist_empty(&bsc->cfg->lac_list)) {
+ if (cmd->variable) {
+ var = talloc_asprintf(cmd, "net.0.bsc.%i.%s", bsc->cfg->nr,
+ cmd->variable);
+ if (!var) {
+ cmd->type = CTRL_TYPE_ERROR;
+ cmd->reply = "OOM";
+ goto err;
+ }
+ talloc_free(cmd->variable);
+ cmd->variable = var;
+ }
+
+ /* We have to handle TRAPs before matching pending */
+ if (cmd->type == CTRL_TYPE_TRAP) {
+ ctrl_cmd_send_to_all(bsc->nat->ctrl, cmd);
+ talloc_free(cmd);
+ return 0;
+ }
+
+ /* Find the pending command */
+ pending = bsc_get_pending(bsc, cmd->id);
+ if (pending) {
+ id = talloc_strdup(cmd, pending->cmd->id);
+ if (!id) {
+ cmd->type = CTRL_TYPE_ERROR;
+ cmd->reply = "OOM";
+ goto err;
+ }
+ cmd->id = id;
+ ctrl_cmd_send(&pending->ccon->write_queue, cmd);
+ bsc_nat_ctrl_del_pending(pending);
+ } else {
+ /* We need to handle TRAPS here */
+ if ((cmd->type != CTRL_TYPE_ERROR) &&
+ (cmd->type != CTRL_TYPE_TRAP)) {
+ LOGP(DNAT, LOGL_NOTICE, "Got control message "
+ "from BSC without pending entry\n");
+ cmd->type = CTRL_TYPE_ERROR;
+ cmd->reply = "No request outstanding";
+ goto err;
+ }
+ }
+ }
+ talloc_free(cmd);
+ return 0;
+err:
+ ctrl_cmd_send(&bsc->write_queue, cmd);
+ talloc_free(cmd);
+ return 0;
+}
+
+static void pending_timeout_cb(void *data)
+{
+ struct bsc_cmd_list *pending = data;
+ LOGP(DNAT, LOGL_ERROR, "Command timed out\n");
+ pending->cmd->type = CTRL_TYPE_ERROR;
+ pending->cmd->reply = "Command timed out";
+ ctrl_cmd_send(&pending->ccon->write_queue, pending->cmd);
+
+ bsc_nat_ctrl_del_pending(pending);
+}
+
+static void ctrl_conn_closed_cb(struct ctrl_connection *connection)
+{
+ struct bsc_connection *bsc;
+ struct bsc_cmd_list *pending, *tmp;
+
+ llist_for_each_entry(bsc, &g_nat->bsc_connections, list_entry) {
+ llist_for_each_entry_safe(pending, tmp, &bsc->cmd_pending, list_entry) {
+ if (pending->ccon == connection)
+ bsc_nat_ctrl_del_pending(pending);
+ }
+ }
+}
+
+static int extract_bsc_nr_variable(char *variable, unsigned int *nr, char **bsc_variable)
+{
+ char *nr_str, *tmp, *saveptr = NULL;
+
+ tmp = strtok_r(variable, ".", &saveptr);
+ tmp = strtok_r(NULL, ".", &saveptr);
+ tmp = strtok_r(NULL, ".", &saveptr);
+ nr_str = strtok_r(NULL, ".", &saveptr);
+ if (!nr_str)
+ return 0;
+ *nr = atoi(nr_str);
+
+ tmp = strtok_r(NULL, "\0", &saveptr);
+ if (!tmp)
+ return 0;
+
+ *bsc_variable = tmp;
+ return 1;
+}
+
+static int forward_to_bsc(struct ctrl_cmd *cmd)
+{
+ int ret = CTRL_CMD_HANDLED;
+ struct ctrl_cmd *bsc_cmd = NULL;
+ struct bsc_connection *bsc;
+ struct bsc_cmd_list *pending;
+ unsigned int nr;
+ char *bsc_variable;
+
+ /* Skip over the beginning (bsc.) */
+ if (!extract_bsc_nr_variable(cmd->variable, &nr, &bsc_variable)) {
+ cmd->reply = "command incomplete";
+ goto err;
+ }
+
+
+ llist_for_each_entry(bsc, &g_nat->bsc_connections, list_entry) {
+ if (!bsc->cfg)
+ continue;
+ if (!bsc->authenticated)
+ continue;
+ if (bsc->cfg->nr == nr) {
+ /* Add pending command to list */
+ pending = talloc_zero(bsc, struct bsc_cmd_list);
+ if (!pending) {
+ cmd->reply = "OOM";
+ goto err;
+ }
+
+ pending->nat_id = get_next_free_bsc_id(bsc);
+ if (pending->nat_id < 0) {
+ cmd->reply = "No free ID found";
+ goto err;
+ }
+
+ bsc_cmd = ctrl_cmd_cpy(bsc, cmd);
+ if (!bsc_cmd) {
+ cmd->reply = "Could not forward command";
+ goto err;
+ }
+
+ talloc_free(bsc_cmd->id);
+ bsc_cmd->id = talloc_asprintf(bsc_cmd, "%i", pending->nat_id);
+ if (!bsc_cmd->id) {
+ cmd->reply = "OOM";
+ goto err;
+ }
+
+ talloc_free(bsc_cmd->variable);
+ bsc_cmd->variable = talloc_strdup(bsc_cmd, bsc_variable);
+ if (!bsc_cmd->variable) {
+ cmd->reply = "OOM";
+ goto err;
+ }
+
+ if (ctrl_cmd_send(&bsc->write_queue, bsc_cmd)) {
+ cmd->reply = "Sending failed";
+ goto err;
+ }
+ pending->ccon = cmd->ccon;
+ pending->ccon->closed_cb = ctrl_conn_closed_cb;
+ pending->cmd = cmd;
+
+ /* Setup the timeout */
+ osmo_timer_setup(&pending->timeout, pending_timeout_cb,
+ pending);
+ /* TODO: Make timeout configurable */
+ osmo_timer_schedule(&pending->timeout, 10, 0);
+ llist_add_tail(&pending->list_entry, &bsc->cmd_pending);
+
+ goto done;
+ }
+ }
+ /* We end up here if there's no bsc to handle our LAC */
+ cmd->reply = "no BSC with this nr";
+err:
+ ret = CTRL_CMD_ERROR;
+done:
+ talloc_free(bsc_cmd);
+ return ret;
+
+}
+
+
+CTRL_CMD_DEFINE(fwd_cmd, "net 0 bsc *");
+static int get_fwd_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ return forward_to_bsc(cmd);
+}
+
+static int set_fwd_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ return forward_to_bsc(cmd);
+}
+
+static int verify_fwd_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+static int extract_bsc_cfg_variable(struct ctrl_cmd *cmd, struct bsc_config **cfg,
+ char **bsc_variable)
+{
+ unsigned int nr;
+
+ if (!extract_bsc_nr_variable(cmd->variable, &nr, bsc_variable)) {
+ cmd->reply = "command incomplete";
+ return 0;
+ }
+
+ *cfg = bsc_config_num(g_nat, nr);
+ if (!*cfg) {
+ cmd->reply = "Unknown BSC";
+ return 0;
+ }
+
+ return 1;
+}
+
+CTRL_CMD_DEFINE(net_cfg_cmd, "net 0 bsc_cfg *");
+static int get_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ char *bsc_variable;
+ struct bsc_config *bsc_cfg;
+
+ if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+ return CTRL_CMD_ERROR;
+
+ if (strcmp(bsc_variable, "access-list-name") == 0) {
+ cmd->reply = talloc_asprintf(cmd, "%s",
+ bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+ return CTRL_CMD_REPLY;
+ }
+
+ cmd->reply = "unknown command";
+ return CTRL_CMD_ERROR;
+}
+
+static int set_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ char *bsc_variable;
+ struct bsc_config *bsc_cfg;
+
+ if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+ return CTRL_CMD_ERROR;
+
+ if (strcmp(bsc_variable, "access-list-name") == 0) {
+ osmo_talloc_replace_string(bsc_cfg, &bsc_cfg->acc_lst_name, cmd->value);
+ cmd->reply = talloc_asprintf(cmd, "%s",
+ bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+ return CTRL_CMD_REPLY;
+ } else if (strcmp(bsc_variable, "no-access-list-name") == 0) {
+ talloc_free(bsc_cfg->acc_lst_name);
+ bsc_cfg->acc_lst_name = NULL;
+ cmd->reply = "";
+ return CTRL_CMD_REPLY;
+ }
+
+ cmd->reply = "unknown command";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_net_cfg_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+CTRL_CMD_DEFINE(net_cfg_acc_cmd, "net 0 add allow access-list *");
+static const char *extract_acc_name(const char *var)
+{
+ char *str;
+
+ str = strstr(var, "net.0.add.allow.access-list.");
+ if (!str)
+ return NULL;
+ str += strlen("net.0.add.allow.access-list.");
+ if (strlen(str) == 0)
+ return NULL;
+ return str;
+}
+
+static int get_net_cfg_acc_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = "Append only";
+ return CTRL_CMD_ERROR;
+}
+
+static int set_net_cfg_acc_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ const char *access_name = extract_acc_name(cmd->variable);
+ struct bsc_msg_acc_lst *acc;
+ struct bsc_msg_acc_lst_entry *entry;
+ const char *value = cmd->value;
+ int rc;
+
+ /* Should have been caught by verify_net_cfg_acc_cmd */
+ acc = bsc_msg_acc_lst_find(&g_nat->access_lists, access_name);
+ if (!acc) {
+ cmd->reply = "Access list not found";
+ return CTRL_CMD_ERROR;
+ }
+
+ entry = bsc_msg_acc_lst_entry_create(acc);
+ if (!entry) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ rc = gsm_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, 1, &value);
+ if (rc != 0) {
+ cmd->reply = "Failed to compile expression";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "IMSI allow added to access list";
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_net_cfg_acc_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ const char *access_name = extract_acc_name(cmd->variable);
+ struct bsc_msg_acc_lst *acc = bsc_msg_acc_lst_find(&g_nat->access_lists, access_name);
+
+ if (!acc) {
+ cmd->reply = "Access list not known";
+ return -1;
+ }
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_save_cmd, "net 0 save-configuration");
+
+static int set_net_save_cmd(struct ctrl_cmd *cmd, void *data)
+{
+ int rc = osmo_vty_save_config_file();
+ cmd->reply = talloc_asprintf(cmd, "%d", rc);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+struct ctrl_handle *bsc_nat_controlif_setup(struct bsc_nat *nat,
+ const char *bind_addr, int port)
+{
+ struct ctrl_handle *ctrl;
+ int rc;
+
+
+ ctrl = bsc_controlif_setup(NULL, bind_addr, OSMO_CTRL_PORT_BSC_NAT);
+ if (!ctrl) {
+ fprintf(stderr, "Failed to initialize the control interface. Exiting.\n");
+ return NULL;
+ }
+
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_fwd_cmd);
+ if (rc) {
+ fprintf(stderr, "Failed to install the control command. Exiting.\n");
+ goto error;
+ }
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_cfg_cmd);
+ if (rc) {
+ fprintf(stderr, "Failed to install the net cfg command. Exiting.\n");
+ goto error;
+ }
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_cfg_acc_cmd);
+ if (rc) {
+ fprintf(stderr, "Failed to install the net acc command. Exiting.\n");
+ goto error;
+ }
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_save_cmd);
+ if (rc) {
+ fprintf(stderr, "Failed to install the net save command. Exiting.\n");
+ goto error;
+ }
+
+ g_nat = nat;
+ return ctrl;
+
+error:
+ osmo_fd_unregister(&ctrl->listen_fd);
+ close(ctrl->listen_fd.fd);
+ talloc_free(ctrl);
+ return NULL;
+}
+
+void bsc_nat_inform_reject(struct bsc_connection *conn, const char *imsi)
+{
+ struct ctrl_cmd *cmd;
+
+ cmd = ctrl_cmd_create(conn, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+ return;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "net.0.bsc.%d.notification-rejection-v1",
+ conn->cfg->nr);
+ cmd->reply = talloc_asprintf(cmd, "imsi=%s", imsi);
+
+ ctrl_cmd_send_to_all(conn->cfg->nat->ctrl, cmd);
+ talloc_free(cmd);
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_filter.c b/src/osmo-bsc_nat/bsc_nat_filter.c
new file mode 100644
index 000000000..e73529097
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_filter.c
@@ -0,0 +1,119 @@
+/*
+ * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/debug.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* Filter out CR data... */
+int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
+ struct bsc_nat_parsed *parsed, int *con_type,
+ char **imsi, struct bsc_filter_reject_cause *cause)
+{
+ struct bsc_filter_request req;
+ struct tlv_parsed tp;
+ struct gsm48_hdr *hdr48;
+ int hdr48_len;
+ int len;
+
+ *con_type = FLT_CON_TYPE_NONE;
+ cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ *imsi = NULL;
+
+ if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Rejecting CR message due wrong GSM Type %d\n", parsed->gsm_type);
+ return -1;
+ }
+
+ /* the parsed has had some basic l3 length check */
+ len = msg->l3h[1];
+ if (msgb_l3len(msg) - 3 < len) {
+ LOGP(DNAT, LOGL_ERROR,
+ "The CR Data has not enough space...\n");
+ return -1;
+ }
+
+ msg->l4h = &msg->l3h[3];
+ len -= 1;
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h, len, 0, 0);
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
+ LOGP(DNAT, LOGL_ERROR, "CR Data does not contain layer3 information.\n");
+ return -1;
+ }
+
+ hdr48_len = TLVP_LEN(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+
+ if (hdr48_len < sizeof(*hdr48)) {
+ LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n");
+ return -1;
+ }
+
+ hdr48 = (struct gsm48_hdr *) TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION);
+ req.ctx = bsc;
+ req.black_list = &bsc->nat->imsi_black_list;
+ req.access_lists = &bsc->nat->access_lists;
+ req.local_lst_name = bsc->cfg->acc_lst_name;
+ req.global_lst_name = bsc->nat->acc_lst_name;
+ req.bsc_nr = bsc->cfg->nr;
+ return bsc_msg_filter_initial(hdr48, hdr48_len, &req, con_type, imsi, cause);
+}
+
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+ struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed,
+ struct bsc_filter_reject_cause *cause)
+{
+ uint32_t len;
+ struct gsm48_hdr *hdr48;
+ struct bsc_filter_request req;
+
+ cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+ cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
+
+ if (con->filter_state.imsi_checked)
+ return 0;
+
+ /* only care about DTAP messages */
+ if (parsed->bssap != BSSAP_MSG_DTAP)
+ return 0;
+
+ hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+ if (!hdr48)
+ return -1;
+
+ req.ctx = con;
+ req.black_list = &bsc->nat->imsi_black_list;
+ req.access_lists = &bsc->nat->access_lists;
+ req.local_lst_name = bsc->cfg->acc_lst_name;
+ req.global_lst_name = bsc->nat->acc_lst_name;
+ req.bsc_nr = bsc->cfg->nr;
+ return bsc_msg_filter_data(hdr48, len, &req, &con->filter_state, cause);
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_rewrite.c b/src/osmo-bsc_nat/bsc_nat_rewrite.c
new file mode 100644
index 000000000..e7c387c22
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_rewrite.c
@@ -0,0 +1,714 @@
+/*
+ * Message rewriting functionality
+ */
+/*
+ * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2013 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/nat_rewrite_trie.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+static char *trie_lookup(struct nat_rewrite *trie, const char *number,
+ regoff_t off, void *ctx)
+{
+ struct nat_rewrite_rule *rule;
+
+ if (!trie) {
+ LOGP(DCC, LOGL_ERROR,
+ "Asked to do a table lookup but no table.\n");
+ return NULL;
+ }
+
+ rule = nat_rewrite_lookup(trie, number);
+ if (!rule) {
+ LOGP(DCC, LOGL_DEBUG,
+ "Couldn't find a prefix rule for %s\n", number);
+ return NULL;
+ }
+
+ return talloc_asprintf(ctx, "%s%s", rule->rewrite, &number[off]);
+}
+
+static char *match_and_rewrite_number(void *ctx, const char *number,
+ const char *imsi, struct llist_head *list,
+ struct nat_rewrite *trie)
+{
+ struct bsc_nat_num_rewr_entry *entry;
+ char *new_number = NULL;
+
+ /* need to find a replacement and then fix it */
+ llist_for_each_entry(entry, list, list) {
+ regmatch_t matches[2];
+
+ /* check the IMSI match */
+ if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+ continue;
+
+ /* this regexp matches... */
+ if (regexec(&entry->num_reg, number, 2, matches, 0) == 0
+ && matches[1].rm_eo != -1) {
+ if (entry->is_prefix_lookup)
+ new_number = trie_lookup(trie, number,
+ matches[1].rm_so, ctx);
+ else
+ new_number = talloc_asprintf(ctx, "%s%s",
+ entry->replace,
+ &number[matches[1].rm_so]);
+ }
+
+ if (new_number)
+ break;
+ }
+
+ return new_number;
+}
+
+static char *rewrite_isdn_number(struct bsc_nat *nat, struct llist_head *rewr_list,
+ void *ctx, const char *imsi,
+ struct gsm_mncc_number *called)
+{
+ char int_number[sizeof(called->number) + 2];
+ char *number = called->number;
+
+ if (llist_empty(&nat->num_rewr)) {
+ LOGP(DCC, LOGL_DEBUG, "Rewrite rules empty.\n");
+ return NULL;
+ }
+
+ /* only ISDN plan */
+ if (called->plan != 1) {
+ LOGP(DCC, LOGL_DEBUG, "Called plan is not 1 it was %d\n",
+ called->plan);
+ return NULL;
+ }
+
+ /* international, prepend */
+ if (called->type == 1) {
+ int_number[0] = '+';
+ memcpy(&int_number[1], number, strlen(number) + 1);
+ number = int_number;
+ }
+
+ return match_and_rewrite_number(ctx, number,
+ imsi, rewr_list, nat->num_rewr_trie);
+}
+
+static void update_called_number(struct gsm_mncc_number *called,
+ const char *chosen_number)
+{
+ if (strncmp(chosen_number, "00", 2) == 0) {
+ called->type = 1;
+ osmo_strlcpy(called->number, chosen_number + 2,
+ sizeof(called->number));
+ } else {
+ /* rewrite international to unknown */
+ if (called->type == 1)
+ called->type = 0;
+ osmo_strlcpy(called->number, chosen_number,
+ sizeof(called->number));
+ }
+}
+
+/**
+ * Rewrite non global numbers... according to rules based on the IMSI
+ */
+static struct msgb *rewrite_setup(struct bsc_nat *nat, struct msgb *msg,
+ struct bsc_nat_parsed *parsed, const char *imsi,
+ struct gsm48_hdr *hdr48, const uint32_t len)
+{
+ struct tlv_parsed tp;
+ unsigned int payload_len;
+ struct gsm_mncc_number called;
+ struct msgb *out;
+ char *new_number_pre = NULL, *new_number_post = NULL, *chosen_number;
+ uint8_t *outptr;
+ const uint8_t *msgptr;
+ int sec_len;
+
+ /* decode and rewrite the message */
+ payload_len = len - sizeof(*hdr48);
+ tlv_parse(&tp, &gsm48_att_tlvdef, hdr48->data, payload_len, 0, 0);
+
+ /* no number, well let us ignore it */
+ if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD))
+ return NULL;
+
+ memset(&called, 0, sizeof(called));
+ gsm48_decode_called(&called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+ /* check if it looks international and stop */
+ LOGP(DCC, LOGL_DEBUG,
+ "Pre-Rewrite for IMSI(%s) Plan(%d) Type(%d) Number(%s)\n",
+ imsi, called.plan, called.type, called.number);
+ new_number_pre = rewrite_isdn_number(nat, &nat->num_rewr, msg, imsi, &called);
+
+ if (!new_number_pre) {
+ LOGP(DCC, LOGL_DEBUG, "No IMSI(%s) match found, returning message.\n",
+ imsi);
+ return NULL;
+ }
+
+ if (strlen(new_number_pre) > sizeof(called.number)) {
+ LOGP(DCC, LOGL_ERROR, "Number %s is too long for structure.\n",
+ new_number_pre);
+ talloc_free(new_number_pre);
+ return NULL;
+ }
+ update_called_number(&called, new_number_pre);
+
+ /* another run through the re-write engine with other rules */
+ LOGP(DCC, LOGL_DEBUG,
+ "Post-Rewrite for IMSI(%s) Plan(%d) Type(%d) Number(%s)\n",
+ imsi, called.plan, called.type, called.number);
+ new_number_post = rewrite_isdn_number(nat, &nat->num_rewr_post, msg,
+ imsi, &called);
+ chosen_number = new_number_post ? new_number_post : new_number_pre;
+
+
+ if (strlen(chosen_number) > sizeof(called.number)) {
+ LOGP(DCC, LOGL_ERROR, "Number %s is too long for structure.\n",
+ chosen_number);
+ talloc_free(new_number_pre);
+ talloc_free(new_number_post);
+ return NULL;
+ }
+
+ /*
+ * Need to create a new message now based on the old onew
+ * with a new number. We can sadly not patch this in place
+ * so we will need to regenerate it.
+ */
+
+ out = msgb_alloc_headroom(4096, 128, "changed-setup");
+ if (!out) {
+ LOGP(DCC, LOGL_ERROR, "Failed to allocate.\n");
+ talloc_free(new_number_pre);
+ talloc_free(new_number_post);
+ return NULL;
+ }
+
+ /* copy the header */
+ outptr = msgb_put(out, sizeof(*hdr48));
+ memcpy(outptr, hdr48, sizeof(*hdr48));
+
+ /* copy everything up to the number */
+ sec_len = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 2 - &hdr48->data[0];
+ outptr = msgb_put(out, sec_len);
+ memcpy(outptr, &hdr48->data[0], sec_len);
+
+ /* create the new number */
+ update_called_number(&called, chosen_number);
+ LOGP(DCC, LOGL_DEBUG,
+ "Chosen number for IMSI(%s) is Plan(%d) Type(%d) Number(%s)\n",
+ imsi, called.plan, called.type, called.number);
+ gsm48_encode_called(out, &called);
+
+ /* copy thre rest */
+ msgptr = TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) +
+ TLVP_LEN(&tp, GSM48_IE_CALLED_BCD);
+ sec_len = payload_len - (msgptr - &hdr48->data[0]);
+ outptr = msgb_put(out, sec_len);
+ memcpy(outptr, msgptr, sec_len);
+
+ talloc_free(new_number_pre);
+ talloc_free(new_number_post);
+ return out;
+}
+
+/**
+ * Find a new SMSC address, returns an allocated string that needs to be
+ * freed or is NULL.
+ */
+static char *find_new_smsc(struct bsc_nat *nat, void *ctx, const char *imsi,
+ const char *smsc_addr, const char *dest_nr)
+{
+ struct bsc_nat_num_rewr_entry *entry;
+ char *new_number = NULL;
+ uint8_t dest_match = llist_empty(&nat->tpdest_match);
+
+ /* We will find a new number now */
+ llist_for_each_entry(entry, &nat->smsc_rewr, list) {
+ regmatch_t matches[2];
+
+ /* check the IMSI match */
+ if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+ continue;
+
+ /* this regexp matches... */
+ if (regexec(&entry->num_reg, smsc_addr, 2, matches, 0) == 0 &&
+ matches[1].rm_eo != -1)
+ new_number = talloc_asprintf(ctx, "%s%s",
+ entry->replace,
+ &smsc_addr[matches[1].rm_so]);
+ if (new_number)
+ break;
+ }
+
+ if (!new_number)
+ return NULL;
+
+ /*
+ * now match the number against another list
+ */
+ llist_for_each_entry(entry, &nat->tpdest_match, list) {
+ /* check the IMSI match */
+ if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+ continue;
+
+ if (regexec(&entry->num_reg, dest_nr, 0, NULL, 0) == 0) {
+ dest_match = 1;
+ break;
+ }
+ }
+
+ if (!dest_match) {
+ talloc_free(new_number);
+ return NULL;
+ }
+
+ return new_number;
+}
+
+/**
+ * Clear the TP-SRR from the TPDU header
+ */
+static uint8_t sms_new_tpdu_hdr(struct bsc_nat *nat, const char *imsi,
+ const char *dest_nr, uint8_t hdr)
+{
+ struct bsc_nat_num_rewr_entry *entry;
+
+ /* We will find a new number now */
+ llist_for_each_entry(entry, &nat->sms_clear_tp_srr, list) {
+ /* check the IMSI match */
+ if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
+ continue;
+ if (regexec(&entry->num_reg, dest_nr, 0, NULL, 0) != 0)
+ continue;
+
+ /* matched phone number and imsi */
+ return hdr & ~0x20;
+ }
+
+ return hdr;
+}
+
+/**
+ * Check if we need to rewrite the number. For this SMS.
+ */
+static char *sms_new_dest_nr(struct bsc_nat *nat, void *ctx,
+ const char *imsi, const char *dest_nr)
+{
+ return match_and_rewrite_number(ctx, dest_nr, imsi,
+ &nat->sms_num_rewr, NULL);
+}
+
+/**
+ * This is a helper for GSM 04.11 8.2.5.2 Destination address element
+ */
+void sms_encode_addr_element(struct msgb *out, const char *new_number,
+ int format, int tp_data)
+{
+ uint8_t new_addr_len;
+ uint8_t new_addr[26];
+
+ /*
+ * Copy the new number. We let libosmocore encode it, then set
+ * the extension followed after the length. Depending on if
+ * we want to write RP we will let the TLV code add the
+ * length for us or we need to use strlen... This is not very clear
+ * as of 03.40 and 04.11.
+ */
+ new_addr_len = gsm48_encode_bcd_number(new_addr, ARRAY_SIZE(new_addr),
+ 1, new_number);
+ new_addr[1] = format;
+ if (tp_data) {
+ uint8_t *data = msgb_put(out, new_addr_len);
+ memcpy(data, new_addr, new_addr_len);
+ data[0] = strlen(new_number);
+ } else {
+ msgb_lv_put(out, new_addr_len - 1, new_addr + 1);
+ }
+}
+
+static struct msgb *sms_create_new(uint8_t type, uint8_t ref,
+ struct gsm48_hdr *old_hdr48,
+ const uint8_t *orig_addr_ptr,
+ int orig_addr_len, const char *new_number,
+ const uint8_t *data_ptr, int data_len,
+ uint8_t tpdu_first_byte,
+ const int old_dest_len, const char *new_dest_nr)
+{
+ struct gsm48_hdr *new_hdr48;
+ struct msgb *out;
+
+ /*
+ * We need to re-create the patched structure. This is why we have
+ * saved the above pointers.
+ */
+ out = msgb_alloc_headroom(4096, 128, "changed-smsc");
+ if (!out) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+ return NULL;
+ }
+
+ out->l2h = out->data;
+ msgb_v_put(out, GSM411_MT_RP_DATA_MO);
+ msgb_v_put(out, ref);
+ msgb_lv_put(out, orig_addr_len, orig_addr_ptr);
+
+ sms_encode_addr_element(out, new_number, 0x91, 0);
+
+
+ /* Patch the TPDU from here on */
+
+ /**
+ * Do we need to put a new TP-Destination-Address (TP-DA) here or
+ * can we copy the old thing? For the TP-DA we need to find out the
+ * new size.
+ */
+ if (new_dest_nr) {
+ uint8_t *data, *new_size;
+
+ /* reserve the size and write the header */
+ new_size = msgb_put(out, 1);
+ out->l3h = new_size + 1;
+ msgb_v_put(out, tpdu_first_byte);
+ msgb_v_put(out, data_ptr[1]);
+
+ /* encode the new number and put it */
+ if (strncmp(new_dest_nr, "00", 2) == 0)
+ sms_encode_addr_element(out, new_dest_nr + 2, 0x91, 1);
+ else
+ sms_encode_addr_element(out, new_dest_nr, 0x81, 1);
+
+ /* Copy the rest after the TP-DS */
+ data = msgb_put(out, data_len - 2 - 1 - old_dest_len);
+ memcpy(data, &data_ptr[2 + 1 + old_dest_len], data_len - 2 - 1 - old_dest_len);
+
+ /* fill in the new size */
+ new_size[0] = msgb_l3len(out);
+ } else {
+ msgb_v_put(out, data_len);
+ msgb_tv_fixed_put(out, tpdu_first_byte, data_len - 1, &data_ptr[1]);
+ }
+
+ /* prepend GSM 04.08 header */
+ new_hdr48 = (struct gsm48_hdr *) msgb_push(out, sizeof(*new_hdr48) + 1);
+ memcpy(new_hdr48, old_hdr48, sizeof(*old_hdr48));
+ new_hdr48->data[0] = msgb_l2len(out);
+
+ return out;
+}
+
+/**
+ * Parse the SMS and check if it needs to be rewritten
+ */
+static struct msgb *rewrite_sms(struct bsc_nat *nat, struct msgb *msg,
+ struct bsc_nat_parsed *parsed, const char *imsi,
+ struct gsm48_hdr *hdr48, const uint32_t len)
+{
+ unsigned int payload_len;
+ unsigned int cp_len;
+
+ uint8_t ref;
+ uint8_t orig_addr_len, *orig_addr_ptr;
+ uint8_t dest_addr_len, *dest_addr_ptr;
+ uint8_t data_len, *data_ptr;
+ char smsc_addr[30];
+
+
+ uint8_t dest_len, orig_dest_len;
+ char _dest_nr[30];
+ char *dest_nr;
+ char *new_dest_nr;
+
+ char *new_number = NULL;
+ uint8_t tpdu_hdr;
+ struct msgb *out;
+
+ payload_len = len - sizeof(*hdr48);
+ if (payload_len < 1) {
+ LOGP(DNAT, LOGL_ERROR, "SMS too short for things. %d\n", payload_len);
+ return NULL;
+ }
+
+ cp_len = hdr48->data[0];
+ if (payload_len + 1 < cp_len) {
+ LOGP(DNAT, LOGL_ERROR, "SMS RPDU can not fit in: %d %d\n", cp_len, payload_len);
+ return NULL;
+ }
+
+ if (hdr48->data[1] != GSM411_MT_RP_DATA_MO)
+ return NULL;
+
+ if (cp_len < 5) {
+ LOGP(DNAT, LOGL_ERROR, "RD-DATA can not fit in the CP len: %d\n", cp_len);
+ return NULL;
+ }
+
+ /* RP */
+ ref = hdr48->data[2];
+ orig_addr_len = hdr48->data[3];
+ orig_addr_ptr = &hdr48->data[4];
+
+ /* the +1 is for checking if the following element has some space */
+ if (cp_len < 3 + orig_addr_len + 1) {
+ LOGP(DNAT, LOGL_ERROR, "RP-Originator addr does not fit: %d\n", orig_addr_len);
+ return NULL;
+ }
+
+ dest_addr_len = hdr48->data[3 + orig_addr_len + 1];
+ dest_addr_ptr = &hdr48->data[3 + orig_addr_len + 2];
+
+ if (cp_len < 3 + orig_addr_len + 1 + dest_addr_len + 1) {
+ LOGP(DNAT, LOGL_ERROR, "RP-Destination addr does not fit: %d\n", dest_addr_len);
+ return NULL;
+ }
+ gsm48_decode_bcd_number(smsc_addr, ARRAY_SIZE(smsc_addr), dest_addr_ptr - 1, 1);
+
+ data_len = hdr48->data[3 + orig_addr_len + 1 + dest_addr_len + 1];
+ data_ptr = &hdr48->data[3 + orig_addr_len + 1 + dest_addr_len + 2];
+
+ if (cp_len < 3 + orig_addr_len + 1 + dest_addr_len + 1 + data_len) {
+ LOGP(DNAT, LOGL_ERROR, "RP-Data does not fit: %d\n", data_len);
+ return NULL;
+ }
+
+ if (data_len < 3) {
+ LOGP(DNAT, LOGL_ERROR, "SMS-SUBMIT is too short.\n");
+ return NULL;
+ }
+
+ /* TP-PDU starts here */
+ if ((data_ptr[0] & 0x03) != GSM340_SMS_SUBMIT_MS2SC)
+ return NULL;
+
+ /*
+ * look into the phone number. The length is in semi-octets, we will
+ * need to add the byte for the number type as well.
+ */
+ orig_dest_len = data_ptr[2];
+ dest_len = ((orig_dest_len + 1) / 2) + 1;
+ if (data_len < dest_len + 3 || dest_len < 2) {
+ LOGP(DNAT, LOGL_ERROR, "SMS-SUBMIT can not have TP-DestAddr.\n");
+ return NULL;
+ }
+
+ if ((data_ptr[3] & 0x80) == 0) {
+ LOGP(DNAT, LOGL_ERROR, "TP-DestAddr has extension. Not handled.\n");
+ return NULL;
+ }
+
+ if ((data_ptr[3] & 0x0F) == 0) {
+ LOGP(DNAT, LOGL_ERROR, "TP-DestAddr is of unknown type.\n");
+ return NULL;
+ }
+
+ /**
+ * Besides of what I think I read in GSM 03.40 and 04.11 the TP-DA
+ * contains the semi-octets as length (strlen), change it to the
+ * the number of bytes, but then change it back.
+ */
+ data_ptr[2] = dest_len;
+ gsm48_decode_bcd_number(_dest_nr + 2, ARRAY_SIZE(_dest_nr) - 2,
+ &data_ptr[2], 1);
+ data_ptr[2] = orig_dest_len;
+ if ((data_ptr[3] & 0x70) == 0x10) {
+ _dest_nr[0] = _dest_nr[1] = '0';
+ dest_nr = &_dest_nr[0];
+ } else {
+ dest_nr = &_dest_nr[2];
+ }
+
+ /**
+ * Call functions to rewrite the data
+ */
+ tpdu_hdr = sms_new_tpdu_hdr(nat, imsi, dest_nr, data_ptr[0]);
+ new_number = find_new_smsc(nat, msg, imsi, smsc_addr, dest_nr);
+ new_dest_nr = sms_new_dest_nr(nat, msg, imsi, dest_nr);
+
+ if (tpdu_hdr == data_ptr[0] && !new_number && !new_dest_nr)
+ return NULL;
+
+ out = sms_create_new(GSM411_MT_RP_DATA_MO, ref, hdr48,
+ orig_addr_ptr, orig_addr_len,
+ new_number ? new_number : smsc_addr,
+ data_ptr, data_len, tpdu_hdr,
+ dest_len, new_dest_nr);
+ talloc_free(new_number);
+ talloc_free(new_dest_nr);
+ return out;
+}
+
+struct msgb *bsc_nat_rewrite_msg(struct bsc_nat *nat, struct msgb *msg, struct bsc_nat_parsed *parsed, const char *imsi)
+{
+ struct gsm48_hdr *hdr48;
+ uint32_t len;
+ uint8_t msg_type, proto;
+ struct msgb *new_msg = NULL, *sccp;
+ uint8_t link_id;
+
+ if (!imsi || strlen(imsi) < 5)
+ return msg;
+
+ /* only care about DTAP messages */
+ if (parsed->bssap != BSSAP_MSG_DTAP)
+ return msg;
+ if (!parsed->dest_local_ref)
+ return msg;
+
+ hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+ if (!hdr48)
+ return msg;
+
+ link_id = msg->l3h[1];
+ proto = gsm48_hdr_pdisc(hdr48);
+ msg_type = gsm48_hdr_msg_type(hdr48);
+
+ if (proto == GSM48_PDISC_CC && msg_type == GSM48_MT_CC_SETUP)
+ new_msg = rewrite_setup(nat, msg, parsed, imsi, hdr48, len);
+ else if (proto == GSM48_PDISC_SMS && msg_type == GSM411_MT_CP_DATA)
+ new_msg = rewrite_sms(nat, msg, parsed, imsi, hdr48, len);
+
+ if (!new_msg)
+ return msg;
+
+ /* wrap with DTAP, SCCP, then IPA. TODO: Stop copying */
+ gsm0808_prepend_dtap_header(new_msg, link_id);
+ sccp = sccp_create_dt1(parsed->dest_local_ref, new_msg->data, new_msg->len);
+ talloc_free(new_msg);
+
+ if (!sccp) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+ return msg;
+ }
+
+ ipa_prepend_header(sccp, IPAC_PROTO_SCCP);
+
+ /* the parsed hangs off from msg but it needs to survive */
+ talloc_steal(sccp, parsed);
+ msgb_free(msg);
+ return sccp;
+}
+
+static void num_rewr_free_data(struct bsc_nat_num_rewr_entry *entry)
+{
+ regfree(&entry->msisdn_reg);
+ regfree(&entry->num_reg);
+ talloc_free(entry->replace);
+}
+
+void bsc_nat_num_rewr_entry_adapt(void *ctx, struct llist_head *head,
+ const struct osmo_config_list *list)
+{
+ struct bsc_nat_num_rewr_entry *entry, *tmp;
+ struct osmo_config_entry *cfg_entry;
+
+ /* free the old data */
+ llist_for_each_entry_safe(entry, tmp, head, list) {
+ num_rewr_free_data(entry);
+ llist_del(&entry->list);
+ talloc_free(entry);
+ }
+
+
+ if (!list)
+ return;
+
+ llist_for_each_entry(cfg_entry, &list->entry, list) {
+ char *regexp;
+ if (cfg_entry->text[0] == '+') {
+ LOGP(DNAT, LOGL_ERROR,
+ "Plus is not allowed in the number\n");
+ continue;
+ }
+
+ entry = talloc_zero(ctx, struct bsc_nat_num_rewr_entry);
+ if (!entry) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Allocation of the num_rewr entry failed.\n");
+ continue;
+ }
+
+ entry->replace = talloc_strdup(entry, cfg_entry->text);
+ if (!entry->replace) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Failed to copy the replacement text.\n");
+ talloc_free(entry);
+ continue;
+ }
+
+ if (strcmp("prefix_lookup", entry->replace) == 0)
+ entry->is_prefix_lookup = 1;
+
+ /* we will now build a regexp string */
+ if (cfg_entry->mcc[0] == '^') {
+ regexp = talloc_strdup(entry, cfg_entry->mcc);
+ } else {
+ regexp = talloc_asprintf(entry, "^%s%s",
+ cfg_entry->mcc[0] == '*' ?
+ "[0-9][0-9][0-9]" : cfg_entry->mcc,
+ cfg_entry->mnc[0] == '*' ?
+ "[0-9][0-9]" : cfg_entry->mnc);
+ }
+
+ if (!regexp) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to create a regexp string.\n");
+ talloc_free(entry);
+ continue;
+ }
+
+ if (regcomp(&entry->msisdn_reg, regexp, 0) != 0) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Failed to compile regexp '%s'\n", regexp);
+ talloc_free(regexp);
+ talloc_free(entry);
+ continue;
+ }
+
+ talloc_free(regexp);
+ if (regcomp(&entry->num_reg, cfg_entry->option, REG_EXTENDED) != 0) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Failed to compile regexp '%s'\n", cfg_entry->option);
+ regfree(&entry->msisdn_reg);
+ talloc_free(entry);
+ continue;
+ }
+
+ /* we have copied the number */
+ llist_add_tail(&entry->list, head);
+ }
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c b/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c
new file mode 100644
index 000000000..633fa87d0
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_rewrite_trie.c
@@ -0,0 +1,259 @@
+/* Handling for loading a re-write file/database */
+/*
+ * (C) 2013 by On-Waves
+ * (C) 2013 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/nat_rewrite_trie.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define CHECK_IS_DIGIT_OR_FAIL(prefix, pos) \
+ if (!isdigit(prefix[pos]) && prefix[pos] != '+') { \
+ LOGP(DNAT, LOGL_ERROR, \
+ "Prefix(%s) contains non ascii text at(%d=%c)\n", \
+ prefix, pos, prefix[pos]); \
+ goto fail; \
+ }
+#define TO_INT(c) \
+ ((c) == '+' ? 10 : ((c - '0') % 10))
+
+static void insert_rewrite_node(struct nat_rewrite_rule *rule, struct nat_rewrite *root)
+{
+ struct nat_rewrite_rule *new = &root->rule;
+
+ const int len = strlen(rule->prefix);
+ int i;
+
+ if (len <= 0) {
+ LOGP(DNAT, LOGL_ERROR, "An empty prefix does not make sense.\n");
+ goto fail;
+ }
+
+ for (i = 0; i < len - 1; ++i) {
+ int pos;
+
+ /* check if the input is valid */
+ CHECK_IS_DIGIT_OR_FAIL(rule->prefix, i);
+
+ /* check if the next node is already valid */
+ pos = TO_INT(rule->prefix[i]);
+ if (!new->rules[pos]) {
+ new->rules[pos] = talloc_zero(root, struct nat_rewrite_rule);
+ if (!new->rules[pos]) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Failed to allocate memory.\n");
+ goto fail;
+ }
+
+ new->rules[pos]->empty = 1;
+ }
+
+ /* we continue here */
+ new = new->rules[pos];
+ }
+
+ /* new now points to the place where we want to add it */
+ int pos;
+
+ /* check if the input is valid */
+ CHECK_IS_DIGIT_OR_FAIL(rule->prefix, (len - 1));
+
+ /* check if the next node is already valid */
+ pos = TO_INT(rule->prefix[len - 1]);
+ if (!new->rules[pos])
+ new->rules[pos] = rule;
+ else if (new->rules[pos]->empty) {
+ /* copy over entries */
+ new->rules[pos]->empty = 0;
+ memcpy(new->rules[pos]->prefix, rule->prefix, sizeof(rule->prefix));
+ memcpy(new->rules[pos]->rewrite, rule->rewrite, sizeof(rule->rewrite));
+ talloc_free(rule);
+ } else {
+ LOGP(DNAT, LOGL_ERROR,
+ "Prefix(%s) is already installed\n", rule->prefix);
+ goto fail;
+ }
+
+ root->prefixes += 1;
+ return;
+
+fail:
+ talloc_free(rule);
+ return;
+}
+
+static void handle_line(struct nat_rewrite *rewrite, char *line)
+{
+ char *split;
+ struct nat_rewrite_rule *rule;
+ size_t size_prefix, size_end, len;
+
+
+ /* Find the ',' in the line */
+ len = strlen(line);
+ split = strstr(line, ",");
+ if (!split) {
+ LOGP(DNAT, LOGL_ERROR, "Line doesn't contain ','\n");
+ return;
+ }
+
+ /* Check if there is space for the rewrite rule */
+ size_prefix = split - line;
+ if (len - size_prefix <= 2) {
+ LOGP(DNAT, LOGL_ERROR, "No rewrite available.\n");
+ return;
+ }
+
+ /* Continue after the ',' to the end */
+ split = &line[size_prefix + 1];
+ size_end = strlen(split) - 1;
+
+ /* Check if both strings can fit into the static array */
+ if (size_prefix > sizeof(rule->prefix) - 1) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Prefix is too long with %zu\n", size_prefix);
+ return;
+ }
+
+ if (size_end > sizeof(rule->rewrite) - 1) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Rewrite is too long with %zu on %s\n",
+ size_end, &line[size_prefix + 1]);
+ return;
+ }
+
+ /* Now create the entry and insert it into the trie */
+ rule = talloc_zero(rewrite, struct nat_rewrite_rule);
+ if (!rule) {
+ LOGP(DNAT, LOGL_ERROR, "Can not allocate memory\n");
+ return;
+ }
+
+ memcpy(rule->prefix, line, size_prefix);
+ assert(size_prefix < sizeof(rule->prefix));
+ rule->prefix[size_prefix] = '\0';
+
+ memcpy(rule->rewrite, split, size_end);
+ assert(size_end < sizeof(rule->rewrite));
+ rule->rewrite[size_end] = '\0';
+
+ /* now insert and balance the tree */
+ insert_rewrite_node(rule, rewrite);
+}
+
+struct nat_rewrite *nat_rewrite_parse(void *ctx, const char *filename)
+{
+ FILE *file;
+ char *line = NULL;
+ size_t n = 0;
+ struct nat_rewrite *res;
+
+ file = fopen(filename, "r");
+ if (!file)
+ return NULL;
+
+ res = talloc_zero(ctx, struct nat_rewrite);
+ if (!res) {
+ fclose(file);
+ return NULL;
+ }
+
+ /* mark the root as empty */
+ res->rule.empty = 1;
+
+ while (getline(&line, &n, file) != -1) {
+ handle_line(res, line);
+ }
+
+ free(line);
+ fclose(file);
+ return res;
+}
+
+/**
+ * Simple find that tries to do a longest match...
+ */
+struct nat_rewrite_rule *nat_rewrite_lookup(struct nat_rewrite *rewrite,
+ const char *prefix)
+{
+ struct nat_rewrite_rule *rule = &rewrite->rule;
+ struct nat_rewrite_rule *last = NULL;
+ const int len = OSMO_MIN(strlen(prefix), (sizeof(rule->prefix) - 1));
+ int i;
+
+ for (i = 0; rule && i < len; ++i) {
+ int pos;
+
+ CHECK_IS_DIGIT_OR_FAIL(prefix, i);
+ pos = TO_INT(prefix[i]);
+
+ rule = rule->rules[pos];
+ if (rule && !rule->empty)
+ last = rule;
+ }
+
+ return last;
+
+fail:
+ return NULL;
+}
+
+static void nat_rewrite_dump_rec(struct nat_rewrite_rule *rule)
+{
+ int i;
+ if (!rule->empty)
+ printf("%s,%s\n", rule->prefix, rule->rewrite);
+
+ for (i = 0; i < ARRAY_SIZE(rule->rules); ++i) {
+ if (!rule->rules[i])
+ continue;
+ nat_rewrite_dump_rec(rule->rules[i]);
+ }
+}
+
+void nat_rewrite_dump(struct nat_rewrite *rewrite)
+{
+ nat_rewrite_dump_rec(&rewrite->rule);
+}
+
+static void nat_rewrite_dump_rec_vty(struct vty *vty, struct nat_rewrite_rule *rule)
+{
+ int i;
+ if (!rule->empty)
+ vty_out(vty, "%s,%s%s", rule->prefix, rule->rewrite, VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(rule->rules); ++i) {
+ if (!rule->rules[i])
+ continue;
+ nat_rewrite_dump_rec_vty(vty, rule->rules[i]);
+ }
+}
+
+void nat_rewrite_dump_vty(struct vty *vty, struct nat_rewrite *rewrite)
+{
+ nat_rewrite_dump_rec_vty(vty, &rewrite->rule);
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_utils.c b/src/osmo-bsc_nat/bsc_nat_utils.c
new file mode 100644
index 000000000..c12b29f1f
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_utils.c
@@ -0,0 +1,535 @@
+
+/* BSC Multiplexer/NAT Utilities */
+
+/*
+ * (C) 2010-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_11.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+static const struct rate_ctr_desc bsc_cfg_ctr_description[] = {
+ [BCFG_CTR_SCCP_CONN] = { "sccp.conn", "SCCP Connections "},
+ [BCFG_CTR_SCCP_CALLS] = { "sccp.calls", "SCCP Assignment Commands "},
+ [BCFG_CTR_NET_RECONN] = { "net.reconnects", "Network reconnects "},
+ [BCFG_CTR_DROPPED_SCCP] = { "dropped.sccp", "Dropped SCCP connections."},
+ [BCFG_CTR_DROPPED_CALLS] = { "dropped.calls", "Dropped active calls. "},
+ [BCFG_CTR_REJECTED_CR] = { "rejected.cr", "Rejected CR due filter "},
+ [BCFG_CTR_REJECTED_MSG] = { "rejected.msg", "Rejected MSG due filter "},
+ [BCFG_CTR_ILL_PACKET] = { "rejected.ill", "Rejected due parse error "},
+ [BCFG_CTR_CON_TYPE_LU] = { "conn.lu", "Conn Location Update "},
+ [BCFG_CTR_CON_CMSERV_RQ] = { "conn.rq", "Conn CM Service Req "},
+ [BCFG_CTR_CON_PAG_RESP] = { "conn.pag", "Conn Paging Response "},
+ [BCFG_CTR_CON_SSA] = { "conn.ssa", "Conn USSD "},
+ [BCFG_CTR_CON_OTHER] = { "conn.other", "Conn Other "},
+};
+
+static const struct rate_ctr_group_desc bsc_cfg_ctrg_desc = {
+ .group_name_prefix = "nat.bsc",
+ .group_description = "NAT BSC Statistics",
+ .num_ctr = ARRAY_SIZE(bsc_cfg_ctr_description),
+ .ctr_desc = bsc_cfg_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+struct bsc_nat *bsc_nat_alloc(void)
+{
+ struct bsc_nat *nat = talloc_zero(tall_bsc_ctx, struct bsc_nat);
+ if (!nat)
+ return NULL;
+
+ nat->main_dest = talloc_zero(nat, struct bsc_msc_dest);
+ if (!nat->main_dest) {
+ talloc_free(nat);
+ return NULL;
+ }
+
+ INIT_LLIST_HEAD(&nat->sccp_connections);
+ INIT_LLIST_HEAD(&nat->bsc_connections);
+ INIT_LLIST_HEAD(&nat->paging_groups);
+ INIT_LLIST_HEAD(&nat->bsc_configs);
+ INIT_LLIST_HEAD(&nat->access_lists);
+ INIT_LLIST_HEAD(&nat->dests);
+ INIT_LLIST_HEAD(&nat->num_rewr);
+ INIT_LLIST_HEAD(&nat->num_rewr_post);
+ INIT_LLIST_HEAD(&nat->smsc_rewr);
+ INIT_LLIST_HEAD(&nat->tpdest_match);
+ INIT_LLIST_HEAD(&nat->sms_clear_tp_srr);
+ INIT_LLIST_HEAD(&nat->sms_num_rewr);
+
+ nat->stats.sccp.conn = osmo_counter_alloc("nat.sccp.conn");
+ nat->stats.sccp.calls = osmo_counter_alloc("nat.sccp.calls");
+ nat->stats.bsc.reconn = osmo_counter_alloc("nat.bsc.conn");
+ nat->stats.bsc.auth_fail = osmo_counter_alloc("nat.bsc.auth_fail");
+ nat->stats.msc.reconn = osmo_counter_alloc("nat.msc.conn");
+ nat->stats.ussd.reconn = osmo_counter_alloc("nat.ussd.conn");
+ nat->auth_timeout = 2;
+ nat->ping_timeout = 20;
+ nat->pong_timeout = 5;
+
+ llist_add(&nat->main_dest->list, &nat->dests);
+ nat->main_dest->ip = talloc_strdup(nat, "127.0.0.1");
+ nat->main_dest->port = 5000;
+
+ return nat;
+}
+
+void bsc_nat_free(struct bsc_nat *nat)
+{
+ struct bsc_config *cfg, *tmp;
+ struct bsc_msg_acc_lst *lst, *tmp_lst;
+
+ llist_for_each_entry_safe(cfg, tmp, &nat->bsc_configs, entry)
+ bsc_config_free(cfg);
+ llist_for_each_entry_safe(lst, tmp_lst, &nat->access_lists, list)
+ bsc_msg_acc_lst_delete(lst);
+
+ bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr, NULL);
+ bsc_nat_num_rewr_entry_adapt(nat, &nat->num_rewr_post, NULL);
+ bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_clear_tp_srr, NULL);
+ bsc_nat_num_rewr_entry_adapt(nat, &nat->sms_num_rewr, NULL);
+ bsc_nat_num_rewr_entry_adapt(nat, &nat->tpdest_match, NULL);
+
+ osmo_counter_free(nat->stats.sccp.conn);
+ osmo_counter_free(nat->stats.sccp.calls);
+ osmo_counter_free(nat->stats.bsc.reconn);
+ osmo_counter_free(nat->stats.bsc.auth_fail);
+ osmo_counter_free(nat->stats.msc.reconn);
+ osmo_counter_free(nat->stats.ussd.reconn);
+ talloc_free(nat->mgcp_cfg);
+ talloc_free(nat);
+}
+
+void bsc_nat_set_msc_ip(struct bsc_nat *nat, const char *ip)
+{
+ osmo_talloc_replace_string(nat, &nat->main_dest->ip, ip);
+}
+
+struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat)
+{
+ struct bsc_connection *con = talloc_zero(nat, struct bsc_connection);
+ if (!con)
+ return NULL;
+
+ con->nat = nat;
+ osmo_wqueue_init(&con->write_queue, 100);
+ INIT_LLIST_HEAD(&con->cmd_pending);
+ INIT_LLIST_HEAD(&con->pending_dlcx);
+ return con;
+}
+
+struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token,
+ unsigned int number)
+{
+ struct bsc_config *conf = talloc_zero(nat, struct bsc_config);
+ if (!conf)
+ return NULL;
+
+ conf->token = talloc_strdup(conf, token);
+ conf->nr = number;
+ conf->nat = nat;
+ conf->max_endpoints = 32;
+ conf->paging_group = PAGIN_GROUP_UNASSIGNED;
+
+ INIT_LLIST_HEAD(&conf->lac_list);
+
+ llist_add_tail(&conf->entry, &nat->bsc_configs);
+ ++nat->num_bsc;
+
+ conf->stats.ctrg = rate_ctr_group_alloc(conf, &bsc_cfg_ctrg_desc, conf->nr);
+ if (!conf->stats.ctrg) {
+ llist_del(&conf->entry);
+ talloc_free(conf);
+ return NULL;
+ }
+
+ return conf;
+}
+
+struct bsc_config *bsc_config_by_token(struct bsc_nat *nat, const char *token, int len)
+{
+ struct bsc_config *conf;
+
+ llist_for_each_entry(conf, &nat->bsc_configs, entry) {
+ /*
+ * Add the '\0' of the token for the memcmp, the IPA messages
+ * for some reason added null termination.
+ */
+ const int token_len = strlen(conf->token) + 1;
+
+ if (token_len == len && memcmp(conf->token, token, token_len) == 0)
+ return conf;
+ }
+
+ return NULL;
+}
+
+void bsc_config_free(struct bsc_config *cfg)
+{
+ llist_del(&cfg->entry);
+ rate_ctr_group_free(cfg->stats.ctrg);
+ cfg->nat->num_bsc--;
+ OSMO_ASSERT(cfg->nat->num_bsc >= 0)
+ talloc_free(cfg);
+}
+
+static void _add_lac(void *ctx, struct llist_head *list, int _lac)
+{
+ struct bsc_lac_entry *lac;
+
+ llist_for_each_entry(lac, list, entry)
+ if (lac->lac == _lac)
+ return;
+
+ lac = talloc_zero(ctx, struct bsc_lac_entry);
+ if (!lac) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate.\n");
+ return;
+ }
+
+ lac->lac = _lac;
+ llist_add_tail(&lac->entry, list);
+}
+
+static void _del_lac(struct llist_head *list, int _lac)
+{
+ struct bsc_lac_entry *lac;
+
+ llist_for_each_entry(lac, list, entry)
+ if (lac->lac == _lac) {
+ llist_del(&lac->entry);
+ talloc_free(lac);
+ return;
+ }
+}
+
+void bsc_config_add_lac(struct bsc_config *cfg, int _lac)
+{
+ _add_lac(cfg, &cfg->lac_list, _lac);
+}
+
+void bsc_config_del_lac(struct bsc_config *cfg, int _lac)
+{
+ _del_lac(&cfg->lac_list, _lac);
+}
+
+struct bsc_nat_paging_group *bsc_nat_paging_group_create(struct bsc_nat *nat, int group)
+{
+ struct bsc_nat_paging_group *pgroup;
+
+ pgroup = talloc_zero(nat, struct bsc_nat_paging_group);
+ if (!pgroup) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate a paging group.\n");
+ return NULL;
+ }
+
+ pgroup->nr = group;
+ INIT_LLIST_HEAD(&pgroup->lists);
+ llist_add_tail(&pgroup->entry, &nat->paging_groups);
+ return pgroup;
+}
+
+void bsc_nat_paging_group_delete(struct bsc_nat_paging_group *pgroup)
+{
+ llist_del(&pgroup->entry);
+ talloc_free(pgroup);
+}
+
+struct bsc_nat_paging_group *bsc_nat_paging_group_num(struct bsc_nat *nat, int group)
+{
+ struct bsc_nat_paging_group *pgroup;
+
+ llist_for_each_entry(pgroup, &nat->paging_groups, entry)
+ if (pgroup->nr == group)
+ return pgroup;
+
+ return NULL;
+}
+
+void bsc_nat_paging_group_add_lac(struct bsc_nat_paging_group *pgroup, int lac)
+{
+ _add_lac(pgroup, &pgroup->lists, lac);
+}
+
+void bsc_nat_paging_group_del_lac(struct bsc_nat_paging_group *pgroup, int lac)
+{
+ _del_lac(&pgroup->lists, lac);
+}
+
+int bsc_config_handles_lac(struct bsc_config *cfg, int lac_nr)
+{
+ struct bsc_nat_paging_group *pgroup;
+ struct bsc_lac_entry *entry;
+
+ llist_for_each_entry(entry, &cfg->lac_list, entry)
+ if (entry->lac == lac_nr)
+ return 1;
+
+ /* now lookup the paging group */
+ pgroup = bsc_nat_paging_group_num(cfg->nat, cfg->paging_group);
+ if (!pgroup)
+ return 0;
+
+ llist_for_each_entry(entry, &pgroup->lists, entry)
+ if (entry->lac == lac_nr)
+ return 1;
+
+ return 0;
+}
+
+void sccp_connection_destroy(struct nat_sccp_connection *conn)
+{
+ LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n",
+ sccp_src_ref_to_int(&conn->real_ref),
+ sccp_src_ref_to_int(&conn->patched_ref), conn->bsc);
+ bsc_mgcp_dlcx(conn);
+ llist_del(&conn->list_entry);
+ talloc_free(conn);
+}
+
+
+int bsc_nat_find_paging(struct msgb *msg,
+ const uint8_t **out_data, int *out_leng)
+{
+ int data_length;
+ const uint8_t *data;
+ struct tlv_parsed tp;
+
+ if (!msg->l3h || msgb_l3len(msg) < 3) {
+ LOGP(DNAT, LOGL_ERROR, "Paging message is too short.\n");
+ return -1;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+ LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n");
+ return -2;
+ }
+
+ data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+ data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+ /* No need to try a different BSS */
+ if (data[0] == CELL_IDENT_BSS) {
+ return -3;
+ } else if (data[0] != CELL_IDENT_LAC) {
+ LOGP(DNAT, LOGL_ERROR, "Unhandled cell ident discrminator: %d\n", data[0]);
+ return -4;
+ }
+
+ *out_data = &data[1];
+ *out_leng = data_length - 1;
+ return 0;
+}
+
+int bsc_write_mgcp(struct bsc_connection *bsc, const uint8_t *data, unsigned int length)
+{
+ struct msgb *msg;
+
+ if (length > 4096 - 128) {
+ LOGP(DLINP, LOGL_ERROR, "Can not send message of that size.\n");
+ return -1;
+ }
+
+ msg = msgb_alloc_headroom(4096, 128, "to-bsc");
+ if (!msg) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n");
+ return -1;
+ }
+
+ /* copy the data */
+ msg->l3h = msgb_put(msg, length);
+ memcpy(msg->l3h, data, length);
+
+ return bsc_write(bsc, msg, IPAC_PROTO_MGCP_OLD);
+}
+
+int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int proto)
+{
+ return bsc_do_write(&bsc->write_queue, msg, proto);
+}
+
+int bsc_do_write(struct osmo_wqueue *queue, struct msgb *msg, int proto)
+{
+ /* prepend the header */
+ ipa_prepend_header(msg, proto);
+ return bsc_write_msg(queue, msg);
+}
+
+int bsc_write_msg(struct osmo_wqueue *queue, struct msgb *msg)
+{
+ if (osmo_wqueue_enqueue(queue, msg) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the write.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed,
+ struct msgb *msg, uint32_t *len)
+{
+ /* gsm_type is actually the size of the dtap */
+ *len = parsed->gsm_type;
+ if (*len < msgb_l3len(msg) - 3) {
+ LOGP(DNAT, LOGL_ERROR, "Not enough space for DTAP.\n");
+ return NULL;
+ }
+
+ if (msgb_l3len(msg) - 3 < msg->l3h[2]) {
+ LOGP(DNAT, LOGL_ERROR,
+ "GSM48 payload does not fit: %d %d\n",
+ msg->l3h[2], msgb_l3len(msg) - 3);
+ return NULL;
+ }
+
+ msg->l4h = &msg->l3h[3];
+ return (struct gsm48_hdr *) msg->l4h;
+}
+
+static const char *con_types [] = {
+ [FLT_CON_TYPE_NONE] = "n/a",
+ [FLT_CON_TYPE_LU] = "Location Update",
+ [FLT_CON_TYPE_CM_SERV_REQ] = "CM Serv Req",
+ [FLT_CON_TYPE_PAG_RESP] = "Paging Response",
+ [FLT_CON_TYPE_SSA] = "Supplementar Service Activation",
+ [FLT_CON_TYPE_LOCAL_REJECT] = "Local Reject",
+ [FLT_CON_TYPE_OTHER] = "Other",
+};
+
+const char *bsc_con_type_to_string(int type)
+{
+ return con_types[type];
+}
+
+int bsc_nat_msc_is_connected(struct bsc_nat *nat)
+{
+ return nat->msc_con->is_connected;
+}
+
+static const int con_to_ctr[] = {
+ [FLT_CON_TYPE_NONE] = -1,
+ [FLT_CON_TYPE_LU] = BCFG_CTR_CON_TYPE_LU,
+ [FLT_CON_TYPE_CM_SERV_REQ] = BCFG_CTR_CON_CMSERV_RQ,
+ [FLT_CON_TYPE_PAG_RESP] = BCFG_CTR_CON_PAG_RESP,
+ [FLT_CON_TYPE_SSA] = BCFG_CTR_CON_SSA,
+ [FLT_CON_TYPE_LOCAL_REJECT] = -1,
+ [FLT_CON_TYPE_OTHER] = BCFG_CTR_CON_OTHER,
+};
+
+int bsc_conn_type_to_ctr(struct nat_sccp_connection *conn)
+{
+ return con_to_ctr[conn->filter_state.con_type];
+}
+
+int bsc_write_cb(struct osmo_fd *bfd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(bfd->fd, msg->data, msg->len);
+ if (rc != msg->len)
+ LOGP(DNAT, LOGL_ERROR, "Failed to write message to the BSC.\n");
+
+ return rc;
+}
+
+static void extract_lac(const uint8_t *data, uint16_t *lac, uint16_t *ci)
+{
+ memcpy(lac, &data[0], sizeof(*lac));
+ memcpy(ci, &data[2], sizeof(*ci));
+
+ *lac = ntohs(*lac);
+ *ci = ntohs(*ci);
+}
+
+int bsc_nat_extract_lac(struct bsc_connection *bsc,
+ struct nat_sccp_connection *con,
+ struct bsc_nat_parsed *parsed, struct msgb *msg)
+{
+ int data_length;
+ const uint8_t *data;
+ struct tlv_parsed tp;
+ uint16_t lac, ci;
+
+ if (parsed->gsm_type != BSS_MAP_MSG_COMPLETE_LAYER_3) {
+ LOGP(DNAT, LOGL_ERROR, "Can only extract LAC from Complete Layer3\n");
+ return -1;
+ }
+
+ if (!msg->l3h || msgb_l3len(msg) < 3) {
+ LOGP(DNAT, LOGL_ERROR, "Complete Layer3 mssage is too short.\n");
+ return -1;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER)) {
+ LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n");
+ return -2;
+ }
+
+ data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER);
+ data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER);
+
+ /* Attemt to get the LAC/CI from it */
+ if (data[0] == CELL_IDENT_WHOLE_GLOBAL) {
+ if (data_length != 8) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Ident too short: %d\n", data_length);
+ return -3;
+ }
+ extract_lac(&data[1 + 3], &lac, &ci);
+ } else if (data[0] == CELL_IDENT_LAC_AND_CI) {
+ if (data_length != 5) {
+ LOGP(DNAT, LOGL_ERROR,
+ "Ident too short: %d\n", data_length);
+ return -3;
+ }
+ extract_lac(&data[1], &lac, &ci);
+ } else {
+ LOGP(DNAT, LOGL_ERROR,
+ "Unhandled cell identifier: %d\n", data[0]);
+ return -1;
+ }
+
+ con->lac = lac;
+ con->ci = ci;
+ return 0;
+}
diff --git a/src/osmo-bsc_nat/bsc_nat_vty.c b/src/osmo-bsc_nat/bsc_nat_vty.c
new file mode 100644
index 000000000..a11ae151e
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_nat_vty.c
@@ -0,0 +1,1336 @@
+/* OpenBSC NAT interface to quagga VTY */
+/* (C) 2010-2015 by Holger Hans Peter Freyther
+ * (C) 2010-2015 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/bsc_msc.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/vty.h>
+#include <openbsc/nat_rewrite_trie.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <openbsc/osmux.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+static struct bsc_nat *_nat;
+
+
+#define BSC_STR "Information about BSCs\n"
+#define MGCP_STR "MGCP related status\n"
+#define PAGING_STR "Paging\n"
+#define SMSC_REWRITE "SMSC Rewriting\n"
+
+static struct cmd_node nat_node = {
+ NAT_NODE,
+ "%s(config-nat)# ",
+ 1,
+};
+
+static struct cmd_node bsc_node = {
+ NAT_BSC_NODE,
+ "%s(config-nat-bsc)# ",
+ 1,
+};
+
+static struct cmd_node pgroup_node = {
+ PGROUP_NODE,
+ "%s(config-nat-paging-group)# ",
+ 1,
+};
+
+static int config_write_pgroup(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+static void dump_lac(struct vty *vty, struct llist_head *head)
+{
+ struct bsc_lac_entry *lac;
+ llist_for_each_entry(lac, head, entry)
+ vty_out(vty, " location_area_code %u%s", lac->lac, VTY_NEWLINE);
+}
+
+
+static void write_pgroup_lst(struct vty *vty, struct bsc_nat_paging_group *pgroup)
+{
+ vty_out(vty, " paging-group %d%s", pgroup->nr, VTY_NEWLINE);
+ dump_lac(vty, &pgroup->lists);
+}
+
+static int config_write_nat(struct vty *vty)
+{
+ struct bsc_msg_acc_lst *lst;
+ struct bsc_nat_paging_group *pgroup;
+
+ vty_out(vty, "nat%s", VTY_NEWLINE);
+ vty_out(vty, " msc ip %s%s", _nat->main_dest->ip, VTY_NEWLINE);
+ vty_out(vty, " msc port %d%s", _nat->main_dest->port, VTY_NEWLINE);
+ vty_out(vty, " timeout auth %d%s", _nat->auth_timeout, VTY_NEWLINE);
+ vty_out(vty, " timeout ping %d%s", _nat->ping_timeout, VTY_NEWLINE);
+ vty_out(vty, " timeout pong %d%s", _nat->pong_timeout, VTY_NEWLINE);
+ if (_nat->include_file)
+ vty_out(vty, " bscs-config-file %s%s", _nat->include_file, VTY_NEWLINE);
+ if (_nat->token)
+ vty_out(vty, " token %s%s", _nat->token, VTY_NEWLINE);
+ 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)
+ vty_out(vty, " ussd-query %s%s", _nat->ussd_query, VTY_NEWLINE);
+ if (_nat->ussd_token)
+ vty_out(vty, " ussd-token %s%s", _nat->ussd_token, VTY_NEWLINE);
+ if (_nat->ussd_local)
+ vty_out(vty, " ussd-local-ip %s%s", _nat->ussd_local, VTY_NEWLINE);
+
+ if (_nat->num_rewr_name)
+ vty_out(vty, " number-rewrite %s%s", _nat->num_rewr_name, VTY_NEWLINE);
+ if (_nat->num_rewr_post_name)
+ vty_out(vty, " number-rewrite-post %s%s",
+ _nat->num_rewr_post_name, VTY_NEWLINE);
+
+ if (_nat->smsc_rewr_name)
+ vty_out(vty, " rewrite-smsc addr %s%s",
+ _nat->smsc_rewr_name, VTY_NEWLINE);
+ if (_nat->tpdest_match_name)
+ vty_out(vty, " rewrite-smsc tp-dest-match %s%s",
+ _nat->tpdest_match_name, VTY_NEWLINE);
+ if (_nat->sms_clear_tp_srr_name)
+ vty_out(vty, " sms-clear-tp-srr %s%s",
+ _nat->sms_clear_tp_srr_name, VTY_NEWLINE);
+ if (_nat->sms_num_rewr_name)
+ vty_out(vty, " sms-number-rewrite %s%s",
+ _nat->sms_num_rewr_name, VTY_NEWLINE);
+ if (_nat->num_rewr_trie_name)
+ vty_out(vty, " prefix-tree %s%s",
+ _nat->num_rewr_trie_name, VTY_NEWLINE);
+
+ llist_for_each_entry(lst, &_nat->access_lists, list)
+ bsc_msg_acc_lst_write(vty, lst);
+ llist_for_each_entry(pgroup, &_nat->paging_groups, entry)
+ write_pgroup_lst(vty, pgroup);
+ if (_nat->mgcp_ipa)
+ vty_out(vty, " use-msc-ipa-for-mgcp%s", VTY_NEWLINE);
+ vty_out(vty, " %ssdp-ensure-amr-mode-set%s",
+ _nat->sdp_ensure_amr_mode_set ? "" : "no ", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+static void config_write_bsc_single(struct vty *vty, struct bsc_config *bsc)
+{
+ vty_out(vty, " bsc %u%s", bsc->nr, VTY_NEWLINE);
+ vty_out(vty, " token %s%s", bsc->token, VTY_NEWLINE);
+ if (bsc->key_present)
+ vty_out(vty, " auth-key %s%s", osmo_hexdump(bsc->key, 16), VTY_NEWLINE);
+ dump_lac(vty, &bsc->lac_list);
+ if (bsc->description)
+ vty_out(vty, " description %s%s", bsc->description, VTY_NEWLINE);
+ if (bsc->acc_lst_name)
+ vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+ vty_out(vty, " max-endpoints %d%s", bsc->max_endpoints, VTY_NEWLINE);
+ if (bsc->paging_group != -1)
+ vty_out(vty, " paging group %d%s", bsc->paging_group, VTY_NEWLINE);
+ vty_out(vty, " paging forbidden %d%s", bsc->forbid_paging, VTY_NEWLINE);
+ switch (bsc->osmux) {
+ case OSMUX_USAGE_ON:
+ vty_out(vty, " osmux on%s", VTY_NEWLINE);
+ break;
+ case OSMUX_USAGE_ONLY:
+ vty_out(vty, " osmux only%s", VTY_NEWLINE);
+ break;
+ }
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+ struct bsc_config *bsc;
+
+ llist_for_each_entry(bsc, &_nat->bsc_configs, entry)
+ config_write_bsc_single(vty, bsc);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_bscs, show_bscs_cmd, "show bscs-config",
+ SHOW_STR "Show configured BSCs\n"
+ "Both from included file and vty\n")
+{
+ vty_out(vty, "BSCs configuration loaded from %s:%s", _nat->resolved_path,
+ VTY_NEWLINE);
+ return config_write_bsc(vty);
+}
+
+DEFUN(show_sccp, show_sccp_cmd, "show sccp connections",
+ SHOW_STR "Display information about SCCP\n"
+ "All active connections\n")
+{
+ struct nat_sccp_connection *con;
+ vty_out(vty, "Listing all open SCCP connections%s", VTY_NEWLINE);
+
+ llist_for_each_entry(con, &_nat->sccp_connections, list_entry) {
+ vty_out(vty, "For BSC Nr: %d BSC ref: 0x%x; MUX ref: 0x%x; Network has ref: %d ref: 0x%x MSC/BSC mux: 0x%x/0x%x type: %s%s",
+ con->bsc->cfg ? con->bsc->cfg->nr : -1,
+ sccp_src_ref_to_int(&con->real_ref),
+ sccp_src_ref_to_int(&con->patched_ref),
+ con->has_remote_ref,
+ sccp_src_ref_to_int(&con->remote_ref),
+ con->msc_endp, con->bsc_endp,
+ bsc_con_type_to_string(con->filter_state.con_type),
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_nat_bsc, show_nat_bsc_cmd, "show nat num-bscs-configured",
+ SHOW_STR "Display NAT configuration details\n"
+ "BSCs-related\n")
+{
+ vty_out(vty, "%d BSCs configured%s", _nat->num_bsc, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc, show_bsc_cmd, "show bsc connections",
+ SHOW_STR BSC_STR
+ "All active connections\n")
+{
+ struct bsc_connection *con;
+ struct sockaddr_in sock;
+ socklen_t len = sizeof(sock);
+
+ llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+ getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len);
+ vty_out(vty, "BSC nr: %d auth: %d fd: %d peername: %s pending-stats: %u%s",
+ con->cfg ? con->cfg->nr : -1,
+ con->authenticated, con->write_queue.bfd.fd,
+ inet_ntoa(sock.sin_addr), con->pending_dlcx_count,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc_mgcp, show_bsc_mgcp_cmd, "show bsc mgcp NR",
+ SHOW_STR BSC_STR MGCP_STR "Identifier of the BSC\n")
+{
+ struct bsc_connection *con;
+ int nr = atoi(argv[0]);
+ int i, j, endp;
+
+ llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+ int max;
+ if (!con->cfg)
+ continue;
+ if (con->cfg->nr != nr)
+ continue;
+
+ /* this bsc has no audio endpoints yet */
+ if (!con->_endpoint_status)
+ continue;
+
+ vty_out(vty, "MGCP Status for %d%s", con->cfg->nr, VTY_NEWLINE);
+ max = bsc_mgcp_nr_multiplexes(con->max_endpoints);
+ for (i = 0; i < max; ++i) {
+ for (j = 1; j < 32; ++j) {
+ endp = mgcp_timeslot_to_endpoint(i, j);
+ vty_out(vty, " Endpoint 0x%x %s%s", endp,
+ con->_endpoint_status[endp] == 0
+ ? "free" : "allocated",
+ VTY_NEWLINE);
+ }
+ }
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_bsc_cfg, show_bsc_cfg_cmd, "show bsc config",
+ SHOW_STR BSC_STR "Configuration of BSCs\n")
+{
+ struct bsc_config *conf;
+ llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+ vty_out(vty, "BSC token: '%s' nr: %u%s",
+ conf->token, conf->nr, VTY_NEWLINE);
+ if (conf->acc_lst_name)
+ vty_out(vty, " access-list: %s%s",
+ conf->acc_lst_name, VTY_NEWLINE);
+ vty_out(vty, " paging forbidden: %d%s",
+ conf->forbid_paging, VTY_NEWLINE);
+ if (conf->description)
+ vty_out(vty, " description: %s%s", conf->description, VTY_NEWLINE);
+ else
+ vty_out(vty, " No description.%s", VTY_NEWLINE);
+
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void dump_stat_total(struct vty *vty, struct bsc_nat *nat)
+{
+ vty_out(vty, "NAT statistics%s", VTY_NEWLINE);
+ vty_out(vty, " SCCP Connections %lu total, %lu calls%s",
+ osmo_counter_get(nat->stats.sccp.conn),
+ osmo_counter_get(nat->stats.sccp.calls), VTY_NEWLINE);
+ vty_out(vty, " MSC Connections %lu%s",
+ osmo_counter_get(nat->stats.msc.reconn), VTY_NEWLINE);
+ vty_out(vty, " MSC Connected: %d%s",
+ bsc_nat_msc_is_connected(nat), VTY_NEWLINE);
+ vty_out(vty, " BSC Connections %lu total, %lu auth failed.%s",
+ osmo_counter_get(nat->stats.bsc.reconn),
+ osmo_counter_get(nat->stats.bsc.auth_fail), VTY_NEWLINE);
+}
+
+static void dump_stat_bsc(struct vty *vty, struct bsc_config *conf)
+{
+ int connected = 0;
+ struct bsc_connection *con;
+
+ vty_out(vty, " BSC nr: %d%s",
+ conf->nr, VTY_NEWLINE);
+ vty_out_rate_ctr_group(vty, " ", conf->stats.ctrg);
+
+ llist_for_each_entry(con, &conf->nat->bsc_connections, list_entry) {
+ if (con->cfg != conf)
+ continue;
+ connected = 1;
+ break;
+ }
+
+ vty_out(vty, " Connected: %d%s", connected, VTY_NEWLINE);
+}
+
+DEFUN(show_stats,
+ show_stats_cmd,
+ "show statistics [NR]",
+ SHOW_STR "Display network statistics\n"
+ "Number of the BSC\n")
+{
+ struct bsc_config *conf;
+
+ int nr = -1;
+
+ if (argc == 1)
+ nr = atoi(argv[0]);
+
+ dump_stat_total(vty, _nat);
+ llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+ if (argc == 1 && nr != conf->nr)
+ continue;
+ dump_stat_bsc(vty, conf);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_stats_lac,
+ show_stats_lac_cmd,
+ "show statistics-by-lac <0-65535>",
+ SHOW_STR "Display network statistics by lac\n"
+ "The lac of the BSC\n")
+{
+ int lac;
+ struct bsc_config *conf;
+
+ lac = atoi(argv[0]);
+
+ dump_stat_total(vty, _nat);
+ llist_for_each_entry(conf, &_nat->bsc_configs, entry) {
+ if (!bsc_config_handles_lac(conf, lac))
+ continue;
+ dump_stat_bsc(vty, conf);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_msc,
+ show_msc_cmd,
+ "show msc connection",
+ SHOW_STR "MSC related information\n"
+ "Status of the A-link connection\n")
+{
+ if (!_nat->msc_con) {
+ vty_out(vty, "The MSC is not yet configured.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "MSC is connected: %d%s",
+ bsc_nat_msc_is_connected(_nat), VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(close_bsc,
+ close_bsc_cmd,
+ "close bsc connection BSC_NR",
+ "Close\n" "A-link\n" "Connection\n" "Identifier of the BSC\n")
+{
+ struct bsc_connection *bsc;
+ int bsc_nr = atoi(argv[0]);
+
+ llist_for_each_entry(bsc, &_nat->bsc_connections, list_entry) {
+ if (!bsc->cfg || bsc->cfg->nr != bsc_nr)
+ continue;
+ bsc_close_connection(bsc);
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat, cfg_nat_cmd, "nat", "Configure the NAT")
+{
+ vty->index = _nat;
+ vty->node = NAT_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_ip,
+ cfg_nat_msc_ip_cmd,
+ "msc ip A.B.C.D",
+ "MSC related configuration\n"
+ "Configure the IP address\n" IP_STR)
+{
+ bsc_nat_set_msc_ip(_nat, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_msc_port,
+ cfg_nat_msc_port_cmd,
+ "msc port <1-65500>",
+ "MSC related configuration\n"
+ "Configure the port\n"
+ "Port number\n")
+{
+ _nat->main_dest->port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_auth_time,
+ cfg_nat_auth_time_cmd,
+ "timeout auth <1-256>",
+ "Timeout configuration\n"
+ "Authentication timeout\n"
+ "Timeout in seconds\n")
+{
+ _nat->auth_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ping_time,
+ cfg_nat_ping_time_cmd,
+ "timeout ping NR",
+ "Timeout configuration\n"
+ "Time between two pings\n"
+ "Timeout in seconds\n")
+{
+ _nat->ping_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_pong_time,
+ cfg_nat_pong_time_cmd,
+ "timeout pong NR",
+ "Timeout configuration\n"
+ "Waiting for pong timeout\n"
+ "Timeout in seconds\n")
+{
+ _nat->pong_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_token, cfg_nat_token_cmd,
+ "token TOKEN",
+ "Authentication token configuration\n"
+ "Token of the BSC, currently transferred in cleartext\n")
+{
+ osmo_talloc_replace_string(_nat, &_nat->token, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_dscp_cmd,
+ "ip-dscp <0-255>",
+ "Set the IP DSCP for the BSCs to use\n" "Set the IP_TOS attribute")
+{
+ _nat->bsc_ip_dscp = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_nat_bsc_ip_dscp, cfg_nat_bsc_ip_tos_cmd,
+ "ip-tos <0-255>",
+ "Use ip-dscp in the future.\n" "Set the DSCP\n")
+
+
+DEFUN(cfg_nat_acc_lst_name,
+ cfg_nat_acc_lst_name_cmd,
+ "access-list-name NAME",
+ "Set the name of the access list to use.\n"
+ "The name of the to be used access list.")
+{
+ osmo_talloc_replace_string(_nat, &_nat->acc_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_include,
+ cfg_nat_include_cmd,
+ "bscs-config-file NAME",
+ "Set the filename of the BSC configuration to include.\n"
+ "The filename to be included.")
+{
+ char *path;
+ int rc;
+ struct bsc_config *cf1, *cf2;
+ struct bsc_connection *con1, *con2;
+
+ if ('/' == argv[0][0])
+ osmo_talloc_replace_string(_nat, &_nat->resolved_path, argv[0]);
+ else {
+ path = talloc_asprintf(_nat, "%s/%s", _nat->include_base,
+ argv[0]);
+ osmo_talloc_replace_string(_nat, &_nat->resolved_path, path);
+ talloc_free(path);
+ }
+
+ llist_for_each_entry_safe(cf1, cf2, &_nat->bsc_configs, entry) {
+ cf1->remove = true;
+ cf1->token_updated = false;
+ }
+
+ rc = vty_read_config_file(_nat->resolved_path, NULL);
+ if (rc < 0) {
+ vty_out(vty, "Failed to parse the config file %s: %s%s",
+ _nat->resolved_path, strerror(-rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_talloc_replace_string(_nat, &_nat->include_file, argv[0]);
+
+ llist_for_each_entry_safe(con1, con2, &_nat->bsc_connections,
+ list_entry) {
+ if (con1->cfg)
+ if (con1->cfg->token_updated || con1->cfg->remove)
+ bsc_close_connection(con1);
+ }
+
+ llist_for_each_entry_safe(cf1, cf2, &_nat->bsc_configs, entry) {
+ if (cf1->remove)
+ bsc_config_free(cf1);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_acc_lst_name,
+ cfg_nat_no_acc_lst_name_cmd,
+ "no access-list-name",
+ NO_STR "Remove the access list from the NAT.\n")
+{
+ if (_nat->acc_lst_name) {
+ talloc_free(_nat->acc_lst_name);
+ _nat->acc_lst_name = NULL;
+ }
+
+ 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")
+{
+
+ osmo_talloc_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_filter_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_filter_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_filter_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)
+{
+ struct osmo_config_list *rewr = NULL;
+
+ osmo_talloc_replace_string(nat, name, file);
+ if (*name) {
+ rewr = osmo_config_list_parse(nat, *name);
+ bsc_nat_num_rewr_entry_adapt(nat, head, rewr);
+ talloc_free(rewr);
+ return CMD_SUCCESS;
+ } else {
+ bsc_nat_num_rewr_entry_adapt(nat, head, NULL);
+ return CMD_SUCCESS;
+ }
+}
+
+DEFUN(cfg_nat_number_rewrite,
+ cfg_nat_number_rewrite_cmd,
+ "number-rewrite FILENAME",
+ "Set the file with rewriting rules.\n" "Filename")
+{
+ return replace_rules(_nat, &_nat->num_rewr_name,
+ &_nat->num_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_number_rewrite,
+ cfg_nat_no_number_rewrite_cmd,
+ "no number-rewrite",
+ NO_STR "Set the file with rewriting rules.\n")
+{
+ talloc_free(_nat->num_rewr_name);
+ _nat->num_rewr_name = NULL;
+
+ bsc_nat_num_rewr_entry_adapt(NULL, &_nat->num_rewr, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_number_rewrite_post,
+ cfg_nat_number_rewrite_post_cmd,
+ "number-rewrite-post FILENAME",
+ "Set the file with post-routing rewriting rules.\n" "Filename")
+{
+ return replace_rules(_nat, &_nat->num_rewr_post_name,
+ &_nat->num_rewr_post, argv[0]);
+}
+
+DEFUN(cfg_nat_no_number_rewrite_post,
+ cfg_nat_no_number_rewrite_post_cmd,
+ "no number-rewrite-post",
+ NO_STR "Set the file with post-routing rewriting rules.\n")
+{
+ talloc_free(_nat->num_rewr_post_name);
+ _nat->num_rewr_post_name = NULL;
+
+ bsc_nat_num_rewr_entry_adapt(NULL, &_nat->num_rewr_post, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_smsc_addr,
+ cfg_nat_smsc_addr_cmd,
+ "rewrite-smsc addr FILENAME",
+ SMSC_REWRITE
+ "The SMSC Address to match and replace in RP-DATA\n"
+ "File with rules for the SMSC Address replacing\n")
+{
+ return replace_rules(_nat, &_nat->smsc_rewr_name,
+ &_nat->smsc_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_smsc_tpdest,
+ cfg_nat_smsc_tpdest_cmd,
+ "rewrite-smsc tp-dest-match FILENAME",
+ SMSC_REWRITE
+ "Match TP-Destination of a SMS.\n"
+ "File with rules for matching MSISDN and TP-DEST\n")
+{
+ return replace_rules(_nat, &_nat->tpdest_match_name,
+ &_nat->tpdest_match, argv[0]);
+}
+
+DEFUN(cfg_nat_sms_clear_tpsrr,
+ cfg_nat_sms_clear_tpsrr_cmd,
+ "sms-clear-tp-srr FILENAME",
+ "SMS TPDU Sender Report Request clearing\n"
+ "Files with rules for matching MSISDN\n")
+{
+ return replace_rules(_nat, &_nat->sms_clear_tp_srr_name,
+ &_nat->sms_clear_tp_srr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_sms_clear_tpsrr,
+ cfg_nat_no_sms_clear_tpsrr_cmd,
+ "no sms-clear-tp-srr",
+ NO_STR
+ "SMS TPDU Sender Report Request clearing\n")
+{
+ talloc_free(_nat->sms_clear_tp_srr_name);
+ _nat->sms_clear_tp_srr_name = NULL;
+
+ bsc_nat_num_rewr_entry_adapt(NULL, &_nat->sms_clear_tp_srr, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_sms_number_rewrite,
+ cfg_nat_sms_number_rewrite_cmd,
+ "sms-number-rewrite FILENAME",
+ "SMS TP-DA Number rewriting\n"
+ "Files with rules for matching MSISDN\n")
+{
+ return replace_rules(_nat, &_nat->sms_num_rewr_name,
+ &_nat->sms_num_rewr, argv[0]);
+}
+
+DEFUN(cfg_nat_no_sms_number_rewrite,
+ cfg_nat_no_sms_number_rewrite_cmd,
+ "no sms-number-rewrite",
+ NO_STR "Disable SMS TP-DA rewriting\n")
+{
+ talloc_free(_nat->sms_num_rewr_name);
+ _nat->sms_num_rewr_name = NULL;
+
+ bsc_nat_num_rewr_entry_adapt(NULL, &_nat->sms_num_rewr, NULL);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_prefix_trie,
+ cfg_nat_prefix_trie_cmd,
+ "prefix-tree FILENAME",
+ "Prefix tree for number rewriting\n" "File to load\n")
+{
+ /* give up the old data */
+ talloc_free(_nat->num_rewr_trie);
+ _nat->num_rewr_trie = NULL;
+
+ /* replace the file name */
+ osmo_talloc_replace_string(_nat, &_nat->num_rewr_trie_name, argv[0]);
+ if (!_nat->num_rewr_trie_name) {
+ vty_out(vty, "%% prefix-tree no filename is present.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ _nat->num_rewr_trie = nat_rewrite_parse(_nat, _nat->num_rewr_trie_name);
+ if (!_nat->num_rewr_trie) {
+ vty_out(vty, "%% prefix-tree parsing has failed.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% prefix-tree loaded %zu rules.%s",
+ _nat->num_rewr_trie->prefixes, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_prefix_trie, cfg_nat_no_prefix_trie_cmd,
+ "no prefix-tree",
+ NO_STR "Prefix tree for number rewriting\n")
+{
+ talloc_free(_nat->num_rewr_trie);
+ _nat->num_rewr_trie = NULL;
+ talloc_free(_nat->num_rewr_trie_name);
+ _nat->num_rewr_trie_name = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_prefix_tree, show_prefix_tree_cmd,
+ "show prefix-tree",
+ SHOW_STR "Prefix tree for number rewriting\n")
+{
+ if (!_nat->num_rewr_trie) {
+ vty_out(vty, "%% there is now prefix tree loaded.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nat_rewrite_dump_vty(vty, _nat->num_rewr_trie);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_lst_name,
+ cfg_nat_ussd_lst_name_cmd,
+ "ussd-list-name NAME",
+ "Set the name of the access list to check for IMSIs for USSD message\n"
+ "The name of the access list for HLR USSD handling")
+{
+ osmo_talloc_replace_string(_nat, &_nat->ussd_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_query,
+ cfg_nat_ussd_query_cmd,
+ "ussd-query REGEXP",
+ "Set the USSD query to match with the ussd-list-name\n"
+ "The query to match")
+{
+ if (gsm_parse_reg(_nat, &_nat->ussd_query_re, &_nat->ussd_query, argc, argv) != 0)
+ return CMD_WARNING;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_token,
+ cfg_nat_ussd_token_cmd,
+ "ussd-token TOKEN",
+ "Set the token used to identify the USSD module\n" "Secret key\n")
+{
+ osmo_talloc_replace_string(_nat, &_nat->ussd_token, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_local,
+ cfg_nat_ussd_local_cmd,
+ "ussd-local-ip A.B.C.D",
+ "Set the IP to listen for the USSD Provider\n" "IP Address\n")
+{
+ osmo_talloc_replace_string(_nat, &_nat->ussd_local, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_use_ipa_for_mgcp,
+ cfg_nat_use_ipa_for_mgcp_cmd,
+ "use-msc-ipa-for-mgcp",
+ "This needs to be set at start. Handle MGCP messages through "
+ "the IPA protocol and not through the UDP socket.\n")
+{
+ if (_nat->mgcp_cfg->data)
+ vty_out(vty,
+ "%%the setting will not be applied right now.%s",
+ VTY_NEWLINE);
+ _nat->mgcp_ipa = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_sdp_amr_mode_set,
+ cfg_nat_sdp_amr_mode_set_cmd,
+ "sdp-ensure-amr-mode-set",
+ "Ensure that SDP records include a mode-set\n")
+{
+ _nat->sdp_ensure_amr_mode_set = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_sdp_amr_mode_set,
+ cfg_nat_no_sdp_amr_mode_set_cmd,
+ "no sdp-ensure-amr-mode-set",
+ NO_STR "Ensure that SDP records include a mode-set\n")
+{
+ _nat->sdp_ensure_amr_mode_set = 0;
+ return CMD_SUCCESS;
+}
+
+/* per BSC configuration */
+DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR",
+ "BSC configuration\n" "Identifier of the BSC\n")
+{
+ int bsc_nr = atoi(argv[0]);
+ struct bsc_config *bsc = bsc_config_num(_nat, bsc_nr);
+
+ /* allocate a new one */
+ if (!bsc)
+ bsc = bsc_config_alloc(_nat, "unknown", bsc_nr);
+
+ if (!bsc)
+ return CMD_WARNING;
+
+ bsc->remove = false;
+ vty->index = bsc;
+ vty->node = NAT_BSC_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_token, cfg_bsc_token_cmd, "token TOKEN",
+ "Authentication token configuration\n"
+ "Token of the BSC, currently transferred in cleartext\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ if (strncmp(conf->token, argv[0], 128) != 0)
+ conf->token_updated = true;
+
+ osmo_talloc_replace_string(conf, &conf->token, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_auth_key, cfg_bsc_auth_key_cmd,
+ "auth-key KEY",
+ "Authentication (secret) key configuration\n"
+ "Non null security key\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ memset(conf->key, 0, sizeof(conf->key));
+ osmo_hexparse(argv[0], conf->key, sizeof(conf->key));
+ conf->key_present = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_auth_key, cfg_bsc_no_auth_key_cmd,
+ "no auth-key",
+ NO_STR "Authentication (secret) key configuration\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ memset(conf->key, 0, sizeof(conf->key));
+ conf->key_present = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_lac, cfg_bsc_lac_cmd, "location_area_code <0-65535>",
+ "Add the Location Area Code (LAC) of this BSC\n" "LAC\n")
+{
+ struct bsc_config *tmp;
+ struct bsc_config *conf = vty->index;
+
+ int lac = atoi(argv[0]);
+
+ if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+ vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+ lac, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* verify that the LACs are unique */
+ llist_for_each_entry(tmp, &_nat->bsc_configs, entry) {
+ if (bsc_config_handles_lac(tmp, lac)) {
+ if (tmp->nr != conf->nr) {
+ vty_out(vty, "%% LAC %d is already used.%s", lac,
+ VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+ }
+
+ bsc_config_add_lac(conf, lac);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_lac, cfg_bsc_no_lac_cmd,
+ "no location_area_code <0-65535>",
+ NO_STR "Remove the Location Area Code (LAC) of this BSC\n" "LAC\n")
+{
+ int lac = atoi(argv[0]);
+ struct bsc_config *conf = vty->index;
+
+ bsc_config_del_lac(conf, lac);
+ 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_filter_barr_entry *entry;
+ entry = rb_entry(node, struct bsc_filter_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,
+ "access-list-name NAME",
+ "Set the name of the access list to use.\n"
+ "The name of the to be used access list.")
+{
+ struct bsc_config *conf = vty->index;
+
+ osmo_talloc_replace_string(conf, &conf->acc_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_no_acc_lst_name,
+ cfg_bsc_no_acc_lst_name_cmd,
+ "no access-list-name",
+ NO_STR "Do not use an access-list for the BSC.\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ if (conf->acc_lst_name) {
+ talloc_free(conf->acc_lst_name);
+ conf->acc_lst_name = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_max_endps, cfg_bsc_max_endps_cmd,
+ "max-endpoints <1-1024>",
+ "Highest endpoint to use (exclusively)\n" "Number of ports\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ conf->max_endpoints = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_paging,
+ cfg_bsc_paging_cmd,
+ "paging forbidden (0|1)",
+ PAGING_STR "Forbid sending PAGING REQUESTS to the BSC.\n"
+ "Do not forbid\n" "Forbid\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ if (strcmp("1", argv[0]) == 0)
+ conf->forbid_paging = 1;
+ else
+ conf->forbid_paging = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_desc,
+ cfg_bsc_desc_cmd,
+ "description DESC",
+ "Provide a description for the given BSC.\n" "Description\n")
+{
+ struct bsc_config *conf = vty->index;
+
+ osmo_talloc_replace_string(conf, &conf->description, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bsc_paging_grp,
+ cfg_bsc_paging_grp_cmd,
+ "paging group <0-1000>",
+ PAGING_STR "Use a paging group\n" "Paging Group to use\n")
+{
+ struct bsc_config *conf = vty->index;
+ conf->paging_group = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+ALIAS_DEPRECATED(cfg_bsc_paging_grp, cfg_bsc_old_grp_cmd,
+ "paging-group <0-1000>",
+ "Use a paging group\n" "Paging Group to use\n")
+
+DEFUN(cfg_bsc_no_paging_grp,
+ cfg_bsc_no_paging_grp_cmd,
+ "no paging group",
+ NO_STR PAGING_STR "Disable the usage of a paging group.\n")
+{
+ struct bsc_config *conf = vty->index;
+ conf->paging_group = PAGIN_GROUP_UNASSIGNED;
+ return CMD_SUCCESS;
+}
+
+DEFUN(test_regex, test_regex_cmd,
+ "test regex PATTERN STRING",
+ "Test utilities\n"
+ "Regexp testing\n" "The regexp pattern\n"
+ "The string to match\n")
+{
+ regex_t reg;
+ char *str = NULL;
+
+ memset(&reg, 0, sizeof(reg));
+ if (gsm_parse_reg(_nat, &reg, &str, 1, argv) != 0)
+ return CMD_WARNING;
+
+ vty_out(vty, "String matches allow pattern: %d%s",
+ regexec(&reg, argv[1], 0, NULL, 0) == 0, VTY_NEWLINE);
+
+ talloc_free(str);
+ regfree(&reg);
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_last_endp, set_last_endp_cmd,
+ "set bsc last-used-endpoint <0-9999999999> <0-1024>",
+ "Set a value\n" "Operate on a BSC\n"
+ "Last used endpoint for an assignment\n" "BSC configuration number\n"
+ "Endpoint number used\n")
+{
+ struct bsc_connection *con;
+ int nr = atoi(argv[0]);
+ int endp = atoi(argv[1]);
+
+
+ llist_for_each_entry(con, &_nat->bsc_connections, list_entry) {
+ if (!con->cfg)
+ continue;
+ if (con->cfg->nr != nr)
+ continue;
+
+ con->last_endpoint = endp;
+ vty_out(vty, "Updated the last endpoint for %d to %d.%s",
+ con->cfg->nr, con->last_endpoint, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(block_new_conn, block_new_conn_cmd,
+ "nat-block (block|unblock)",
+ "Block the NAT for new connections\n"
+ "Block\n" "Unblock\n")
+{
+ _nat->blocked = argv[0][0] == 'b';
+ vty_out(vty, "%%Going to %s the NAT.%s",
+ _nat->blocked ? "block" : "unblock", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+/* paging group */
+DEFUN(cfg_nat_pgroup, cfg_nat_pgroup_cmd,
+ "paging-group <0-1000>",
+ "Create a Paging Group\n" "Number of the Group\n")
+{
+ int group = atoi(argv[0]);
+ struct bsc_nat_paging_group *pgroup;
+ pgroup = bsc_nat_paging_group_num(_nat, group);
+ if (!pgroup)
+ pgroup = bsc_nat_paging_group_create(_nat, group);
+ if (!pgroup) {
+ vty_out(vty, "Failed to create the group.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = pgroup;
+ vty->node = PGROUP_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_no_pgroup, cfg_nat_no_pgroup_cmd,
+ "no paging-group <0-1000>",
+ NO_STR "Delete paging-group\n" "Paging-group number\n")
+{
+ int group = atoi(argv[0]);
+ struct bsc_nat_paging_group *pgroup;
+ pgroup = bsc_nat_paging_group_num(_nat, group);
+ if (!pgroup) {
+ vty_out(vty, "No such paging group %d.%s", group, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bsc_nat_paging_group_delete(pgroup);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_pgroup_lac, cfg_pgroup_lac_cmd,
+ "location_area_code <0-65535>",
+ "Add the Location Area Code (LAC)\n" "LAC\n")
+{
+ struct bsc_nat_paging_group *pgroup = vty->index;
+
+ int lac = atoi(argv[0]);
+ if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+ vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+ lac, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bsc_nat_paging_group_add_lac(pgroup, lac);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_pgroup_no_lac, cfg_pgroup_no_lac_cmd,
+ "no location_area_code <0-65535>",
+ NO_STR "Remove the Location Area Code (LAC)\n" "LAC\n")
+{
+ int lac = atoi(argv[0]);
+ struct bsc_nat_paging_group *pgroup = vty->index;
+
+ bsc_nat_paging_group_del_lac(pgroup, lac);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ussd_connection,
+ show_ussd_connection_cmd,
+ "show ussd-connection",
+ SHOW_STR "USSD connection related information\n")
+{
+ vty_out(vty, "The USSD side channel provider is %sconnected and %sauthorized.%s",
+ _nat->ussd_con ? "" : "not ",
+ _nat->ussd_con && _nat->ussd_con->authorized? "" : "not ",
+ VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN(cfg_bsc_osmux,
+ cfg_bsc_osmux_cmd,
+ "osmux (on|off|only)",
+ OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only OSMUX\n")
+{
+ struct bsc_config *conf = vty->index;
+ int old = conf->osmux;
+
+ if (strcmp(argv[0], "on") == 0)
+ conf->osmux = OSMUX_USAGE_ON;
+ else if (strcmp(argv[0], "off") == 0)
+ conf->osmux = OSMUX_USAGE_OFF;
+ else if (strcmp(argv[0], "only") == 0)
+ conf->osmux = OSMUX_USAGE_ONLY;
+
+ if (old == 0 && conf->osmux > 0 && !conf->nat->mgcp_cfg->osmux_init) {
+ LOGP(DMGCP, LOGL_NOTICE, "Setting up OSMUX socket\n");
+ if (osmux_init(OSMUX_ROLE_BSC_NAT, conf->nat->mgcp_cfg) < 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Cannot init OSMUX\n");
+ vty_out(vty, "%% failed to create Osmux socket%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else if (old > 0 && conf->osmux == 0) {
+ LOGP(DMGCP, LOGL_NOTICE, "Disabling OSMUX socket\n");
+ /* Don't stop the socket, we may already have ongoing voice
+ * flows already using Osmux. This just switch indicates that
+ * new upcoming flows should use RTP.
+ */
+ }
+
+ return CMD_SUCCESS;
+}
+
+int bsc_nat_vty_init(struct bsc_nat *nat)
+{
+ _nat = nat;
+
+ /* show commands */
+ install_element_ve(&show_sccp_cmd);
+ install_element_ve(&show_bsc_cmd);
+ install_element_ve(&show_nat_bsc_cmd);
+ install_element_ve(&show_bsc_cfg_cmd);
+ install_element_ve(&show_stats_cmd);
+ install_element_ve(&show_stats_lac_cmd);
+ install_element_ve(&close_bsc_cmd);
+ install_element_ve(&show_msc_cmd);
+ install_element_ve(&test_regex_cmd);
+ install_element_ve(&show_bsc_mgcp_cmd);
+ install_element_ve(&show_bscs_cmd);
+ install_element_ve(&show_bar_lst_cmd);
+ install_element_ve(&show_prefix_tree_cmd);
+ install_element_ve(&show_ussd_connection_cmd);
+
+ install_element(ENABLE_NODE, &set_last_endp_cmd);
+ install_element(ENABLE_NODE, &block_new_conn_cmd);
+
+ /* nat group */
+ install_element(CONFIG_NODE, &cfg_nat_cmd);
+ install_node(&nat_node, config_write_nat);
+ vty_install_default(NAT_NODE);
+ install_element(NAT_NODE, &cfg_nat_msc_ip_cmd);
+ install_element(NAT_NODE, &cfg_nat_msc_port_cmd);
+ install_element(NAT_NODE, &cfg_nat_auth_time_cmd);
+ install_element(NAT_NODE, &cfg_nat_ping_time_cmd);
+ install_element(NAT_NODE, &cfg_nat_pong_time_cmd);
+ install_element(NAT_NODE, &cfg_nat_token_cmd);
+ install_element(NAT_NODE, &cfg_nat_bsc_ip_dscp_cmd);
+ 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_include_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);
+ install_element(NAT_NODE, &cfg_nat_ussd_local_cmd);
+ install_element(NAT_NODE, &cfg_nat_use_ipa_for_mgcp_cmd);
+
+ bsc_msg_lst_vty_init(nat, &nat->access_lists, NAT_NODE);
+
+ /* number rewriting */
+ install_element(NAT_NODE, &cfg_nat_number_rewrite_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_number_rewrite_cmd);
+ install_element(NAT_NODE, &cfg_nat_number_rewrite_post_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_number_rewrite_post_cmd);
+ install_element(NAT_NODE, &cfg_nat_smsc_addr_cmd);
+ install_element(NAT_NODE, &cfg_nat_smsc_tpdest_cmd);
+ install_element(NAT_NODE, &cfg_nat_sms_clear_tpsrr_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_sms_clear_tpsrr_cmd);
+ install_element(NAT_NODE, &cfg_nat_sms_number_rewrite_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_sms_number_rewrite_cmd);
+ install_element(NAT_NODE, &cfg_nat_prefix_trie_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_prefix_trie_cmd);
+
+ install_element(NAT_NODE, &cfg_nat_sdp_amr_mode_set_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_sdp_amr_mode_set_cmd);
+
+ install_element(NAT_NODE, &cfg_nat_pgroup_cmd);
+ install_element(NAT_NODE, &cfg_nat_no_pgroup_cmd);
+ install_node(&pgroup_node, config_write_pgroup);
+ vty_install_default(PGROUP_NODE);
+ install_element(PGROUP_NODE, &cfg_pgroup_lac_cmd);
+ install_element(PGROUP_NODE, &cfg_pgroup_no_lac_cmd);
+
+ /* BSC subgroups */
+ install_element(NAT_NODE, &cfg_bsc_cmd);
+ install_node(&bsc_node, NULL);
+ vty_install_default(NAT_BSC_NODE);
+ install_element(NAT_BSC_NODE, &cfg_bsc_token_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_auth_key_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_no_auth_key_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_lac_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_no_lac_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_paging_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_desc_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_max_endps_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_old_grp_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_paging_grp_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_no_paging_grp_cmd);
+ install_element(NAT_BSC_NODE, &cfg_bsc_osmux_cmd);
+
+ mgcp_vty_init();
+
+ return 0;
+}
+
+
+/* called by the telnet interface... we have our own init above */
+int bsc_vty_init(struct gsm_network *network)
+{
+ logging_vty_add_cmds(NULL);
+ return 0;
+}
diff --git a/src/osmo-bsc_nat/bsc_sccp.c b/src/osmo-bsc_nat/bsc_sccp.c
new file mode 100644
index 000000000..c6c265f7a
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_sccp.c
@@ -0,0 +1,247 @@
+/* SCCP patching and handling routines */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/debug.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <string.h>
+#include <time.h>
+
+static int equal(struct sccp_source_reference *ref1, struct sccp_source_reference *ref2)
+{
+ return memcmp(ref1, ref2, sizeof(*ref1)) == 0;
+}
+
+/*
+ * SCCP patching below
+ */
+
+/* check if we are using this ref for patched already */
+static int sccp_ref_is_free(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+ struct nat_sccp_connection *conn;
+
+ llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+ if (equal(ref, &conn->patched_ref))
+ return -1;
+ }
+
+ return 0;
+}
+
+/* copied from sccp.c */
+static int assign_src_local_reference(struct sccp_source_reference *ref, struct bsc_nat *nat)
+{
+ static uint32_t last_ref = 0x50000;
+ int wrapped = 0;
+
+ do {
+ struct sccp_source_reference reference;
+ reference.octet1 = (last_ref >> 0) & 0xff;
+ reference.octet2 = (last_ref >> 8) & 0xff;
+ reference.octet3 = (last_ref >> 16) & 0xff;
+
+ ++last_ref;
+ /* do not use the reversed word and wrap around */
+ if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) {
+ LOGP(DNAT, LOGL_NOTICE, "Wrapped searching for a free code\n");
+ last_ref = 0;
+ ++wrapped;
+ }
+
+ if (sccp_ref_is_free(&reference, nat) == 0) {
+ *ref = reference;
+ return 0;
+ }
+ } while (wrapped != 2);
+
+ LOGP(DNAT, LOGL_ERROR, "Finding a free reference failed\n");
+ return -1;
+}
+
+struct nat_sccp_connection *create_sccp_src_ref(struct bsc_connection *bsc,
+ struct bsc_nat_parsed *parsed)
+{
+ struct nat_sccp_connection *conn;
+
+ /* Some commercial BSCs like to reassign there SRC ref */
+ llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+ if (conn->bsc != bsc)
+ continue;
+ if (!equal(parsed->src_local_ref, &conn->real_ref))
+ continue;
+
+ /* the BSC has reassigned the SRC ref and we failed to keep track */
+ memset(&conn->remote_ref, 0, sizeof(conn->remote_ref));
+ if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+ LOGP(DNAT, LOGL_ERROR, "BSC %d reused src ref: %d and we failed to generate a new id.\n",
+ bsc->cfg->nr, sccp_src_ref_to_int(parsed->src_local_ref));
+ bsc_mgcp_dlcx(conn);
+ llist_del(&conn->list_entry);
+ talloc_free(conn);
+ return NULL;
+ } else {
+ clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+ bsc_mgcp_dlcx(conn);
+ return conn;
+ }
+ }
+
+
+ conn = talloc_zero(bsc->nat, struct nat_sccp_connection);
+ if (!conn) {
+ LOGP(DNAT, LOGL_ERROR, "Memory allocation failure.\n");
+ return NULL;
+ }
+
+ conn->bsc = bsc;
+ clock_gettime(CLOCK_MONOTONIC, &conn->creation_time);
+ conn->real_ref = *parsed->src_local_ref;
+ if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to assign a ref.\n");
+ talloc_free(conn);
+ return NULL;
+ }
+
+ bsc_mgcp_init(conn);
+ llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections);
+ rate_ctr_inc(&bsc->cfg->stats.ctrg->ctr[BCFG_CTR_SCCP_CONN]);
+ osmo_counter_inc(bsc->cfg->nat->stats.sccp.conn);
+
+ LOGP(DNAT, LOGL_DEBUG, "Created 0x%x <-> 0x%x mapping for con %p\n",
+ sccp_src_ref_to_int(&conn->real_ref),
+ sccp_src_ref_to_int(&conn->patched_ref), bsc);
+
+ return conn;
+}
+
+int update_sccp_src_ref(struct nat_sccp_connection *sccp, struct bsc_nat_parsed *parsed)
+{
+ if (!parsed->dest_local_ref || !parsed->src_local_ref) {
+ LOGP(DNAT, LOGL_ERROR, "CC MSG should contain both local and dest address.\n");
+ return -1;
+ }
+
+ sccp->remote_ref = *parsed->src_local_ref;
+ sccp->has_remote_ref = 1;
+ LOGP(DNAT, LOGL_DEBUG, "Updating 0x%x to remote 0x%x on %p\n",
+ sccp_src_ref_to_int(&sccp->patched_ref),
+ sccp_src_ref_to_int(&sccp->remote_ref), sccp->bsc);
+
+ return 0;
+}
+
+void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed)
+{
+ struct nat_sccp_connection *conn;
+
+ llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+ if (equal(parsed->src_local_ref, &conn->patched_ref)) {
+ sccp_connection_destroy(conn);
+ return;
+ }
+ }
+
+ LOGP(DNAT, LOGL_ERROR, "Can not remove connection: 0x%x\n",
+ sccp_src_ref_to_int(parsed->src_local_ref));
+}
+
+/*
+ * We have a message from the MSC to the BSC. The MSC is using
+ * an address that was assigned by the MUX, we need to update the
+ * dest reference to the real network.
+ */
+struct nat_sccp_connection *patch_sccp_src_ref_to_bsc(struct msgb *msg,
+ struct bsc_nat_parsed *parsed,
+ struct bsc_nat *nat)
+{
+ struct nat_sccp_connection *conn;
+
+ if (!parsed->dest_local_ref) {
+ LOGP(DNAT, LOGL_ERROR, "MSG should contain dest_local_ref.\n");
+ return NULL;
+ }
+
+
+ llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+ if (!equal(parsed->dest_local_ref, &conn->patched_ref))
+ continue;
+
+ /* Change the dest address to the real one */
+ *parsed->dest_local_ref = conn->real_ref;
+ return conn;
+ }
+
+ return NULL;
+}
+
+/*
+ * These are message to the MSC. We will need to find the BSC
+ * Connection by either the SRC or the DST local reference.
+ *
+ * In case of a CR we need to work by the SRC local reference
+ * in all other cases we need to work by the destination local
+ * reference..
+ */
+struct nat_sccp_connection *patch_sccp_src_ref_to_msc(struct msgb *msg,
+ struct bsc_nat_parsed *parsed,
+ struct bsc_connection *bsc)
+{
+ struct nat_sccp_connection *conn;
+
+ llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) {
+ if (conn->bsc != bsc)
+ continue;
+
+ if (parsed->src_local_ref) {
+ if (equal(parsed->src_local_ref, &conn->real_ref)) {
+ *parsed->src_local_ref = conn->patched_ref;
+ return conn;
+ }
+ } else if (parsed->dest_local_ref) {
+ if (equal(parsed->dest_local_ref, &conn->remote_ref))
+ return conn;
+ } else {
+ LOGP(DNAT, LOGL_ERROR, "Header has neither loc/dst ref.\n");
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+struct nat_sccp_connection *bsc_nat_find_con_by_bsc(struct bsc_nat *nat,
+ struct sccp_source_reference *ref)
+{
+ struct nat_sccp_connection *conn;
+
+ llist_for_each_entry(conn, &nat->sccp_connections, list_entry) {
+ if (equal(ref, &conn->real_ref))
+ return conn;
+ }
+
+ return NULL;
+}
diff --git a/src/osmo-bsc_nat/bsc_ussd.c b/src/osmo-bsc_nat/bsc_ussd.c
new file mode 100644
index 000000000..0ba63270d
--- /dev/null
+++ b/src/osmo-bsc_nat/bsc_ussd.c
@@ -0,0 +1,453 @@
+/* USSD Filter Code */
+
+/*
+ * (C) 2010-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2011 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/bsc_msg_filter.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0480.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <sys/socket.h>
+#include <string.h>
+#include <unistd.h>
+
+#define USSD_LAC_IE 0
+#define USSD_CI_IE 1
+
+static void ussd_auth_con(struct tlv_parsed *, struct bsc_nat_ussd_con *);
+
+static struct bsc_nat_ussd_con *bsc_nat_ussd_alloc(struct bsc_nat *nat)
+{
+ struct bsc_nat_ussd_con *con;
+
+ con = talloc_zero(nat, struct bsc_nat_ussd_con);
+ if (!con)
+ return NULL;
+
+ con->nat = nat;
+ return con;
+}
+
+static void bsc_nat_ussd_destroy(struct bsc_nat_ussd_con *con)
+{
+ if (con->nat->ussd_con == con) {
+ bsc_ussd_close_connections(con->nat);
+ con->nat->ussd_con = NULL;
+ }
+
+ close(con->queue.bfd.fd);
+ osmo_fd_unregister(&con->queue.bfd);
+ osmo_timer_del(&con->auth_timeout);
+ osmo_wqueue_clear(&con->queue);
+
+ msgb_free(con->pending_msg);
+ talloc_free(con);
+}
+
+static void ussd_pong(struct bsc_nat_ussd_con *conn)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(4096, 128, "pong message");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate pong msg\n");
+ return;
+ }
+
+ msgb_v_put(msg, IPAC_MSGT_PONG);
+ bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int forward_sccp(struct bsc_nat *nat, struct msgb *msg)
+{
+ struct nat_sccp_connection *con;
+ struct bsc_nat_parsed *parsed;
+
+
+ parsed = bsc_nat_parse(msg);
+ if (!parsed) {
+ LOGP(DNAT, LOGL_ERROR, "Can not parse msg from USSD.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ if (!parsed->dest_local_ref) {
+ LOGP(DNAT, LOGL_ERROR, "No destination local reference.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ con = bsc_nat_find_con_by_bsc(nat, parsed->dest_local_ref);
+ if (!con || !con->bsc) {
+ LOGP(DNAT, LOGL_ERROR, "No active connection found.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ talloc_free(parsed);
+ bsc_write_msg(&con->bsc->write_queue, msg);
+ return 0;
+}
+
+static int ussd_read_cb(struct osmo_fd *bfd)
+{
+ struct bsc_nat_ussd_con *conn = bfd->data;
+ struct msgb *msg = NULL;
+ struct ipaccess_head *hh;
+ int ret;
+
+ ret = ipa_msg_recv_buffered(bfd->fd, &msg, &conn->pending_msg);
+ if (ret <= 0) {
+ if (ret == -EAGAIN)
+ return 0;
+ LOGP(DNAT, LOGL_ERROR, "USSD Connection was lost.\n");
+ bsc_nat_ussd_destroy(conn);
+ return -1;
+ }
+
+ LOGP(DNAT, LOGL_NOTICE, "MSG from USSD: %s proto: %d\n",
+ osmo_hexdump(msg->data, msg->len), msg->l2h[0]);
+ hh = (struct ipaccess_head *) msg->data;
+
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ if (msg->l2h[0] == IPAC_MSGT_ID_RESP) {
+ struct tlv_parsed tvp;
+ int ret;
+ ret = ipa_ccm_idtag_parse(&tvp,
+ (unsigned char *) msg->l2h + 2,
+ msgb_l2len(msg) - 2);
+ if (ret < 0) {
+ LOGP(DNAT, LOGL_ERROR, "ignoring IPA response "
+ "message with malformed TLVs\n");
+ msgb_free(msg);
+ return ret;
+ }
+ if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+ ussd_auth_con(&tvp, conn);
+ } else if (msg->l2h[0] == IPAC_MSGT_PING) {
+ LOGP(DNAT, LOGL_DEBUG, "Got USSD ping request.\n");
+ ussd_pong(conn);
+ } else {
+ LOGP(DNAT, LOGL_NOTICE, "Got unknown IPACCESS message 0x%02x.\n", msg->l2h[0]);
+ }
+
+ msgb_free(msg);
+ } else if (hh->proto == IPAC_PROTO_SCCP) {
+ forward_sccp(conn->nat, msg);
+ } else {
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+static void ussd_auth_cb(void *_data)
+{
+ LOGP(DNAT, LOGL_ERROR, "USSD module didn't authenticate\n");
+ bsc_nat_ussd_destroy((struct bsc_nat_ussd_con *) _data);
+}
+
+static void ussd_auth_con(struct tlv_parsed *tvp, struct bsc_nat_ussd_con *conn)
+{
+ const char *token;
+ int len;
+ if (!conn->nat->ussd_token) {
+ LOGP(DNAT, LOGL_ERROR, "No USSD token set. Closing\n");
+ bsc_nat_ussd_destroy(conn);
+ return;
+ }
+
+ token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+ len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+
+ /* last byte should be a NULL */
+ if (strlen(conn->nat->ussd_token) != len - 1)
+ goto disconnect;
+ /* compare everything including the null byte */
+ if (memcmp(conn->nat->ussd_token, token, len) != 0)
+ goto disconnect;
+
+ /* it is authenticated now */
+ if (conn->nat->ussd_con && conn->nat->ussd_con != conn)
+ bsc_nat_ussd_destroy(conn->nat->ussd_con);
+
+ LOGP(DNAT, LOGL_ERROR, "USSD token specified. USSD provider is connected.\n");
+ osmo_timer_del(&conn->auth_timeout);
+ conn->authorized = 1;
+ conn->nat->ussd_con = conn;
+ return;
+
+disconnect:
+ LOGP(DNAT, LOGL_ERROR, "Wrong USSD token by client: %d\n",
+ conn->queue.bfd.fd);
+ bsc_nat_ussd_destroy(conn);
+}
+
+static void ussd_start_auth(struct bsc_nat_ussd_con *conn)
+{
+ struct msgb *msg;
+
+ osmo_timer_setup(&conn->auth_timeout, ussd_auth_cb, conn);
+ osmo_timer_schedule(&conn->auth_timeout, conn->nat->auth_timeout, 0);
+
+ msg = msgb_alloc_headroom(4096, 128, "auth message");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate auth msg\n");
+ return;
+ }
+
+ msgb_v_put(msg, IPAC_MSGT_ID_GET);
+ bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int ussd_listen_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ struct bsc_nat_ussd_con *conn;
+ struct bsc_nat *nat;
+ struct sockaddr_in sa;
+ socklen_t sa_len = sizeof(sa);
+ int fd;
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+ if (fd < 0) {
+ perror("accept");
+ return fd;
+ }
+
+ nat = (struct bsc_nat *) bfd->data;
+ osmo_counter_inc(nat->stats.ussd.reconn);
+
+ conn = bsc_nat_ussd_alloc(nat);
+ if (!conn) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to allocate USSD con struct.\n");
+ close(fd);
+ return -1;
+ }
+
+ osmo_wqueue_init(&conn->queue, 10);
+ conn->queue.bfd.data = conn;
+ conn->queue.bfd.fd = fd;
+ conn->queue.bfd.when = BSC_FD_READ;
+ conn->queue.read_cb = ussd_read_cb;
+ conn->queue.write_cb = bsc_write_cb;
+
+ if (osmo_fd_register(&conn->queue.bfd) < 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to register USSD fd.\n");
+ bsc_nat_ussd_destroy(conn);
+ return -1;
+ }
+
+ LOGP(DNAT, LOGL_NOTICE, "USSD Connection on %d with IP: %s\n",
+ fd, inet_ntoa(sa.sin_addr));
+
+ /* do authentication */
+ ussd_start_auth(conn);
+ return 0;
+}
+
+int bsc_ussd_init(struct bsc_nat *nat)
+{
+ struct in_addr addr;
+
+ addr.s_addr = INADDR_ANY;
+ if (nat->ussd_local)
+ inet_aton(nat->ussd_local, &addr);
+
+ nat->ussd_listen.data = nat;
+ return make_sock(&nat->ussd_listen, IPPROTO_TCP,
+ ntohl(addr.s_addr), 5001, 0, ussd_listen_cb, nat);
+}
+
+static int forward_ussd_simple(struct nat_sccp_connection *con, struct msgb *input)
+{
+ struct msgb *copy;
+ struct bsc_nat_ussd_con *ussd;
+
+ if (!con->bsc->nat->ussd_con)
+ return -1;
+
+ copy = msgb_alloc_headroom(4096, 128, "forward bts");
+ if (!copy) {
+ LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+ return -1;
+ }
+
+ /* copy the data into the copy */
+ copy->l2h = msgb_put(copy, msgb_l2len(input));
+ memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+ /* send it out */
+ ussd = con->bsc->nat->ussd_con;
+ bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+ return 0;
+}
+
+static int forward_ussd(struct nat_sccp_connection *con, const struct ussd_request *req,
+ struct msgb *input)
+{
+ struct msgb *msg, *copy;
+ struct ipac_msgt_sccp_state *state;
+ struct bsc_nat_ussd_con *ussd;
+ uint16_t lac, ci;
+
+ if (!con->bsc->nat->ussd_con)
+ return -1;
+
+ msg = msgb_alloc_headroom(4096, 128, "forward ussd");
+ if (!msg) {
+ LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+ return -1;
+ }
+
+ copy = msgb_alloc_headroom(4096, 128, "forward bts");
+ if (!copy) {
+ LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ copy->l2h = msgb_put(copy, msgb_l2len(input));
+ memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+ msg->l2h = msgb_put(msg, 1);
+ msg->l2h[0] = IPAC_MSGT_SCCP_OLD;
+
+ /* fill out the data */
+ state = (struct ipac_msgt_sccp_state *) msgb_put(msg, sizeof(*state));
+ state->trans_id = req->transaction_id;
+ state->invoke_id = req->invoke_id;
+ memcpy(&state->src_ref, &con->remote_ref, sizeof(con->remote_ref));
+ memcpy(&state->dst_ref, &con->real_ref, sizeof(con->real_ref));
+ memcpy(state->imsi, con->filter_state.imsi, strlen(con->filter_state.imsi));
+
+ /* add additional tag/values */
+ lac = htons(con->lac);
+ ci = htons(con->ci);
+ msgb_tv_fixed_put(msg, USSD_LAC_IE, sizeof(lac), (const uint8_t *) &lac);
+ msgb_tv_fixed_put(msg, USSD_CI_IE, sizeof(ci), (const uint8_t *) &ci);
+
+ ussd = con->bsc->nat->ussd_con;
+ bsc_do_write(&ussd->queue, msg, IPAC_PROTO_IPACCESS);
+ bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+
+ return 0;
+}
+
+int bsc_ussd_check(struct nat_sccp_connection *con, struct bsc_nat_parsed *parsed,
+ struct msgb *msg)
+{
+ uint32_t len;
+ uint8_t msg_type;
+ uint8_t proto;
+ uint8_t ti;
+ struct gsm48_hdr *hdr48;
+ struct bsc_msg_acc_lst *lst;
+ struct ussd_request req;
+
+ /*
+ * various checks to avoid the decoding work. Right now we only want to
+ * decode if the connection was created for USSD, we do have a USSD access
+ * list, a query, a IMSI and such...
+ */
+ if (con->filter_state.con_type != FLT_CON_TYPE_SSA)
+ return 0;
+
+ if (!con->filter_state.imsi)
+ return 0;
+
+ /* We have not verified the IMSI yet */
+ if (!con->authorized)
+ return 0;
+
+ if (!con->bsc->nat->ussd_lst_name)
+ return 0;
+ if (!con->bsc->nat->ussd_query)
+ return 0;
+
+ if (parsed->bssap != BSSAP_MSG_DTAP)
+ return 0;
+
+ if (strlen(con->filter_state.imsi) > GSM23003_IMSI_MAX_DIGITS)
+ return 0;
+
+ hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+ if (!hdr48)
+ return 0;
+
+ proto = gsm48_hdr_pdisc(hdr48);
+ msg_type = gsm48_hdr_msg_type(hdr48);
+ ti = gsm48_hdr_trans_id_no_ti(hdr48);
+ if (proto != GSM48_PDISC_NC_SS)
+ return 0;
+
+ if (msg_type == GSM0480_MTYPE_REGISTER) {
+
+ /* now check if it is a IMSI we care about */
+ lst = bsc_msg_acc_lst_find(&con->bsc->nat->access_lists,
+ con->bsc->nat->ussd_lst_name);
+ if (!lst)
+ return 0;
+
+ if (bsc_msg_acc_lst_check_allow(lst, con->filter_state.imsi) != 0)
+ return 0;
+
+ /* now decode the message and see if we really want to handle it */
+ memset(&req, 0, sizeof(req));
+ if (gsm0480_decode_ussd_request(hdr48, len, &req) != 1)
+ return 0;
+ if (req.text[0] == 0xff)
+ return 0;
+
+ if (regexec(&con->bsc->nat->ussd_query_re,
+ req.text, 0, NULL, 0) == REG_NOMATCH)
+ return 0;
+
+ /* found a USSD query for our subscriber */
+ LOGP(DNAT, LOGL_NOTICE, "Found USSD query for %s\n",
+ con->filter_state.imsi);
+ con->ussd_ti[ti] = 1;
+ if (forward_ussd(con, &req, msg) != 0)
+ return 0;
+ return 1;
+ } else if (msg_type == GSM0480_MTYPE_FACILITY && con->ussd_ti[ti]) {
+ LOGP(DNAT, LOGL_NOTICE, "Forwarding message part of TI: %d %s\n",
+ ti, con->filter_state.imsi);
+ if (forward_ussd_simple(con, msg) != 0)
+ return 0;
+ return 1;
+ }
+
+ return 0;
+}