aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc')
-rw-r--r--src/osmo-bsc/Makefile.am55
-rw-r--r--src/osmo-bsc/osmo_bsc_api.c550
-rw-r--r--src/osmo-bsc/osmo_bsc_audio.c73
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c548
-rw-r--r--src/osmo-bsc/osmo_bsc_ctrl.c680
-rw-r--r--src/osmo-bsc/osmo_bsc_filter.c381
-rw-r--r--src/osmo-bsc/osmo_bsc_grace.c169
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c301
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c586
-rw-r--r--src/osmo-bsc/osmo_bsc_sccp.c328
-rw-r--r--src/osmo-bsc/osmo_bsc_vty.c945
11 files changed, 4616 insertions, 0 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
new file mode 100644
index 000000000..ae9410c9d
--- /dev/null
+++ b/src/osmo-bsc/Makefile.am
@@ -0,0 +1,55 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBOSMOSCCP_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-bsc \
+ $(NULL)
+
+osmo_bsc_SOURCES = \
+ osmo_bsc_main.c \
+ osmo_bsc_vty.c \
+ osmo_bsc_api.c \
+ osmo_bsc_grace.c \
+ osmo_bsc_msc.c \
+ osmo_bsc_sccp.c \
+ osmo_bsc_filter.c \
+ osmo_bsc_bssap.c \
+ osmo_bsc_audio.c \
+ osmo_bsc_ctrl.c \
+ $(NULL)
+
+# once again since TRAU uses CC symbol :(
+osmo_bsc_LDADD = \
+ $(top_builddir)/src/libfilter/libfilter.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+ $(top_builddir)/src/libmsc/libmsc.a \
+ $(top_builddir)/src/libtrau/libtrau.a \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(LIBOSMOSCCP_LIBS) \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(NULL)
diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c
new file mode 100644
index 000000000..bac5e4717
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_api.c
@@ -0,0 +1,550 @@
+/* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/debug.h>
+
+#include <openbsc/gsm_04_80.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#define return_when_not_connected(conn) \
+ if (!conn->sccp_con) {\
+ LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+ return; \
+ }
+
+#define return_when_not_connected_val(conn, ret) \
+ if (!conn->sccp_con) {\
+ LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \
+ return ret; \
+ }
+
+#define queue_msg_or_return(resp) \
+ if (!resp) { \
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \
+ return; \
+ } \
+ bsc_queue_for_msc(conn->sccp_con, resp);
+
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause);
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_msc_data *msc);
+
+static uint16_t get_network_code_for_msc(struct bsc_msc_data *msc)
+{
+ if (msc->core_mnc != -1)
+ return msc->core_mnc;
+ return msc->network->network_code;
+}
+
+static uint16_t get_country_code_for_msc(struct bsc_msc_data *msc)
+{
+ if (msc->core_mcc != -1)
+ return msc->core_mcc;
+ return msc->network->country_code;
+}
+
+static uint16_t get_lac_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ if (msc->core_lac != -1)
+ return msc->core_lac;
+ return bts->location_area_code;
+}
+
+static uint16_t get_ci_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ if (msc->core_ci != -1)
+ return msc->core_ci;
+ return bts->cell_identity;
+}
+
+static void bsc_maybe_lu_reject(struct gsm_subscriber_connection *conn, int con_type, int cause)
+{
+ struct msgb *msg;
+
+ /* ignore cm service request or such */
+ if (con_type != FLT_CON_TYPE_LU)
+ return;
+
+ msg = gsm48_create_loc_upd_rej(cause);
+ if (!msg) {
+ LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+ return;
+ }
+
+ msg->lchan = conn->lchan;
+ gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+static int bsc_filter_initial(struct osmo_bsc_data *bsc,
+ struct bsc_msc_data *msc,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg, char **imsi, int *con_type,
+ int *lu_cause)
+{
+ struct bsc_filter_request req;
+ struct bsc_filter_reject_cause cause;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ int rc;
+
+ req.ctx = conn;
+ req.black_list = NULL;
+ req.access_lists = bsc_access_lists();
+ req.local_lst_name = msc->acc_lst_name;
+ req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name;
+ req.bsc_nr = 0;
+
+ rc = bsc_msg_filter_initial(gh, msgb_l3len(msg), &req,
+ con_type, imsi, &cause);
+ *lu_cause = cause.lu_reject_cause;
+ return rc;
+}
+
+static int bsc_filter_data(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int *lu_cause)
+{
+ struct bsc_filter_request req;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct bsc_filter_reject_cause cause;
+ int rc;
+
+ req.ctx = conn;
+ req.black_list = NULL;
+ req.access_lists = bsc_access_lists();
+ req.local_lst_name = conn->sccp_con->msc->acc_lst_name;
+ req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name;
+ req.bsc_nr = 0;
+
+ rc = bsc_msg_filter_data(gh, msgb_l3len(msg), &req,
+ &conn->sccp_con->filter_state,
+ &cause);
+ *lu_cause = cause.lu_reject_cause;
+ return rc;
+}
+
+static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci);
+
+ resp = gsm0808_create_sapi_reject(dlci);
+ queue_msg_or_return(resp);
+}
+
+static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, uint8_t chosen_encr)
+{
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
+ resp = gsm0808_create_cipher_complete(msg, chosen_encr);
+ queue_msg_or_return(resp);
+}
+
+static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, const char *text)
+{
+ struct gsm48_hdr *gh;
+ int8_t pdisc;
+ uint8_t mtype;
+ int drop_message = 1;
+
+ if (!text)
+ return;
+
+ if (!msg || msgb_l3len(msg) < sizeof(*gh))
+ return;
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
+
+ /* Is CM service request? */
+ if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+ struct gsm48_service_request *cm;
+
+ cm = (struct gsm48_service_request *) &gh->data[0];
+
+ /* Is type SMS or call? */
+ if (cm->cm_service_type == GSM48_CMSERV_SMS)
+ drop_message = 0;
+ else if (cm->cm_service_type == GSM48_CMSERV_MO_CALL_PACKET)
+ drop_message = 0;
+ }
+
+ if (drop_message) {
+ LOGP(DMSC, LOGL_DEBUG, "Skipping (not sending) USSD message: '%s'\n", text);
+ return;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Sending CM Service Accept\n");
+ gsm48_tx_mm_serv_ack(conn);
+
+ LOGP(DMSC, LOGL_INFO, "Sending USSD message: '%s'\n", text);
+ bsc_send_ussd_notify(conn, 1, text);
+ bsc_send_ussd_release_complete(conn);
+}
+
+/*
+ * Instruct to reserve data for a new connectiom, create the complete
+ * layer three message, send it to open the connection.
+ */
+static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+ uint16_t chosen_channel)
+{
+ struct bsc_msc_data *msc;
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n");
+
+ /* find the MSC link we want to use */
+ msc = bsc_find_msc(conn, msg);
+ if (!msc) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to find a MSC for a connection.\n");
+ bsc_send_ussd_no_srv(conn, msg,
+ conn->bts->network->bsc_data->ussd_no_msc_txt);
+ return -1;
+ }
+
+ return complete_layer3(conn, msg, msc);
+}
+
+static int complete_layer3(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_msc_data *msc)
+{
+ int con_type, rc, lu_cause;
+ char *imsi = NULL;
+ struct timeval tv;
+ struct msgb *resp;
+ uint16_t network_code;
+ uint16_t country_code;
+ uint16_t lac;
+ uint16_t ci;
+ enum bsc_con ret;
+ int send_ping = msc->advanced_ping;
+
+ /* Advanced ping/pong handling */
+ if (osmo_timer_pending(&msc->pong_timer))
+ send_ping = 0;
+ if (msc->ping_timeout <= 0)
+ send_ping = 0;
+ if (send_ping && osmo_timer_remaining(&msc->ping_timer, NULL, &tv) == -1)
+ send_ping = 0;
+
+ /* Check the filter */
+ rc = bsc_filter_initial(msc->network->bsc_data, msc, conn, msg,
+ &imsi, &con_type, &lu_cause);
+ if (rc < 0) {
+ bsc_maybe_lu_reject(conn, con_type, lu_cause);
+ return BSC_API_CONN_POL_REJECT;
+ }
+
+ /* allocate resource for a new connection */
+ ret = bsc_create_new_connection(conn, msc, send_ping);
+
+ if (ret != BSC_CON_SUCCESS) {
+ /* allocation has failed */
+ if (ret == BSC_CON_REJECT_NO_LINK)
+ bsc_send_ussd_no_srv(conn, msg, msc->ussd_msc_lost_txt);
+ else if (ret == BSC_CON_REJECT_RF_GRACE)
+ bsc_send_ussd_no_srv(conn, msg, msc->ussd_grace_txt);
+
+ return BSC_API_CONN_POL_REJECT;
+ }
+
+ if (imsi)
+ conn->sccp_con->filter_state.imsi = talloc_steal(conn, imsi);
+ conn->sccp_con->filter_state.con_type = con_type;
+
+ /* check return value, if failed check msg for and send USSD */
+
+ network_code = get_network_code_for_msc(conn->sccp_con->msc);
+ country_code = get_country_code_for_msc(conn->sccp_con->msc);
+ lac = get_lac_for_msc(conn->sccp_con->msc, conn->bts);
+ ci = get_ci_for_msc(conn->sccp_con->msc, conn->bts);
+
+ bsc_scan_bts_msg(conn, msg);
+
+ resp = gsm0808_create_layer3(msg, network_code, country_code, lac, ci);
+ if (!resp) {
+ LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
+ sccp_connection_free(conn->sccp_con->sccp);
+ bsc_delete_connection(conn->sccp_con);
+ return BSC_API_CONN_POL_REJECT;
+ }
+
+ if (bsc_open_connection(conn->sccp_con, resp) != 0) {
+ sccp_connection_free(conn->sccp_con->sccp);
+ bsc_delete_connection(conn->sccp_con);
+ msgb_free(resp);
+ return BSC_API_CONN_POL_REJECT;
+ }
+
+ return BSC_API_CONN_POL_ACCEPT;
+}
+
+/*
+ * Plastic surgery... we want to give up the current connection
+ */
+static int move_to_msc(struct gsm_subscriber_connection *_conn,
+ struct msgb *msg, struct bsc_msc_data *msc)
+{
+ struct osmo_bsc_sccp_con *old_con = _conn->sccp_con;
+
+ /*
+ * 1. Give up the old connection.
+ * This happens by sending a clear request to the MSC,
+ * it should end with the MSC releasing the connection.
+ */
+ old_con->conn = NULL;
+ bsc_clear_request(_conn, 0);
+
+ /*
+ * 2. Attempt to create a new connection to the local
+ * MSC. If it fails the caller will need to handle this
+ * properly.
+ */
+ _conn->sccp_con = NULL;
+ if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) {
+ gsm0808_clear(_conn);
+ bsc_subscr_con_free(_conn);
+ return 1;
+ }
+
+ return 2;
+}
+
+static int handle_cc_setup(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+ struct bsc_msc_data *msc;
+ struct gsm_mncc_number called;
+ struct tlv_parsed tp;
+ unsigned payload_len;
+
+ char _dest_nr[35];
+
+ /*
+ * Do we have a setup message here? if not return fast.
+ */
+ if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP)
+ return 0;
+
+ payload_len = msgb_l3len(msg) - sizeof(*gh);
+
+ tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+ LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n");
+ return -1;
+ }
+
+ memset(&called, 0, sizeof(called));
+ gsm48_decode_called(&called,
+ TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
+
+ if (called.plan != 1 && called.plan != 0)
+ return 0;
+
+ if (called.plan == 1 && called.type == 1) {
+ _dest_nr[0] = _dest_nr[1] = '0';
+ memcpy(_dest_nr + 2, called.number, sizeof(called.number));
+ } else
+ memcpy(_dest_nr, called.number, sizeof(called.number));
+
+ /*
+ * Check if the connection should be moved...
+ */
+ llist_for_each_entry(msc, &conn->bts->network->bsc_data->mscs, entry) {
+ if (msc->type != MSC_CON_TYPE_LOCAL)
+ continue;
+ if (!msc->local_pref)
+ continue;
+ if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0)
+ continue;
+
+ return move_to_msc(conn, msg, msc);
+ }
+
+ return 0;
+}
+
+
+static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+ int lu_cause;
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
+
+ /*
+ * We might want to move this connection to a new MSC. Ask someone
+ * to handle it. If it was handled we will return.
+ */
+ if (handle_cc_setup(conn, msg) >= 1)
+ return;
+
+ /* Check the filter */
+ if (bsc_filter_data(conn, msg, &lu_cause) < 0) {
+ bsc_maybe_lu_reject(conn,
+ conn->sccp_con->filter_state.con_type,
+ lu_cause);
+ bsc_clear_request(conn, 0);
+ return;
+ }
+
+ bsc_scan_bts_msg(conn, msg);
+
+ resp = gsm0808_create_dtap(msg, link_id);
+ queue_msg_or_return(resp);
+}
+
+static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause,
+ uint8_t chosen_channel, uint8_t encr_alg_id,
+ uint8_t speech_model)
+{
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL\n");
+
+ resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel,
+ encr_alg_id, speech_model);
+ queue_msg_or_return(resp);
+}
+
+static void bsc_assign_fail(struct gsm_subscriber_connection *conn,
+ uint8_t cause, uint8_t *rr_cause)
+{
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN FAIL\n");
+
+ resp = gsm0808_create_assignment_failure(cause, rr_cause);
+ queue_msg_or_return(resp);
+}
+
+static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+ struct osmo_bsc_sccp_con *sccp;
+ struct msgb *resp;
+ return_when_not_connected_val(conn, 1);
+
+ LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n");
+
+ /*
+ * Remove the connection from BSC<->SCCP part, the SCCP part
+ * will either be cleared by channel release or MSC disconnect
+ */
+ sccp = conn->sccp_con;
+ sccp->conn = NULL;
+ conn->sccp_con = NULL;
+
+ resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n");
+ return 1;
+ }
+
+ bsc_queue_for_msc(sccp, resp);
+ return 1;
+}
+
+static void bsc_cm_update(struct gsm_subscriber_connection *conn,
+ const uint8_t *cm2, uint8_t cm2_len,
+ const uint8_t *cm3, uint8_t cm3_len)
+{
+ struct msgb *resp;
+ return_when_not_connected(conn);
+
+ resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len);
+
+ queue_msg_or_return(resp);
+}
+
+static void bsc_mr_config(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *lchan, int full_rate)
+{
+ struct bsc_msc_data *msc;
+ struct gsm48_multi_rate_conf *ms_conf, *bts_conf;
+
+ if (!conn->sccp_con) {
+ LOGP(DMSC, LOGL_ERROR,
+ "No msc data available on conn %p. Audio will be broken.\n",
+ conn);
+ return;
+ }
+
+ msc = conn->sccp_con->msc;
+
+ /* initialize the data structure */
+ lchan->mr_ms_lv[0] = sizeof(*ms_conf);
+ lchan->mr_bts_lv[0] = sizeof(*bts_conf);
+ ms_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_ms_lv[1];
+ bts_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_bts_lv[1];
+ memset(ms_conf, 0, sizeof(*ms_conf));
+ memset(bts_conf, 0, sizeof(*bts_conf));
+
+ bts_conf->ver = ms_conf->ver = 1;
+ bts_conf->icmi = ms_conf->icmi = 1;
+
+ /* maybe gcc see's it is copy of _one_ byte */
+ bts_conf->m4_75 = ms_conf->m4_75 = msc->amr_conf.m4_75;
+ bts_conf->m5_15 = ms_conf->m5_15 = msc->amr_conf.m5_15;
+ bts_conf->m5_90 = ms_conf->m5_90 = msc->amr_conf.m5_90;
+ bts_conf->m6_70 = ms_conf->m6_70 = msc->amr_conf.m6_70;
+ bts_conf->m7_40 = ms_conf->m7_40 = msc->amr_conf.m7_40;
+ bts_conf->m7_95 = ms_conf->m7_95 = msc->amr_conf.m7_95;
+ if (full_rate) {
+ bts_conf->m10_2 = ms_conf->m10_2 = msc->amr_conf.m10_2;
+ bts_conf->m12_2 = ms_conf->m12_2 = msc->amr_conf.m12_2;
+ }
+
+ /* now copy this into the bts structure */
+ memcpy(lchan->mr_bts_lv, lchan->mr_ms_lv, sizeof(lchan->mr_ms_lv));
+}
+
+static struct bsc_api bsc_handler = {
+ .sapi_n_reject = bsc_sapi_n_reject,
+ .cipher_mode_compl = bsc_cipher_mode_compl,
+ .compl_l3 = bsc_compl_l3,
+ .dtap = bsc_dtap,
+ .assign_compl = bsc_assign_compl,
+ .assign_fail = bsc_assign_fail,
+ .clear_request = bsc_clear_request,
+ .classmark_chg = bsc_cm_update,
+ .mr_config = bsc_mr_config,
+};
+
+struct bsc_api *osmo_bsc_api()
+{
+ return &bsc_handler;
+}
diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c
new file mode 100644
index 000000000..116020900
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_audio.c
@@ -0,0 +1,73 @@
+/*
+ * ipaccess audio handling
+ *
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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_msc_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+
+#include <arpa/inet.h>
+
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_subscriber_connection *con;
+ struct gsm_lchan *lchan = signal_data;
+ int rc;
+
+ if (subsys != SS_ABISIP)
+ return 0;
+
+ con = lchan->conn;
+ if (!con || !con->sccp_con)
+ return 0;
+
+ switch (signal) {
+ case S_ABISIP_CRCX_ACK:
+ /*
+ * TODO: handle handover here... then the audio should go to
+ * the old mgcp port..
+ */
+ /* we can ask it to connect now */
+ LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n",
+ con->sccp_con->rtp_port, lchan->abis_ip.conn_id);
+
+ rc = rsl_ipacc_mdcx(lchan, ntohl(INADDR_ANY),
+ con->sccp_con->rtp_port,
+ lchan->abis_ip.rtp_payload2);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to send MDCX: %d\n", rc);
+ return rc;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+int osmo_bsc_audio_init(struct gsm_network *net)
+{
+ osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, net);
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
new file mode 100644
index 000000000..100f66441
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -0,0 +1,548 @@
+/* GSM 08.08 BSSMAP handling */
+/* (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/mgcp.h>
+#include <openbsc/paging.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm0808.h>
+
+/*
+ * helpers for the assignment command
+ */
+enum gsm0808_permitted_speech audio_support_to_gsm88(struct gsm_audio_support *audio)
+{
+ if (audio->hr) {
+ switch (audio->ver) {
+ case 1:
+ return GSM0808_PERM_HR1;
+ break;
+ case 2:
+ return GSM0808_PERM_HR2;
+ break;
+ case 3:
+ return GSM0808_PERM_HR3;
+ break;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+ return GSM0808_PERM_FR1;
+ }
+ } else {
+ switch (audio->ver) {
+ case 1:
+ return GSM0808_PERM_FR1;
+ break;
+ case 2:
+ return GSM0808_PERM_FR2;
+ break;
+ case 3:
+ return GSM0808_PERM_FR3;
+ break;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver);
+ return GSM0808_PERM_HR1;
+ }
+ }
+}
+
+enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech)
+{
+ switch (speech) {
+ case GSM0808_PERM_HR1:
+ case GSM0808_PERM_FR1:
+ return GSM48_CMODE_SPEECH_V1;
+ break;
+ case GSM0808_PERM_HR2:
+ case GSM0808_PERM_FR2:
+ return GSM48_CMODE_SPEECH_EFR;
+ break;
+ case GSM0808_PERM_HR3:
+ case GSM0808_PERM_FR3:
+ return GSM48_CMODE_SPEECH_AMR;
+ break;
+ }
+
+ LOGP(DMSC, LOGL_FATAL, "Should not be reached.\n");
+ return GSM48_CMODE_SPEECH_AMR;
+}
+
+static int bssmap_handle_reset_ack(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ LOGP(DMSC, LOGL_NOTICE, "Reset ACK from MSC\n");
+ return 0;
+}
+
+/* GSM 08.08 § 3.2.1.19 */
+static int bssmap_handle_paging(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int payload_length)
+{
+ struct bsc_subscr *subscr;
+ struct tlv_parsed tp;
+ char mi_string[GSM48_MI_SIZE];
+ uint32_t tmsi = GSM_RESERVED_TMSI;
+ unsigned int lac = GSM_LAC_RESERVED_ALL_BTS;
+ uint8_t data_length;
+ const uint8_t *data;
+ uint8_t chan_needed = RSL_CHANNEED_ANY;
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory IMSI not present.\n");
+ return -1;
+ } else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) {
+ LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n");
+ return -1;
+ }
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory CELL IDENTIFIER LIST not present.\n");
+ return -1;
+ }
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI) &&
+ TLVP_LEN(&tp, GSM0808_IE_TMSI) == 4) {
+ tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI));
+ }
+
+ /*
+ * parse the IMSI
+ */
+ gsm48_mi_to_string(mi_string, sizeof(mi_string),
+ TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI));
+
+ /*
+ * parse the cell identifier list
+ */
+ data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+ data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
+
+ /*
+ * Support paging to all network or one BTS at one LAC
+ */
+ if (data_length == 3 && data[0] == CELL_IDENT_LAC) {
+ lac = osmo_load16be(&data[1]);
+ } else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) {
+ LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", osmo_hexdump(data, data_length));
+ return -1;
+ }
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1)
+ chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) {
+ LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n");
+ }
+
+ subscr = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
+ mi_string);
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate a subscriber for %s\n", mi_string);
+ return -1;
+ }
+
+ subscr->lac = lac;
+ subscr->tmsi = tmsi;
+
+ LOGP(DMSC, LOGL_INFO, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac);
+ bsc_grace_paging_request(msc->network->bsc_data->rf_ctrl->policy,
+ subscr, chan_needed, msc);
+ return 0;
+}
+
+/*
+ * GSM 08.08 § 3.1.9.1 and 3.2.1.21...
+ * release our gsm_subscriber_connection and send message
+ */
+static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int payload_length)
+{
+ struct msgb *resp;
+
+ /* TODO: handle the cause of this package */
+
+ if (conn->conn) {
+ LOGP(DMSC, LOGL_INFO, "Releasing all transactions on %p\n", conn);
+ gsm0808_clear(conn->conn);
+ bsc_subscr_con_free(conn->conn);
+ conn->conn = NULL;
+ }
+
+ /* send the clear complete message */
+ resp = gsm0808_create_clear_complete();
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n");
+ return -1;
+ }
+
+ bsc_queue_for_msc(conn, resp);
+ return 0;
+}
+
+/*
+ * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick
+ * the cipher to be used for this. In case we are already using
+ * a cipher we will have to send cipher mode reject to the MSC,
+ * otherwise we will have to pick something that we and the MS
+ * is supporting. Currently we are doing it in a rather static
+ * way by picking one ecnryption or no encrytpion.
+ */
+static int bssmap_handle_cipher_mode(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int payload_length)
+{
+ uint16_t len;
+ struct gsm_network *network = NULL;
+ const uint8_t *data;
+ struct tlv_parsed tp;
+ struct msgb *resp;
+ int reject_cause = -1;
+ int include_imeisv = 1;
+
+ if (!conn->conn) {
+ LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+ goto reject;
+ }
+
+ if (conn->ciphering_handled) {
+ LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n");
+ goto reject;
+ }
+
+ conn->ciphering_handled = 1;
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) {
+ LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n");
+ goto reject;
+ }
+
+ /*
+ * check if our global setting is allowed
+ * - Currently we check for A5/0 and A5/1
+ * - Copy the key if that is necessary
+ * - Otherwise reject
+ */
+ len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+ if (len < 1) {
+ LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n");
+ goto reject;
+ }
+
+ network = conn->conn->bts->network;
+ data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
+
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE))
+ include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1;
+
+ if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) {
+ gsm0808_cipher_mode(conn->conn, 0, NULL, 0, include_imeisv);
+ } else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) {
+ gsm0808_cipher_mode(conn->conn, 1, &data[1], len - 1, include_imeisv);
+ } else {
+ LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n");
+ goto reject;
+ }
+
+ return 0;
+
+reject:
+ resp = gsm0808_create_cipher_reject(reject_cause);
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n");
+ return -1;
+ }
+
+ bsc_queue_for_msc(conn, resp);
+ return -1;
+}
+
+/*
+ * Handle the assignment request message.
+ *
+ * See §3.2.1.1 for the message type
+ */
+static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct msgb *resp;
+ struct bsc_msc_data *msc;
+ struct tlv_parsed tp;
+ uint8_t *data;
+ uint8_t timeslot;
+ uint8_t multiplex;
+ enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN;
+ int i, supported, port, full_rate = -1;
+
+ if (!conn->conn) {
+ LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
+ return -1;
+ }
+
+ tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) {
+ LOGP(DMSC, LOGL_ERROR, "Mandatory channel type not present.\n");
+ goto reject;
+ }
+
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+ LOGP(DMSC, LOGL_ERROR, "Identity code missing. Audio routing will not work.\n");
+ goto reject;
+ }
+
+ conn->cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+ timeslot = conn->cic & 0x1f;
+ multiplex = (conn->cic & ~0x1f) >> 5;
+
+ /*
+ * Currently we only support a limited subset of all
+ * possible channel types. The limitation ends by not using
+ * multi-slot, limiting the channel coding, speech...
+ */
+ if (TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE) < 3) {
+ LOGP(DMSC, LOGL_ERROR, "ChannelType len !=3 not supported: %d\n",
+ TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
+ goto reject;
+ }
+
+ /*
+ * Try to figure out if we support the proposed speech codecs. For
+ * now we will always pick the full rate codecs.
+ */
+
+ data = (uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE);
+ if ((data[0] & 0xf) != 0x1) {
+ LOGP(DMSC, LOGL_ERROR, "ChannelType != speech: %d\n", data[0]);
+ goto reject;
+ }
+
+ /*
+ * go through the list of preferred codecs of our gsm network
+ * and try to find it among the permitted codecs. If we found
+ * it we will send chan_mode to the right mode and break the
+ * inner loop. The outer loop will exit due chan_mode having
+ * the correct value.
+ */
+ full_rate = 0;
+ msc = conn->msc;
+ for (supported = 0;
+ chan_mode == GSM48_CMODE_SIGN && supported < msc->audio_length;
+ ++supported) {
+
+ int perm_val = audio_support_to_gsm88(msc->audio_support[supported]);
+ for (i = 2; i < TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE); ++i) {
+ if ((data[i] & 0x7f) == perm_val) {
+ chan_mode = gsm88_to_chan_mode(perm_val);
+ full_rate = (data[i] & 0x4) == 0;
+ break;
+ } else if ((data[i] & 0x80) == 0x00) {
+ break;
+ }
+ }
+ }
+
+ if (chan_mode == GSM48_CMODE_SIGN) {
+ LOGP(DMSC, LOGL_ERROR, "No supported audio type found.\n");
+ goto reject;
+ }
+
+ /* map it to a MGCP Endpoint and a RTP port */
+ port = mgcp_timeslot_to_endpoint(multiplex, timeslot);
+ conn->rtp_port = rtp_calculate_port(port, msc->rtp_base);
+
+ return gsm0808_assign_req(conn->conn, chan_mode, full_rate);
+
+reject:
+ resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+ if (!resp) {
+ LOGP(DMSC, LOGL_ERROR, "Channel allocation failure.\n");
+ return -1;
+ }
+
+ bsc_queue_for_msc(conn, resp);
+ return -1;
+}
+
+static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ int ret = 0;
+
+ if (length < 1) {
+ LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC UDT BSSMAP %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+
+ switch (msg->l4h[0]) {
+ case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+ ret = bssmap_handle_reset_ack(msc, msg, length);
+ break;
+ case BSS_MAP_MSG_PAGING:
+ ret = bssmap_handle_paging(msc, msg, length);
+ break;
+ }
+
+ return ret;
+}
+
+static int bssmap_rcvmsg_dt1(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int length)
+{
+ int ret = 0;
+
+ if (length < 1) {
+ LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC DT1 BSSMAP %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+
+ switch (msg->l4h[0]) {
+ case BSS_MAP_MSG_CLEAR_CMD:
+ ret = bssmap_handle_clear_command(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_CIPHER_MODE_CMD:
+ ret = bssmap_handle_cipher_mode(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_ASSIGMENT_RQST:
+ ret = bssmap_handle_assignm_req(conn, msg, length);
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+ gsm0808_bssmap_name(msg->l4h[0]));
+ break;
+ }
+
+ return ret;
+}
+
+static int dtap_rcvmsg(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct dtap_header *header;
+ struct msgb *gsm48;
+ uint8_t *data;
+ int rc, dtap_rc;
+
+ LOGP(DMSC, LOGL_DEBUG, "Rx MSC DTAP: %s\n",
+ osmo_hexdump(msg->l3h, length));
+
+ if (!conn->conn) {
+ LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n");
+ return -1;
+ }
+
+ header = (struct dtap_header *) msg->l3h;
+ if (sizeof(*header) >= length) {
+ LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u\n", sizeof(*header), length);
+ LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+ return -1;
+ }
+
+ if (header->length > length - sizeof(*header)) {
+ LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length);
+ LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0);
+
+ /* forward the data */
+ gsm48 = gsm48_msgb_alloc_name("GSM 04.08 DTAP RCV");
+ if (!gsm48) {
+ LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n");
+ return -1;
+ }
+
+ gsm48->l3h = gsm48->data;
+ data = msgb_put(gsm48, length - sizeof(*header));
+ memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header));
+
+ /* pass it to the filter for extra actions */
+ rc = bsc_scan_msc_msg(conn->conn, gsm48);
+ dtap_rc = gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1);
+ if (rc == BSS_SEND_USSD)
+ bsc_send_welcome_ussd(conn->conn);
+ return dtap_rc;
+}
+
+int bsc_handle_udt(struct bsc_msc_data *msc,
+ struct msgb *msgb, unsigned int length)
+{
+ struct bssmap_header *bs;
+
+ LOGP(DMSC, LOGL_DEBUG, "Rx MSC UDT: %s\n",
+ osmo_hexdump(msgb->l3h, length));
+
+ if (length < sizeof(*bs)) {
+ LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+ return -1;
+ }
+
+ bs = (struct bssmap_header *) msgb->l3h;
+ if (bs->length < length - sizeof(*bs))
+ return -1;
+
+ switch (bs->type) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ msgb->l4h = &msgb->l3h[sizeof(*bs)];
+ bssmap_rcvmsg_udt(msc, msgb, length - sizeof(*bs));
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
+ gsm0808_bssmap_name(bs->type));
+ }
+
+ return 0;
+}
+
+int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn,
+ struct msgb *msg, unsigned int len)
+{
+ if (len < sizeof(struct bssmap_header)) {
+ LOGP(DMSC, LOGL_ERROR, "The header is too short.\n");
+ }
+
+ switch (msg->l3h[0]) {
+ case BSSAP_MSG_BSS_MANAGEMENT:
+ msg->l4h = &msg->l3h[sizeof(struct bssmap_header)];
+ bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header));
+ break;
+ case BSSAP_MSG_DTAP:
+ dtap_rcvmsg(conn, msg, len);
+ break;
+ default:
+ LOGP(DMSC, LOGL_NOTICE, "Unimplemented BSSAP msg type: %s\n",
+ gsm0808_bssap_name(msg->l3h[0]));
+ }
+
+ return -1;
+}
diff --git a/src/osmo-bsc/osmo_bsc_ctrl.c b/src/osmo-bsc/osmo_bsc_ctrl.c
new file mode 100644
index 000000000..c23ed2187
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_ctrl.c
@@ -0,0 +1,680 @@
+/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2011 by Holger Hans Peter Freyther
+ * (C) 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 <osmocom/ctrl/control_cmd.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_04_80.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/talloc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_connection *msc_con)
+{
+ struct ctrl_cmd *trap;
+ struct ctrl_handle *ctrl;
+ struct bsc_msc_data *msc_data;
+
+ msc_data = (struct bsc_msc_data *) msc_con->write_queue.bfd.data;
+ ctrl = msc_data->network->ctrl;
+
+ trap = ctrl_cmd_trap(cmd);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
+ return;
+ }
+
+ ctrl_cmd_send_to_all(ctrl, trap);
+ ctrl_cmd_send(&msc_con->write_queue, trap);
+
+ talloc_free(trap);
+}
+
+CTRL_CMD_DEFINE_RO(msc_connection_status, "msc_connection_status");
+static int msc_connection_status = 0;
+
+static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ if (msc_connection_status)
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+
+ if (signal == S_MSC_LOST && msc_connection_status == 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n");
+ msc_connection_status = 0;
+ } else if (signal == S_MSC_CONNECTED && msc_connection_status == 0) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n");
+ msc_connection_status = 1;
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ cmd->id = "0";
+ cmd->variable = "msc_connection_status";
+
+ get_msc_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status");
+static int bts_connection_status = 0;
+
+static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ if (bts_connection_status)
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+ struct gsm_bts *bts;
+ int bts_current_status;
+
+ if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) {
+ return 0;
+ }
+
+ bts_current_status = 0;
+ /* Check if OML on at least one BTS is up */
+ llist_for_each_entry(bts, &gsmnet->bts_list, list) {
+ if (bts->oml_link) {
+ bts_current_status = 1;
+ break;
+ }
+ }
+ if (bts_connection_status == 0 && bts_current_status == 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n");
+ } else if (bts_connection_status == 1 && bts_current_status == 0) {
+ LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n");
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ bts_connection_status = bts_current_status;
+
+ cmd->id = "0";
+ cmd->variable = "bts_connection_status";
+
+ get_bts_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data);
+
+static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_connection *msc_con)
+{
+ struct ctrl_cmd *cmd;
+ const char *oper, *admin, *policy;
+
+ cmd = ctrl_cmd_create(msc_con, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+ return;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr);
+
+ /* Prepare the location reply */
+ cmd->node = bts;
+ get_bts_loc(cmd, NULL);
+
+ oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+ admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+ policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ ",%s,%s,%s,%d,%d",
+ oper, admin, policy,
+ bts->network->country_code,
+ bts->network->network_code);
+
+ osmo_bsc_send_trap(cmd, msc_con);
+ talloc_free(cmd);
+}
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts)
+{
+ struct bsc_msc_data *msc;
+
+ llist_for_each_entry(msc, &bts->network->bsc_data->mscs, entry)
+ generate_location_state_trap(bts, msc->msc_con);
+}
+
+static int location_equal(struct bts_location *a, struct bts_location *b)
+{
+ return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
+ (a->lon == b->lon) && (a->height == b->height));
+}
+
+static void cleanup_locations(struct llist_head *locations)
+{
+ struct bts_location *myloc, *tmp;
+ int invalpos = 0, i = 0;
+
+ LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n");
+ llist_for_each_entry_safe(myloc, tmp, locations, list) {
+ i++;
+ if (i > 3) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n");
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ } else if (myloc->valid == BTS_LOC_FIX_INVALID) {
+ /* Only capture the newest of subsequent invalid positions */
+ invalpos++;
+ if (invalpos > 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n");
+ invalpos--;
+ i--;
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ }
+ } else {
+ invalpos = 0;
+ }
+ }
+ LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i);
+}
+
+CTRL_CMD_DEFINE(bts_loc, "location");
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ struct bts_location *curloc;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (llist_empty(&bts->loc_list)) {
+ cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
+ return CTRL_CMD_REPLY;
+ } else {
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp,
+ get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp;
+ struct bts_location *curloc, *lastloc;
+ int ret;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
+ if (!curloc) {
+ talloc_free(tmp);
+ goto oom;
+ }
+ INIT_LLIST_HEAD(&curloc->list);
+
+
+ tstamp = strtok_r(tmp, ",", &saveptr);
+ valid = strtok_r(NULL, ",", &saveptr);
+ lat = strtok_r(NULL, ",", &saveptr);
+ lon = strtok_r(NULL, ",", &saveptr);
+ height = strtok_r(NULL, "\0", &saveptr);
+
+ curloc->tstamp = atol(tstamp);
+ curloc->valid = get_string_value(bts_loc_fix_names, valid);
+ curloc->lat = atof(lat);
+ curloc->lon = atof(lon);
+ curloc->height = atof(height);
+ talloc_free(tmp);
+
+ lastloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+
+ /* Add location to the end of the list */
+ llist_add(&curloc->list, &bts->loc_list);
+
+ ret = get_bts_loc(cmd, data);
+
+ if (!location_equal(curloc, lastloc))
+ bsc_gen_location_state_trap(bts);
+
+ cleanup_locations(&bts->loc_list);
+
+ return ret;
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp;
+ time_t tstamp;
+ int valid;
+ double lat, lon, height __attribute__((unused));
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ tstampstr = strtok_r(tmp, ",", &saveptr);
+ validstr = strtok_r(NULL, ",", &saveptr);
+ latstr = strtok_r(NULL, ",", &saveptr);
+ lonstr = strtok_r(NULL, ",", &saveptr);
+ heightstr = strtok_r(NULL, "\0", &saveptr);
+
+ if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) ||
+ (lonstr == NULL) || (heightstr == NULL))
+ goto err;
+
+ tstamp = atol(tstampstr);
+ valid = get_string_value(bts_loc_fix_names, validstr);
+ lat = atof(latstr);
+ lon = atof(lonstr);
+ height = atof(heightstr);
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
+ (lon < -180) || (lon > 180) || (valid < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
+ return 1;
+}
+
+CTRL_CMD_DEFINE(net_timezone, "timezone");
+static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = (struct gsm_network*)cmd->node;
+
+ struct gsm_tz *tz = &net->tz;
+ if (tz->override)
+ cmd->reply = talloc_asprintf(cmd, "%d,%d,%d",
+ tz->hr, tz->mn, tz->dst);
+ else
+ cmd->reply = talloc_asprintf(cmd, "off");
+
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0;
+ int override;
+ struct gsm_network *net = (struct gsm_network*)cmd->node;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ override = 0;
+
+ if (hourstr != NULL)
+ override = strcasecmp(hourstr, "off") != 0;
+
+ struct gsm_tz *tz = &net->tz;
+ tz->override = override;
+
+ if (override) {
+ tz->hr = hourstr ? atol(hourstr) : 0;
+ tz->mn = minstr ? atol(minstr) : 0;
+ tz->dst = dststr ? atol(dststr) : 0;
+ }
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ return get_net_timezone(cmd, data);
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp;
+ int override, tz_hours, tz_mins, tz_dst;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ if (hourstr == NULL)
+ goto err;
+
+ override = strcasecmp(hourstr, "off") != 0;
+
+ if (!override) {
+ talloc_free(tmp);
+ return 0;
+ }
+
+ if (minstr == NULL || dststr == NULL)
+ goto err;
+
+ tz_hours = atol(hourstr);
+ tz_mins = atol(minstr);
+ tz_dst = atol(dststr);
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if ((tz_hours < -19) || (tz_hours > 19) ||
+ (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) ||
+ (tz_dst < 0) || (tz_dst > 2))
+ goto err;
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2");
+ return 1;
+}
+
+CTRL_CMD_DEFINE(net_notification, "notification");
+static int get_net_notification(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = "There is nothing to read";
+ return CTRL_CMD_ERROR;
+}
+
+static int set_net_notification(struct ctrl_cmd *cmd, void *data)
+{
+ struct ctrl_cmd *trap;
+ struct gsm_network *net;
+
+ net = cmd->node;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ goto handled;
+ }
+
+ trap->id = "0";
+ trap->variable = "notification";
+ trap->reply = talloc_strdup(trap, cmd->value);
+
+ /*
+ * This should only be sent to local systems. In the future
+ * we might even ask for systems to register to receive
+ * the notifications.
+ */
+ ctrl_cmd_send_to_all(net->ctrl, trap);
+ talloc_free(trap);
+
+handled:
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_net_notification(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+CTRL_CMD_DEFINE(net_inform_msc, "inform-msc-v1");
+static int get_net_inform_msc(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = "There is nothing to read";
+ return CTRL_CMD_ERROR;
+}
+
+static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net;
+ struct bsc_msc_data *msc;
+
+ net = cmd->node;
+ llist_for_each_entry(msc, &net->bsc_data->mscs, entry) {
+ struct ctrl_cmd *trap;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ continue;
+ }
+
+ trap->id = "0";
+ trap->variable = "inform-msc-v1";
+ trap->reply = talloc_strdup(trap, cmd->value);
+ ctrl_cmd_send(&msc->msc_con->write_queue, trap);
+ talloc_free(trap);
+ }
+
+
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_net_inform_msc(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+CTRL_CMD_DEFINE(net_ussd_notify, "ussd-notify-v1");
+static int get_net_ussd_notify(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = "There is nothing to read";
+ return CTRL_CMD_ERROR;
+}
+
+static int set_net_ussd_notify(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_subscriber_connection *conn;
+ struct gsm_network *net;
+ char *saveptr = NULL;
+ char *cic_str, *alert_str, *text_str;
+ int cic, alert;
+
+ /* Verify has done the test for us */
+ cic_str = strtok_r(cmd->value, ",", &saveptr);
+ alert_str = strtok_r(NULL, ",", &saveptr);
+ text_str = strtok_r(NULL, ",", &saveptr);
+
+ if (!cic_str || !alert_str || !text_str) {
+ cmd->reply = "Programming issue. How did this pass verify?";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "No connection found";
+
+ cic = atoi(cic_str);
+ alert = atoi(alert_str);
+
+ net = cmd->node;
+ llist_for_each_entry(conn, &net->subscr_conns, entry) {
+ if (!conn->sccp_con)
+ continue;
+
+ if (conn->sccp_con->cic != cic)
+ continue;
+
+ /*
+ * This is a hack. My E71 does not like to immediately
+ * receive a release complete on a TCH. So schedule a
+ * release complete to clear any previous attempt. The
+ * right thing would be to track invokeId and only send
+ * the release complete when we get a returnResultLast
+ * for this invoke id.
+ */
+ bsc_send_ussd_release_complete(conn);
+ bsc_send_ussd_notify(conn, alert, text_str);
+ cmd->reply = "Found a connection";
+ break;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_net_ussd_notify(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr = NULL;
+ char *inp, *cic, *alert, *text;
+
+ OSMO_ASSERT(cmd);
+ inp = talloc_strdup(cmd, value);
+
+ cic = strtok_r(inp, ",", &saveptr);
+ alert = strtok_r(NULL, ",", &saveptr);
+ text = strtok_r(NULL, ",", &saveptr);
+
+ talloc_free(inp);
+ if (!cic || !alert || !text)
+ return 1;
+ return 0;
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct msc_signal_data *msc;
+ struct gsm_network *net;
+ struct gsm_bts *bts;
+
+ if (subsys != SS_MSC)
+ return 0;
+ if (signal != S_MSC_AUTHENTICATED)
+ return 0;
+
+ msc = signal_data;
+
+ net = msc->data->network;
+ llist_for_each_entry(bts, &net->bts_list, list)
+ generate_location_state_trap(bts, msc->data->msc_con);
+
+ return 0;
+}
+
+int bsc_ctrl_cmds_install(struct gsm_network *net)
+{
+ int rc;
+
+ rc = bsc_base_ctrl_cmds_install();
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc_connection_status);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc);
+ if (rc)
+ goto end;
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_ussd_notify);
+ if (rc)
+ goto end;
+ rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
+
+end:
+ return rc;
+}
diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c
new file mode 100644
index 000000000..2c84b169f
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_filter.c
@@ -0,0 +1,381 @@
+/* (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+
+#include <stdlib.h>
+
+static void handle_lu_request(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ struct gsm48_loc_upd_req *lu;
+ struct gsm48_loc_area_id lai;
+ struct gsm_network *net;
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
+ LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg));
+ return;
+ }
+
+ net = conn->bts->network;
+
+ gh = msgb_l3(msg);
+ lu = (struct gsm48_loc_upd_req *) gh->data;
+
+ gsm48_generate_lai(&lai, net->country_code, net->network_code,
+ conn->bts->location_area_code);
+
+ if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) {
+ LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n");
+ conn->sccp_con->new_subscriber = 1;
+ }
+}
+
+/* extract a subscriber from the paging response */
+static struct bsc_subscr *extract_sub(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ uint8_t mi_type;
+ char mi_string[GSM48_MI_SIZE];
+ struct gsm48_hdr *gh;
+ struct gsm48_pag_resp *resp;
+ struct bsc_subscr *subscr;
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) {
+ LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg));
+ return NULL;
+ }
+
+ gh = msgb_l3(msg);
+ resp = (struct gsm48_pag_resp *) &gh->data[0];
+
+ gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+ mi_string, &mi_type);
+ DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
+ gsm48_mi_type_name(mi_type), mi_string);
+
+ switch (mi_type) {
+ case GSM_MI_TYPE_TMSI:
+ subscr = bsc_subscr_find_by_tmsi(conn->network->bsc_subscribers,
+ tmsi_from_string(mi_string));
+ break;
+ case GSM_MI_TYPE_IMSI:
+ subscr = bsc_subscr_find_by_imsi(conn->network->bsc_subscribers,
+ mi_string);
+ break;
+ default:
+ subscr = NULL;
+ break;
+ }
+
+ return subscr;
+}
+
+/* we will need to stop the paging request */
+static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct bsc_subscr *subscr = extract_sub(conn, msg);
+
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n");
+ return -1;
+ }
+
+ paging_request_stop(&conn->network->bts_list, conn->bts, subscr, conn,
+ msg);
+ bsc_subscr_put(subscr);
+ return 0;
+}
+
+static int is_cm_service_for_emerg(struct msgb *msg)
+{
+ struct gsm48_service_request *cm;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*cm)) {
+ LOGP(DMSC, LOGL_ERROR, "CM ServiceRequest does not fit.\n");
+ return 0;
+ }
+
+ cm = (struct gsm48_service_request *) &gh->data[0];
+ return cm->cm_service_type == GSM48_CMSERV_EMERGENCY;
+}
+
+struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ int8_t pdisc;
+ uint8_t mtype;
+ struct osmo_bsc_data *bsc;
+ struct bsc_msc_data *msc, *pag_msc;
+ struct bsc_subscr *subscr;
+ int is_emerg = 0;
+
+ bsc = conn->bts->network->bsc_data;
+
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOGP(DMSC, LOGL_ERROR, "There is no GSM48 header here.\n");
+ return NULL;
+ }
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
+
+ /*
+ * We are asked to select a MSC here but they are not equal. We
+ * want to respond to a paging request on the MSC where we got the
+ * request from. This is where we need to decide where this connection
+ * will go.
+ */
+ if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP)
+ goto paging;
+ else if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
+ is_emerg = is_cm_service_for_emerg(msg);
+ goto round_robin;
+ } else
+ goto round_robin;
+
+round_robin:
+ llist_for_each_entry(msc, &bsc->mscs, entry) {
+ if (!msc->msc_con->is_authenticated)
+ continue;
+ if (!is_emerg && msc->type != MSC_CON_TYPE_NORMAL)
+ continue;
+ if (is_emerg && !msc->allow_emerg)
+ continue;
+
+ /* force round robin by moving it to the end */
+ llist_move_tail(&msc->entry, &bsc->mscs);
+ return msc;
+ }
+
+ return NULL;
+
+paging:
+ subscr = extract_sub(conn, msg);
+
+ if (!subscr) {
+ LOGP(DMSC, LOGL_ERROR, "Got paged but no subscriber found.\n");
+ return NULL;
+ }
+
+ pag_msc = paging_get_data(conn->bts, subscr);
+ bsc_subscr_put(subscr);
+
+ llist_for_each_entry(msc, &bsc->mscs, entry) {
+ if (msc != pag_msc)
+ continue;
+
+ /*
+ * We don't check if the MSC is connected. In case it
+ * is not the connection will be dropped.
+ */
+
+ /* force round robin by moving it to the end */
+ llist_move_tail(&msc->entry, &bsc->mscs);
+ return msc;
+ }
+
+ LOGP(DMSC, LOGL_ERROR, "Got paged but no request found.\n");
+ return NULL;
+}
+
+
+/**
+ * This is used to scan a message for extra functionality of the BSC. This
+ * includes scanning for location updating requests/acceptd and then send
+ * a welcome USSD message to the subscriber.
+ */
+int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t mtype = gsm48_hdr_msg_type(gh);
+
+ if (pdisc == GSM48_PDISC_MM) {
+ if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST)
+ handle_lu_request(conn, msg);
+ } else if (pdisc == GSM48_PDISC_RR) {
+ if (mtype == GSM48_MT_RR_PAG_RESP)
+ handle_page_resp(conn, msg);
+ }
+
+ return 0;
+}
+
+static int send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+ struct osmo_bsc_sccp_con *bsc_con;
+
+ bsc_con = conn->sccp_con;
+ if (!bsc_con) {
+ LOGP(DMSC, LOGL_DEBUG, "No SCCP connection associated.\n");
+ return 0;
+ }
+
+ if (!bsc_con->msc->ussd_welcome_txt) {
+ LOGP(DMSC, LOGL_DEBUG, "No USSD Welcome text defined.\n");
+ return 0;
+ }
+
+ return BSS_SEND_USSD;
+}
+
+int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn)
+{
+ bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_welcome_txt);
+ bsc_send_ussd_release_complete(conn);
+
+ return 0;
+}
+
+static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
+ uint8_t *data, unsigned int length)
+{
+ struct tlv_parsed tp;
+ int parse_res;
+ struct gsm_bts *bts = conn->bts;
+ int tzunits;
+ uint8_t tzbsd = 0;
+ uint8_t dst = 0;
+
+ parse_res = tlv_parse(&tp, &gsm48_mm_att_tlvdef, data, length, 0, 0);
+ if (parse_res <= 0 && parse_res != -3)
+ /* FIXME: -3 means unknown IE error, so this accepts messages
+ * with unknown IEs. But parsing has aborted with the unknown
+ * IE and the message is broken or parsed incompletely. */
+ return 0;
+
+ /* Is TZ patching enabled? */
+ struct gsm_tz *tz = &bts->network->tz;
+ if (!tz->override)
+ return 0;
+
+ /* Convert tz.hr and tz.mn to units */
+ if (tz->hr < 0) {
+ tzunits = -tz->hr*4;
+ tzbsd |= 0x08;
+ } else
+ tzunits = tz->hr*4;
+
+ tzunits = tzunits + (tz->mn/15);
+
+ tzbsd |= (tzunits % 10)*0x10 + (tzunits / 10);
+
+ /* Convert DST value */
+ if (tz->dst >= 0 && tz->dst <= 2)
+ dst = tz->dst;
+
+ if (TLVP_PRESENT(&tp, GSM48_IE_UTC)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Local time zone' from 0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_UTC)[6], tzbsd);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_UTC)))[0] = tzbsd;
+ }
+ if (TLVP_PRESENT(&tp, GSM48_IE_NET_TIME_TZ)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Universal time and local time zone' TZ from "
+ "0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)[6], tzbsd);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)))[6] = tzbsd;
+ }
+#ifdef GSM48_IE_NET_DST
+ if (TLVP_PRESENT(&tp, GSM48_IE_NET_DST)) {
+ LOGP(DMSC, LOGL_DEBUG,
+ "Changing 'Network daylight saving time' from "
+ "0x%02x to 0x%02x.\n",
+ TLVP_VAL(&tp, GSM48_IE_NET_DST)[0], dst);
+ ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_DST)))[0] = dst;
+ }
+#endif
+
+ return 0;
+}
+
+static int has_core_identity(struct bsc_msc_data *msc)
+{
+ if (msc->core_mnc != -1)
+ return 1;
+ if (msc->core_mcc != -1)
+ return 1;
+ if (msc->core_lac != -1)
+ return 1;
+ if (msc->core_ci != -1)
+ return 1;
+ return 0;
+}
+
+/**
+ * Messages coming back from the MSC.
+ */
+int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct bsc_msc_data *msc;
+ struct gsm_network *net;
+ struct gsm48_loc_area_id *lai;
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t mtype;
+ int length = msgb_l3len(msg);
+
+ if (length < sizeof(*gh)) {
+ LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n");
+ return -1;
+ }
+
+ gh = (struct gsm48_hdr *) msgb_l3(msg);
+ length -= (const char *)&gh->data[0] - (const char *)gh;
+
+ pdisc = gsm48_hdr_pdisc(gh);
+ if (pdisc != GSM48_PDISC_MM)
+ return 0;
+
+ mtype = gsm48_hdr_msg_type(gh);
+ net = conn->bts->network;
+ msc = conn->sccp_con->msc;
+
+ if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) {
+ if (has_core_identity(msc)) {
+ if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) {
+ /* overwrite LAI in the message */
+ lai = (struct gsm48_loc_area_id *) &gh->data[0];
+ gsm48_generate_lai(lai, net->country_code,
+ net->network_code,
+ conn->bts->location_area_code);
+ }
+ }
+
+ if (conn->sccp_con->new_subscriber)
+ return send_welcome_ussd(conn);
+ return 0;
+ } else if (mtype == GSM48_MT_MM_INFO) {
+ bsc_patch_mm_info(conn, &gh->data[0], length);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c
new file mode 100644
index 000000000..63afa20d0
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_grace.c
@@ -0,0 +1,169 @@
+/*
+ * (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/osmo_bsc_grace.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+
+int bsc_grace_allow_new_connection(struct gsm_network *network, struct gsm_bts *bts)
+{
+ if (bts->excl_from_rf_lock)
+ return 1;
+ return network->bsc_data->rf_ctrl->policy == S_RF_ON;
+}
+
+
+static int normal_paging(struct bsc_subscr *subscr, int chan_needed,
+ struct bsc_msc_data *msc)
+{
+ /* we can't page by lac.. we need to page everything */
+ if (msc->core_lac != -1) {
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &msc->network->bts_list, list)
+ paging_request_bts(bts, subscr, chan_needed, NULL, msc);
+
+ return 0;
+ }
+
+ return paging_request(msc->network, subscr, chan_needed, NULL, msc);
+}
+
+static int locked_paging(struct bsc_subscr *subscr, int chan_needed,
+ struct bsc_msc_data *msc)
+{
+ struct gsm_bts *bts = NULL;
+
+ /*
+ * Check if there is any BTS that is on for the given lac. Start
+ * with NULL and iterate through all bts.
+ */
+ llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ /*
+ * continue if the BTS is not excluded from the lock
+ */
+ if (!bts->excl_from_rf_lock)
+ continue;
+
+ /* in case of no lac patching is in place, check the BTS */
+ if (msc->core_lac == -1 && subscr->lac != bts->location_area_code)
+ continue;
+
+ /*
+ * now page on this bts
+ */
+ paging_request_bts(bts, subscr, chan_needed, NULL, msc);
+ };
+
+ /* All bts are either off or in the grace period */
+ return 0;
+}
+
+/**
+ * Try to not page if everything the cell is not on.
+ */
+int bsc_grace_paging_request(enum signal_rf rf_policy,
+ struct bsc_subscr *subscr,
+ int chan_needed,
+ struct bsc_msc_data *msc)
+{
+ if (rf_policy == S_RF_ON)
+ return normal_paging(subscr, chan_needed, msc);
+ return locked_paging(subscr, chan_needed, msc);
+}
+
+static int handle_sub(struct gsm_lchan *lchan, const char *text)
+{
+ struct gsm_subscriber_connection *conn;
+
+ /* only send it to TCH */
+ if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F)
+ return -1;
+
+ /* only send on the primary channel */
+ conn = lchan->conn;
+ if (!conn)
+ return -1;
+
+ if (conn->lchan != lchan)
+ return -1;
+
+ /* only when active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ bsc_send_ussd_notify(conn, 0, text);
+ bsc_send_ussd_release_complete(conn);
+
+ return 0;
+}
+
+/*
+ * The place to handle the grace mode. Right now we will send
+ * USSD messages to the subscriber, in the future we might start
+ * a timer to have different modes for the grace period.
+ */
+static int handle_grace(struct gsm_network *network)
+{
+ int ts_nr, lchan_nr;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+
+ if (!network->bsc_data->mid_call_txt)
+ return 0;
+
+ llist_for_each_entry(bts, &network->bts_list, list) {
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) {
+ handle_sub(&ts->lchan[lchan_nr],
+ network->bsc_data->mid_call_txt);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int handle_rf_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct rf_signal_data *sig;
+
+ if (subsys != SS_RF)
+ return -1;
+
+ sig = signal_data;
+
+ if (signal == S_RF_GRACE)
+ handle_grace(sig->net);
+
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_grace(void)
+{
+ osmo_signal_register_handler(SS_RF, handle_rf_signal, NULL);
+}
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
new file mode 100644
index 000000000..ee094d670
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -0,0 +1,301 @@
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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/bss.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/vty.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/ctrl.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/core/application.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/abis/abis.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#include "../../bscconfig.h"
+
+struct gsm_network *bsc_gsmnet = 0;
+static const char *config_file = "openbsc.cfg";
+static const char *rf_ctrl = NULL;
+extern const char *openbsc_copyright;
+static int daemonize = 0;
+static struct llist_head access_lists;
+
+struct llist_head *bsc_access_lists(void)
+{
+ return &access_lists;
+}
+
+static void print_usage()
+{
+ printf("Usage: osmo-bsc\n");
+}
+
+static void print_help()
+{
+ printf(" Some useful help...\n");
+ printf(" -h --help this text\n");
+ printf(" -D --daemonize Fork the process into a background daemon\n");
+ printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n");
+ printf(" -s --disable-color\n");
+ printf(" -T --timestamp. Print a timestamp in the debug output.\n");
+ printf(" -c --config-file filename The config file to use.\n");
+ printf(" -l --local=IP. The local address of the MGCP.\n");
+ printf(" -e --log-level number. Set a global loglevel.\n");
+ printf(" -r --rf-ctl NAME. A unix domain socket to listen for cmds.\n");
+ printf(" -t --testmode. A special mode to provoke failures at the MSC.\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'},
+ {"local", 1, 0, 'l'},
+ {"log-level", 1, 0, 'e'},
+ {"rf-ctl", 1, 0, 'r'},
+ {"testmode", 0, 0, 't'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "hd:DsTc:e:r:t",
+ 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 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'r':
+ rf_ctrl = optarg;
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+}
+
+extern int bsc_vty_go_parent(struct vty *vty);
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoBSC",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = bsc_vty_go_parent,
+ .is_config_node = bsc_vty_is_config_node,
+};
+
+extern int bsc_shutdown_net(struct gsm_network *net);
+static void signal_handler(int signal)
+{
+ struct bsc_msc_data *msc;
+
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ bsc_shutdown_net(bsc_gsmnet);
+ osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
+ sleep(3);
+ exit(0);
+ break;
+ 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(tall_vty_ctx, stderr);
+ talloc_report_full(tall_bsc_ctx, stderr);
+ break;
+ case SIGUSR2:
+ if (!bsc_gsmnet->bsc_data)
+ return;
+ llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry)
+ bsc_msc_lost(msc->msc_con);
+ break;
+ default:
+ break;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct bsc_msc_data *msc;
+ struct osmo_bsc_data *data;
+ int rc;
+
+ tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
+ msgb_talloc_ctx_init(tall_bsc_ctx, 0);
+
+ /* Allocate global gsm_network struct */
+ rc = bsc_network_alloc(NULL);
+ if (rc) {
+ fprintf(stderr, "Allocation failed. exiting.\n");
+ exit(1);
+ }
+
+ osmo_init_logging(&log_info);
+ osmo_stats_init(tall_bsc_ctx);
+
+ bts_init();
+ libosmo_abis_init(tall_bsc_ctx);
+
+ /* enable filters */
+
+ /* This needs to precede handle_options() */
+ vty_info.copyright = openbsc_copyright;
+ vty_init(&vty_info);
+ bsc_vty_init(bsc_gsmnet);
+ bsc_msg_lst_vty_init(tall_bsc_ctx, &access_lists, BSC_NODE);
+ ctrl_vty_init(tall_bsc_ctx);
+
+ INIT_LLIST_HEAD(&access_lists);
+
+ /* parse options */
+ handle_options(argc, argv);
+
+ /* seed the PRNG */
+ srand(time(NULL));
+
+ /* initialize SCCP */
+ sccp_set_log_area(DSCCP);
+
+ /* Read the config */
+ rc = bsc_network_configure(config_file);
+ if (rc < 0) {
+ fprintf(stderr, "Bootstrapping the network failed. exiting.\n");
+ exit(1);
+ }
+ bsc_api_init(bsc_gsmnet, osmo_bsc_api());
+
+ /* start control interface after reading config for
+ * ctrl_vty_get_bind_addr() */
+ bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet,
+ ctrl_vty_get_bind_addr(),
+ OSMO_CTRL_PORT_NITB_BSC);
+ if (!bsc_gsmnet->ctrl) {
+ fprintf(stderr, "Failed to init the control interface. Exiting.\n");
+ exit(1);
+ }
+
+ rc = bsc_ctrl_cmds_install(bsc_gsmnet);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to install control commands. Exiting.\n");
+ exit(1);
+ }
+
+ data = bsc_gsmnet->bsc_data;
+ if (rf_ctrl)
+ osmo_talloc_replace_string(data, &data->rf_ctrl_name, rf_ctrl);
+
+ data->rf_ctrl = osmo_bsc_rf_create(data->rf_ctrl_name, bsc_gsmnet);
+ if (!data->rf_ctrl) {
+ fprintf(stderr, "Failed to create the RF service.\n");
+ exit(1);
+ }
+
+ llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+ if (osmo_bsc_msc_init(msc) != 0) {
+ LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n");
+ exit(1);
+ }
+ }
+
+
+ if (osmo_bsc_sccp_init(bsc_gsmnet) != 0) {
+ LOGP(DNM, LOGL_ERROR, "Failed to register SCCP.\n");
+ exit(1);
+ }
+
+ if (osmo_bsc_audio_init(bsc_gsmnet) != 0) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n");
+ exit(1);
+ }
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
new file mode 100644
index 000000000..8d02624b4
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -0,0 +1,586 @@
+/*
+ * Handle the connection to the MSC. This include ping/timeout/reconnect
+ * (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-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/bsc_nat.h>
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/crypt/auth.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/gsm0808.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <osmocom/abis/ipa.h>
+
+#include <sys/socket.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+
+static void initialize_if_needed(struct bsc_msc_connection *conn);
+static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn);
+static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp);
+static void send_ping(struct bsc_msc_data *data);
+static void schedule_ping_pong(struct bsc_msc_data *data);
+
+/*
+ * MGCP forwarding code
+ */
+static int mgcp_do_read(struct osmo_fd *fd)
+{
+ struct bsc_msc_data *data = (struct bsc_msc_data *) fd->data;
+ struct msgb *mgcp;
+ int ret;
+
+ mgcp = msgb_alloc_headroom(4096, 128, "mgcp_from_gw");
+ if (!mgcp) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n");
+ return -1;
+ }
+
+ ret = read(fd->fd, mgcp->data, 4096 - 128);
+ if (ret <= 0) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno));
+ msgb_free(mgcp);
+ return -1;
+ } else if (ret > 4096 - 128) {
+ LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret);
+ msgb_free(mgcp);
+ return -1;
+ }
+
+ mgcp->l2h = msgb_put(mgcp, ret);
+ msc_queue_write(data->msc_con, mgcp, IPAC_PROTO_MGCP_OLD);
+ return 0;
+}
+
+static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg)
+{
+ int ret;
+
+ LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len);
+
+ ret = write(fd->fd, msg->data, msg->len);
+ if (ret != msg->len)
+ LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP GW (%s).\n", strerror(errno));
+
+ return ret;
+}
+
+static void mgcp_forward(struct bsc_msc_data *data, struct msgb *msg)
+{
+ struct msgb *mgcp;
+
+ if (msgb_l2len(msg) > 4096) {
+ LOGP(DMGCP, LOGL_ERROR, "Can not forward too big message.\n");
+ return;
+ }
+
+ mgcp = msgb_alloc(4096, "mgcp_to_gw");
+ if (!mgcp) {
+ LOGP(DMGCP, LOGL_ERROR, "Failed to send message.\n");
+ return;
+ }
+
+ msgb_put(mgcp, msgb_l2len(msg));
+ memcpy(mgcp->data, msg->l2h, mgcp->len);
+ if (osmo_wqueue_enqueue(&data->mgcp_agent, mgcp) != 0) {
+ LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n");
+ msgb_free(mgcp);
+ }
+}
+
+static int mgcp_create_port(struct bsc_msc_data *data)
+{
+ int on;
+ struct sockaddr_in addr;
+
+ data->mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (data->mgcp_agent.bfd.fd < 0) {
+ LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno);
+ return -1;
+ }
+
+ on = 1;
+ setsockopt(data->mgcp_agent.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ /* try to bind the socket */
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = 0;
+
+ if (bind(data->mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n");
+ close(data->mgcp_agent.bfd.fd);
+ data->mgcp_agent.bfd.fd = -1;
+ return -1;
+ }
+
+ /* connect to the remote */
+ addr.sin_port = htons(2427);
+ if (connect(data->mgcp_agent.bfd.fd, (struct sockaddr *) & addr, sizeof(addr)) < 0) {
+ LOGP(DMGCP, LOGL_FATAL, "Failed to connect to local MGCP GW. %s\n", strerror(errno));
+ close(data->mgcp_agent.bfd.fd);
+ data->mgcp_agent.bfd.fd = -1;
+ return -1;
+ }
+
+ osmo_wqueue_init(&data->mgcp_agent, 10);
+ data->mgcp_agent.bfd.when = BSC_FD_READ;
+ data->mgcp_agent.bfd.data = data;
+ data->mgcp_agent.read_cb = mgcp_do_read;
+ data->mgcp_agent.write_cb = mgcp_do_write;
+
+ if (osmo_fd_register(&data->mgcp_agent.bfd) != 0) {
+ LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n");
+ close(data->mgcp_agent.bfd.fd);
+ data->mgcp_agent.bfd.fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Send data to the network
+ */
+int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto)
+{
+ ipa_prepend_header(msg, proto);
+ if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) {
+ LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto);
+ msgb_free(msg);
+ return -1;
+ }
+
+ return 0;
+}
+
+int msc_queue_write_with_ping(struct bsc_msc_connection *conn,
+ struct msgb *msg, int proto)
+{
+ struct bsc_msc_data *data;
+ uint8_t val;
+
+ /* prepend the header */
+ ipa_prepend_header(msg, proto);
+ if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) {
+ LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto);
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* add the ping as the other message */
+ val = IPAC_MSGT_PING;
+ msgb_l16tv_put(msg, 1, IPAC_PROTO_IPACCESS, &val);
+
+ data = (struct bsc_msc_data *) conn->write_queue.bfd.data;
+ schedule_ping_pong(data);
+ return 0;
+}
+
+static int msc_alink_do_write(struct osmo_fd *fd, struct msgb *msg)
+{
+ int ret;
+
+ LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg));
+ LOGP(DLMI, LOGL_DEBUG, "MSC TX %s\n", osmo_hexdump(msg->data, msg->len));
+
+ ret = write(fd->fd, msg->data, msg->len);
+ if (ret < msg->len)
+ perror("MSC: Failed to send SCCP");
+
+ return ret;
+}
+
+static void handle_ctrl(struct bsc_msc_data *msc, struct msgb *msg)
+{
+ int ret;
+ struct ctrl_cmd *cmd;
+
+ cmd = ctrl_cmd_parse(msc->msc_con, msg);
+ if (!cmd) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to parse control message.\n");
+ cmd = talloc_zero(msc->msc_con, struct ctrl_cmd);
+ if (!cmd) {
+ LOGP(DMSC, LOGL_ERROR, "OOM!\n");
+ return;
+ }
+ cmd->type = CTRL_TYPE_ERROR;
+ cmd->id = "err";
+ cmd->reply = "Failed to parse control message.";
+
+ ctrl_cmd_send(&msc->msc_con->write_queue, cmd);
+ talloc_free(cmd);
+
+ return;
+ }
+
+ ret = ctrl_cmd_handle(msc->network->ctrl, cmd, msc->network);
+ if (ret != CTRL_CMD_HANDLED)
+ ctrl_cmd_send(&msc->msc_con->write_queue, cmd);
+ talloc_free(cmd);
+}
+
+static void osmo_ext_handle(struct bsc_msc_data *msc, struct msgb *msg)
+{
+ struct ipaccess_head *hh;
+ struct ipaccess_head_ext *hh_ext;
+
+ hh = (struct ipaccess_head *) msg->data;
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ if (msg->len < sizeof(*hh) + sizeof(*hh_ext)) {
+ LOGP(DMSC, LOGL_ERROR, "Packet too short for extended header.\n");
+ return;
+ }
+
+ msg->l2h = hh_ext->data;
+ if (hh_ext->proto == IPAC_PROTO_EXT_MGCP)
+ mgcp_forward(msc, msg);
+ else if (hh_ext->proto == IPAC_PROTO_EXT_LAC)
+ send_lacs(msc->network, msc->msc_con);
+ else if (hh_ext->proto == IPAC_PROTO_EXT_CTRL)
+ handle_ctrl(msc, msg);
+}
+
+static int ipaccess_a_fd_cb(struct osmo_fd *bfd)
+{
+ struct msgb *msg = NULL;
+ struct ipaccess_head *hh;
+ struct bsc_msc_data *data = (struct bsc_msc_data *) bfd->data;
+ int ret;
+
+ ret = ipa_msg_recv_buffered(bfd->fd, &msg, &data->msc_con->pending_msg);
+ if (ret <= 0) {
+ if (ret == -EAGAIN)
+ return 0;
+ if (ret == 0) {
+ LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n");
+ bsc_msc_lost(data->msc_con);
+ return -1;
+ }
+
+ LOGP(DMSC, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret);
+ return -1;
+ }
+
+ LOGP(DLMI, LOGL_DEBUG, "From MSC: %s proto: %d\n", 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 */
+ msg->cb[0] = (unsigned long) data;
+ if (hh->proto == IPAC_PROTO_IPACCESS) {
+ ipa_ccm_rcvmsg_base(msg, bfd);
+ if (msg->l2h[0] == IPAC_MSGT_ID_ACK)
+ initialize_if_needed(data->msc_con);
+ else if (msg->l2h[0] == IPAC_MSGT_ID_GET) {
+ send_id_get_response(data, bfd->fd, msg);
+ } else if (msg->l2h[0] == IPAC_MSGT_PONG) {
+ osmo_timer_del(&data->pong_timer);
+ }
+ } else if (hh->proto == IPAC_PROTO_SCCP) {
+ sccp_system_incoming_ctx(msg, data->msc_con);
+ } else if (hh->proto == IPAC_PROTO_MGCP_OLD) {
+ mgcp_forward(data, msg);
+ } else if (hh->proto == IPAC_PROTO_OSMO) {
+ osmo_ext_handle(data, msg);
+ }
+
+ msgb_free(msg);
+ return 0;
+}
+
+static void send_ping(struct bsc_msc_data *data)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(4096, 128, "ping");
+ if (!msg) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to create PING.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, 1);
+ msg->l2h[0] = IPAC_MSGT_PING;
+
+ msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+}
+
+static void schedule_ping_pong(struct bsc_msc_data *data)
+{
+ /* send another ping in 20 seconds */
+ osmo_timer_schedule(&data->ping_timer, data->ping_timeout, 0);
+
+ /* also start a pong timer */
+ osmo_timer_schedule(&data->pong_timer, data->pong_timeout, 0);
+}
+
+static void msc_ping_timeout_cb(void *_data)
+{
+ struct bsc_msc_data *data = (struct bsc_msc_data *) _data;
+ if (data->ping_timeout <= 0)
+ return;
+
+ send_ping(data);
+ schedule_ping_pong(data);
+}
+
+static void msc_pong_timeout_cb(void *_data)
+{
+ struct bsc_msc_data *data = (struct bsc_msc_data *) _data;
+
+ LOGP(DMSC, LOGL_ERROR, "MSC didn't answer PING. Closing connection.\n");
+ bsc_msc_lost(data->msc_con);
+}
+
+static void msc_connection_connected(struct bsc_msc_connection *con)
+{
+ struct msc_signal_data sig;
+ struct bsc_msc_data *data;
+ int ret, on;
+ on = 1;
+ ret = setsockopt(con->write_queue.bfd.fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+ if (ret != 0)
+ LOGP(DMSC, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
+
+ data = (struct bsc_msc_data *) con->write_queue.bfd.data;
+ msc_ping_timeout_cb(data);
+
+ sig.data = data;
+ osmo_signal_dispatch(SS_MSC, S_MSC_CONNECTED, &sig);
+}
+
+/*
+ * The connection to the MSC was lost and we will need to free all
+ * resources and then attempt to reconnect.
+ */
+static void msc_connection_was_lost(struct bsc_msc_connection *msc)
+{
+ struct msc_signal_data sig;
+ struct bsc_msc_data *data;
+
+ LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n");
+
+ data = (struct bsc_msc_data *) msc->write_queue.bfd.data;
+ osmo_timer_del(&data->ping_timer);
+ osmo_timer_del(&data->pong_timer);
+
+ sig.data = data;
+ osmo_signal_dispatch(SS_MSC, S_MSC_LOST, &sig);
+
+ msc->is_authenticated = 0;
+ bsc_msc_schedule_connect(msc);
+}
+
+static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn)
+{
+ struct ipac_ext_lac_cmd *lac;
+ struct gsm_bts *bts;
+ struct msgb *msg;
+ int lacs = 0;
+
+ if (llist_empty(&net->bts_list)) {
+ LOGP(DMSC, LOGL_ERROR, "No BTSs configured. Not sending LACs.\n");
+ return;
+ }
+
+ msg = msgb_alloc_headroom(4096, 128, "LAC Command");
+ if (!msg) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to create the LAC command.\n");
+ return;
+ }
+
+ lac = (struct ipac_ext_lac_cmd *) msgb_put(msg, sizeof(*lac));
+ lac->add_remove = 1;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (lacs++ == 0)
+ lac->lac = htons(bts->location_area_code);
+ else
+ msgb_put_u16(msg, htons(bts->location_area_code));
+ }
+
+ lac->nr_extra_lacs = lacs - 1;
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_LAC);
+ msc_queue_write(conn, msg, IPAC_PROTO_OSMO);
+}
+
+static void initialize_if_needed(struct bsc_msc_connection *conn)
+{
+ struct msgb *msg;
+
+ if (!conn->is_authenticated) {
+ /* send a gsm 08.08 reset message from here */
+ msg = gsm0808_create_reset();
+ if (!msg) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to create the reset message.\n");
+ return;
+ }
+
+ sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, conn);
+ msgb_free(msg);
+ conn->is_authenticated = 1;
+ }
+}
+
+static int answer_challenge(struct bsc_msc_data *data, struct msgb *inp, struct osmo_auth_vector *vec)
+{
+ int ret;
+ struct tlv_parsed tvp;
+ const uint8_t *mrand;
+ uint8_t mrand_len;
+ struct osmo_sub_auth_data auth = {
+ .type = OSMO_AUTH_TYPE_GSM,
+ .algo = OSMO_AUTH_ALG_MILENAGE,
+ };
+
+ ret = ipa_ccm_idtag_parse_off(&tvp,
+ inp->l2h + 1,
+ msgb_l2len(inp) - 1, 1);
+ if (ret < 0) {
+ LOGP(DMSC, LOGL_ERROR, "ignoring IPA response "
+ "message with malformed TLVs: %s\n", osmo_hexdump(inp->l2h + 1,
+ msgb_l2len(inp) - 1));
+ return 0;
+ }
+
+ mrand = TLVP_VAL(&tvp, 0x23);
+ mrand_len = TLVP_LEN(&tvp, 0x23);
+ if (mrand_len != 16) {
+ LOGP(DMSC, LOGL_ERROR,
+ "RAND is not 16 bytes. Was %d\n",
+ mrand_len);
+ return 0;
+ }
+
+ /* copy the key */
+ memcpy(auth.u.umts.opc, data->bsc_key, 16);
+ memcpy(auth.u.umts.k, data->bsc_key, 16);
+ memset(auth.u.umts.amf, 0, 2);
+ auth.u.umts.sqn = 0;
+
+ /* generate the result */
+ memset(vec, 0, sizeof(*vec));
+ osmo_auth_gen_vec(vec, &auth, mrand);
+ return 1;
+}
+
+
+static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp)
+{
+ struct msc_signal_data sig;
+ struct msgb *msg;
+ struct osmo_auth_vector vec;
+ int valid = 0;
+
+ if (data->bsc_key_present)
+ valid = answer_challenge(data, inp, &vec);
+
+ msg = bsc_msc_id_get_resp(valid, data->bsc_token,
+ vec.res, valid ? vec.res_len : 0);
+ if (!msg)
+ return;
+ msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS);
+
+ sig.data = data;
+ osmo_signal_dispatch(SS_MSC, S_MSC_AUTHENTICATED, &sig);
+}
+
+int osmo_bsc_msc_init(struct bsc_msc_data *data)
+{
+ if (mgcp_create_port(data) != 0)
+ return -1;
+
+ data->msc_con = bsc_msc_create(data, &data->dests);
+ if (!data->msc_con) {
+ LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n");
+ return -1;
+ }
+
+ osmo_timer_setup(&data->ping_timer, msc_ping_timeout_cb, data);
+ osmo_timer_setup(&data->pong_timer, msc_pong_timeout_cb, data);
+
+ data->msc_con->write_queue.bfd.data = data;
+ data->msc_con->connection_loss = msc_connection_was_lost;
+ data->msc_con->connected = msc_connection_connected;
+ data->msc_con->write_queue.read_cb = ipaccess_a_fd_cb;
+ data->msc_con->write_queue.write_cb = msc_alink_do_write;
+ bsc_msc_connect(data->msc_con);
+
+ return 0;
+}
+
+struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr)
+{
+ struct bsc_msc_data *msc_data;
+
+ llist_for_each_entry(msc_data, &net->bsc_data->mscs, entry)
+ if (msc_data->nr == nr)
+ return msc_data;
+ return NULL;
+}
+
+struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
+{
+ struct bsc_msc_data *msc_data;
+
+ /* check if there is already one */
+ msc_data = osmo_msc_data_find(net, nr);
+ if (msc_data)
+ return msc_data;
+
+ msc_data = talloc_zero(net, struct bsc_msc_data);
+ if (!msc_data)
+ return NULL;
+
+ llist_add_tail(&msc_data->entry, &net->bsc_data->mscs);
+
+ /* Init back pointer */
+ msc_data->network = net;
+
+ INIT_LLIST_HEAD(&msc_data->dests);
+ msc_data->ping_timeout = 20;
+ msc_data->pong_timeout = 5;
+ msc_data->core_mnc = -1;
+ msc_data->core_mcc = -1;
+ msc_data->core_ci = -1;
+ msc_data->core_lac = -1;
+ msc_data->rtp_base = 4000;
+
+ msc_data->nr = nr;
+ msc_data->allow_emerg = 1;
+
+ /* Defaults for the audio setup */
+ msc_data->amr_conf.m5_90 = 1;
+
+ return msc_data;
+}
diff --git a/src/osmo-bsc/osmo_bsc_sccp.c b/src/osmo-bsc/osmo_bsc_sccp.c
new file mode 100644
index 000000000..e242390ef
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_sccp.c
@@ -0,0 +1,328 @@
+/* Interaction with the SCCP subsystem */
+/*
+ * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009-2014 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/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/osmo_bsc_grace.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/debug.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/sccp/sccp.h>
+
+/* SCCP helper */
+#define SCCP_IT_TIMER 60
+
+static LLIST_HEAD(active_connections);
+
+static void free_queued(struct osmo_bsc_sccp_con *conn)
+{
+ struct msgb *msg;
+
+ while (!llist_empty(&conn->sccp_queue)) {
+ /* this is not allowed to fail */
+ msg = msgb_dequeue(&conn->sccp_queue);
+ msgb_free(msg);
+ }
+
+ conn->sccp_queue_size = 0;
+}
+
+static void send_queued(struct osmo_bsc_sccp_con *conn)
+{
+ struct msgb *msg;
+
+ while (!llist_empty(&conn->sccp_queue)) {
+ /* this is not allowed to fail */
+ msg = msgb_dequeue(&conn->sccp_queue);
+ sccp_connection_write(conn->sccp, msg);
+ msgb_free(msg);
+ conn->sccp_queue_size -= 1;
+ }
+}
+
+static void msc_outgoing_sccp_data(struct sccp_connection *conn,
+ struct msgb *msg, unsigned int len)
+{
+ struct osmo_bsc_sccp_con *bsc_con =
+ (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+ bsc_handle_dt1(bsc_con, msg, len);
+}
+
+static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state)
+{
+ struct osmo_bsc_sccp_con *con_data;
+
+ if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) {
+ con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+ if (con_data->conn) {
+ LOGP(DMSC, LOGL_ERROR,
+ "ERROR: The lchan is still associated.\n");
+ gsm0808_clear(con_data->conn);
+ bsc_subscr_con_free(con_data->conn);
+ con_data->conn = NULL;
+ }
+
+ con_data->sccp = NULL;
+ free_queued(con_data);
+ sccp_connection_free(conn);
+ bsc_delete_connection(con_data);
+ } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) {
+ LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn);
+ con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx;
+
+ osmo_timer_del(&con_data->sccp_cc_timeout);
+ osmo_timer_schedule(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+
+ send_queued(con_data);
+ }
+}
+
+static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data)
+{
+ if (data->conn) {
+ gsm0808_clear(data->conn);
+ bsc_subscr_con_free(data->conn);
+ data->conn = NULL;
+ }
+
+ free_queued(data);
+ sccp_connection_force_free(data->sccp);
+ data->sccp = NULL;
+ bsc_delete_connection(data);
+}
+
+static void sccp_it_timeout(void *_data)
+{
+ struct osmo_bsc_sccp_con *data =
+ (struct osmo_bsc_sccp_con *) _data;
+
+ sccp_connection_send_it(data->sccp);
+ osmo_timer_schedule(&data->sccp_it_timeout, SCCP_IT_TIMER, 0);
+}
+
+static void sccp_cc_timeout(void *_data)
+{
+ struct osmo_bsc_sccp_con *data =
+ (struct osmo_bsc_sccp_con *) _data;
+
+ if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED)
+ return;
+
+ LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n");
+ bsc_sccp_force_free(data);
+}
+
+static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg,
+ void *global_ctx, void *ctx)
+{
+ struct bsc_msc_connection *msc_con;
+
+ if (conn) {
+ struct osmo_bsc_sccp_con *bsc_con = conn->data_ctx;
+ msc_con = bsc_con->msc->msc_con;
+ if (bsc_con->send_ping) {
+ bsc_con->send_ping = 0;
+ msc_queue_write_with_ping(msc_con, msg, IPAC_PROTO_SCCP);
+ return;
+ }
+ } else {
+ msc_con = ctx;
+ }
+
+ msc_queue_write(msc_con, msg, IPAC_PROTO_SCCP);
+}
+
+static int msc_sccp_accept(struct sccp_connection *connection, void *data)
+{
+ LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n");
+ return -1;
+}
+
+static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data)
+{
+ struct bsc_msc_data *msc = (struct bsc_msc_data *) msgb->cb[0];
+ return bsc_handle_udt(msc, msgb, length);
+}
+
+int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+ struct sccp_connection *sccp = conn->sccp;
+
+ if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) {
+ LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+ msgb_free(msg);
+ } else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED
+ && conn->sccp_queue_size == 0) {
+ sccp_connection_write(sccp, msg);
+ msgb_free(msg);
+ } else if (conn->sccp_queue_size > 10) {
+ LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp);
+ msgb_free(msg);
+ } else {
+ LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size);
+ conn->sccp_queue_size += 1;
+ msgb_enqueue(&conn->sccp_queue, msg);
+ }
+
+ return 0;
+}
+
+enum bsc_con bsc_create_new_connection(struct gsm_subscriber_connection *conn,
+ struct bsc_msc_data *msc, int send_ping)
+{
+ struct osmo_bsc_sccp_con *bsc_con;
+ struct sccp_connection *sccp;
+
+ /* This should not trigger */
+ if (!msc || !msc->msc_con->is_authenticated) {
+ LOGP(DMSC, LOGL_ERROR,
+ "How did this happen? MSC is not connected. Dropping.\n");
+ return BSC_CON_REJECT_NO_LINK;
+ }
+
+ if (!bsc_grace_allow_new_connection(conn->bts->network, conn->bts)) {
+ LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
+ return BSC_CON_REJECT_RF_GRACE;
+ }
+
+ sccp = sccp_connection_socket();
+ if (!sccp) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n");
+ return BSC_CON_NO_MEM;
+ }
+
+ bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con);
+ if (!bsc_con) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n");
+ sccp_connection_free(sccp);
+ return BSC_CON_NO_MEM;
+ }
+
+ /* callbacks */
+ sccp->state_cb = msc_outgoing_sccp_state;
+ sccp->data_cb = msc_outgoing_sccp_data;
+ sccp->data_ctx = bsc_con;
+
+ bsc_con->send_ping = send_ping;
+
+ /* prepare the timers */
+ osmo_timer_setup(&bsc_con->sccp_it_timeout, sccp_it_timeout, bsc_con);
+ osmo_timer_setup(&bsc_con->sccp_cc_timeout, sccp_cc_timeout, bsc_con);
+
+ INIT_LLIST_HEAD(&bsc_con->sccp_queue);
+
+ bsc_con->sccp = sccp;
+ bsc_con->msc = msc;
+ bsc_con->conn = conn;
+ llist_add_tail(&bsc_con->entry, &active_connections);
+ conn->sccp_con = bsc_con;
+ return BSC_CON_SUCCESS;
+}
+
+int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg)
+{
+ osmo_timer_schedule(&conn->sccp_cc_timeout, 10, 0);
+ sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg);
+ msgb_free(msg);
+ return 0;
+}
+
+int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp)
+{
+ if (!sccp)
+ return 0;
+
+ if (sccp->conn)
+ LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n");
+
+ llist_del(&sccp->entry);
+ osmo_timer_del(&sccp->sccp_it_timeout);
+ osmo_timer_del(&sccp->sccp_cc_timeout);
+ talloc_free(sccp);
+ return 0;
+}
+
+static void bsc_notify_msc_lost(struct osmo_bsc_sccp_con *con)
+{
+ struct gsm_subscriber_connection *conn = con->conn;
+
+ /* send USSD notification if string configured and con->data is set */
+ if (!conn)
+ return;
+
+ /* check for config string */
+ if (!con->msc->ussd_msc_lost_txt)
+ return;
+ if (con->msc->ussd_msc_lost_txt[0] == '\0')
+ return;
+
+ /* send USSD notification */
+ bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_msc_lost_txt);
+ bsc_send_ussd_release_complete(conn);
+}
+
+static void bsc_notify_and_close_conns(struct bsc_msc_connection *msc_con)
+{
+ struct osmo_bsc_sccp_con *con, *tmp;
+
+ llist_for_each_entry_safe(con, tmp, &active_connections, entry) {
+ if (con->msc->msc_con != msc_con)
+ continue;
+
+ bsc_notify_msc_lost(con);
+ bsc_sccp_force_free(con);
+ }
+}
+
+static int handle_msc_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct msc_signal_data *msc;
+
+ if (subsys != SS_MSC)
+ return 0;
+
+ msc = signal_data;
+ if (signal == S_MSC_LOST)
+ bsc_notify_and_close_conns(msc->data->msc_con);
+
+ return 0;
+}
+
+int osmo_bsc_sccp_init(struct gsm_network *gsmnet)
+{
+ sccp_set_log_area(DSCCP);
+ sccp_system_init(msc_sccp_write_ipa, gsmnet);
+ sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL);
+ sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet);
+
+ osmo_signal_register_handler(SS_MSC, handle_msc_signal, gsmnet);
+
+ return 0;
+}
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
new file mode 100644
index 000000000..2e2e99bfd
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_vty.c
@@ -0,0 +1,945 @@
+/* Osmo BSC VTY Configuration */
+/* (C) 2009-2015 by Holger Hans Peter Freyther
+ * (C) 2009-2014 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/gsm_data.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/vty.h>
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/bsc_msg_filter.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/vty/logging.h>
+
+#include <time.h>
+
+
+#define IPA_STR "IP.ACCESS specific\n"
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct osmo_bsc_data *osmo_bsc_data(struct vty *vty)
+{
+ return bsc_gsmnet->bsc_data;
+}
+
+static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
+{
+ return vty->index;
+}
+
+static struct cmd_node bsc_node = {
+ BSC_NODE,
+ "%s(config-bsc)# ",
+ 1,
+};
+
+static struct cmd_node msc_node = {
+ MSC_NODE,
+ "%s(config-msc)# ",
+ 1,
+};
+
+DEFUN(cfg_net_msc, cfg_net_msc_cmd,
+ "msc [<0-1000>]", "Configure MSC details\n" "MSC connection to configure\n")
+{
+ int index = argc == 1 ? atoi(argv[0]) : 0;
+ struct bsc_msc_data *msc;
+
+ msc = osmo_msc_data_alloc(bsc_gsmnet, index);
+ if (!msc) {
+ vty_out(vty, "%%Failed to allocate MSC data.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = msc;
+ vty->node = MSC_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc, cfg_net_bsc_cmd,
+ "bsc", "Configure BSC\n")
+{
+ vty->node = BSC_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
+{
+#define WRITE_AMR(vty, msc, name, var) \
+ vty_out(vty, " amr-config %s %s%s", \
+ name, msc->amr_conf.var ? "allowed" : "forbidden", \
+ VTY_NEWLINE);
+
+ WRITE_AMR(vty, msc, "12_2k", m12_2);
+ WRITE_AMR(vty, msc, "10_2k", m10_2);
+ WRITE_AMR(vty, msc, "7_95k", m7_95);
+ WRITE_AMR(vty, msc, "7_40k", m7_40);
+ WRITE_AMR(vty, msc, "6_70k", m6_70);
+ WRITE_AMR(vty, msc, "5_90k", m5_90);
+ WRITE_AMR(vty, msc, "5_15k", m5_15);
+ WRITE_AMR(vty, msc, "4_75k", m4_75);
+#undef WRITE_AMR
+}
+
+static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
+{
+ struct bsc_msc_dest *dest;
+
+ vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
+ if (msc->bsc_token)
+ vty_out(vty, " token %s%s", msc->bsc_token, VTY_NEWLINE);
+ if (msc->bsc_key_present)
+ vty_out(vty, " auth-key %s%s",
+ osmo_hexdump(msc->bsc_key, sizeof(msc->bsc_key)), VTY_NEWLINE);
+ if (msc->core_mnc != -1)
+ vty_out(vty, " core-mobile-network-code %d%s",
+ msc->core_mnc, VTY_NEWLINE);
+ if (msc->core_mcc != -1)
+ vty_out(vty, " core-mobile-country-code %d%s",
+ msc->core_mcc, VTY_NEWLINE);
+ if (msc->core_lac != -1)
+ vty_out(vty, " core-location-area-code %d%s",
+ msc->core_lac, VTY_NEWLINE);
+ if (msc->core_ci != -1)
+ vty_out(vty, " core-cell-identity %d%s",
+ msc->core_ci, VTY_NEWLINE);
+ vty_out(vty, " ip.access rtp-base %d%s", msc->rtp_base, VTY_NEWLINE);
+
+ if (msc->ping_timeout == -1)
+ vty_out(vty, " no timeout-ping%s", VTY_NEWLINE);
+ else {
+ vty_out(vty, " timeout-ping %d%s", msc->ping_timeout, VTY_NEWLINE);
+ vty_out(vty, " timeout-pong %d%s", msc->pong_timeout, VTY_NEWLINE);
+ if (msc->advanced_ping)
+ vty_out(vty, " timeout-ping advanced%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " no timeout-ping advanced%s", VTY_NEWLINE);
+ }
+
+ if (msc->ussd_welcome_txt)
+ vty_out(vty, " bsc-welcome-text %s%s", msc->ussd_welcome_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-welcome-text%s", VTY_NEWLINE);
+
+ if (msc->ussd_msc_lost_txt && msc->ussd_msc_lost_txt[0])
+ vty_out(vty, " bsc-msc-lost-text %s%s", msc->ussd_msc_lost_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-msc-lost-text%s", VTY_NEWLINE);
+
+ if (msc->ussd_grace_txt && msc->ussd_grace_txt[0])
+ vty_out(vty, " bsc-grace-text %s%s", msc->ussd_grace_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no bsc-grace-text%s", VTY_NEWLINE);
+
+ if (msc->audio_length != 0) {
+ int i;
+
+ vty_out(vty, " codec-list ");
+ for (i = 0; i < msc->audio_length; ++i) {
+ if (i != 0)
+ vty_out(vty, " ");
+
+ if (msc->audio_support[i]->hr)
+ vty_out(vty, "hr%.1u", msc->audio_support[i]->ver);
+ else
+ vty_out(vty, "fr%.1u", msc->audio_support[i]->ver);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ }
+
+ llist_for_each_entry(dest, &msc->dests, list)
+ vty_out(vty, " dest %s %d %d%s", dest->ip, dest->port,
+ dest->dscp, VTY_NEWLINE);
+
+ vty_out(vty, " type %s%s", msc->type == MSC_CON_TYPE_NORMAL ?
+ "normal" : "local", VTY_NEWLINE);
+ vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
+ "allow" : "deny", VTY_NEWLINE);
+
+ if (msc->local_pref)
+ vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE);
+
+ if (msc->acc_lst_name)
+ vty_out(vty, " access-list-name %s%s", msc->acc_lst_name, VTY_NEWLINE);
+
+ /* write amr options */
+ write_msc_amr_options(vty, msc);
+}
+
+static int config_write_msc(struct vty *vty)
+{
+ struct bsc_msc_data *msc;
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ llist_for_each_entry(msc, &bsc->mscs, entry)
+ write_msc(vty, msc);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_bsc(struct vty *vty)
+{
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ vty_out(vty, "bsc%s", VTY_NEWLINE);
+ if (bsc->mid_call_txt)
+ vty_out(vty, " mid-call-text %s%s", bsc->mid_call_txt, VTY_NEWLINE);
+ vty_out(vty, " mid-call-timeout %d%s", bsc->mid_call_timeout, VTY_NEWLINE);
+ if (bsc->rf_ctrl_name)
+ vty_out(vty, " bsc-rf-socket %s%s",
+ bsc->rf_ctrl_name, VTY_NEWLINE);
+
+ if (bsc->auto_off_timeout != -1)
+ vty_out(vty, " bsc-auto-rf-off %d%s",
+ bsc->auto_off_timeout, VTY_NEWLINE);
+
+ if (bsc->ussd_no_msc_txt && bsc->ussd_no_msc_txt[0])
+ vty_out(vty, " missing-msc-text %s%s", bsc->ussd_no_msc_txt, VTY_NEWLINE);
+ else
+ vty_out(vty, " no missing-msc-text%s", VTY_NEWLINE);
+ if (bsc->acc_lst_name)
+ vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_token,
+ cfg_net_bsc_token_cmd,
+ "token TOKEN",
+ "A token for the BSC to be sent to the MSC\n" "A token\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->bsc_token, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_key,
+ cfg_net_bsc_key_cmd,
+ "auth-key KEY",
+ "Authentication (secret) key configuration\n"
+ "Security key\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ osmo_hexparse(argv[0], data->bsc_key, sizeof(data->bsc_key));
+ data->bsc_key_present = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_bsc_key, cfg_net_bsc_no_key_cmd,
+ "no auth-key",
+ NO_STR "Authentication (secret) key configuration\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ memset(data->bsc_key, 0, sizeof(data->bsc_key));
+ data->bsc_key_present = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ncc,
+ cfg_net_bsc_ncc_cmd,
+ "core-mobile-network-code <1-999>",
+ "Use this network code for the core network\n" "MNC value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_mnc = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mcc,
+ cfg_net_bsc_mcc_cmd,
+ "core-mobile-country-code <1-999>",
+ "Use this country code for the core network\n" "MCC value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_mcc = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_lac,
+ cfg_net_bsc_lac_cmd,
+ "core-location-area-code <0-65535>",
+ "Use this location area code for the core network\n" "LAC value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_lac = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_ci,
+ cfg_net_bsc_ci_cmd,
+ "core-cell-identity <0-65535>",
+ "Use this cell identity for the core network\n" "CI value\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->core_ci = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_rtp_base,
+ cfg_net_bsc_rtp_base_cmd,
+ "ip.access rtp-base <1-65000>",
+ IPA_STR
+ "Set the rtp-base port for the RTP stream\n"
+ "Port number\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->rtp_base = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_codec_list,
+ cfg_net_bsc_codec_list_cmd,
+ "codec-list .LIST",
+ "Set the allowed audio codecs\n"
+ "List of audio codecs, e.g. fr3 fr1 hr3\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ int saw_fr, saw_hr;
+ int i;
+
+ saw_fr = saw_hr = 0;
+
+ /* free the old list... if it exists */
+ if (data->audio_support) {
+ talloc_free(data->audio_support);
+ data->audio_support = NULL;
+ data->audio_length = 0;
+ }
+
+ /* create a new array */
+ data->audio_support =
+ talloc_zero_array(osmo_bsc_data(vty), struct gsm_audio_support *, argc);
+ data->audio_length = argc;
+
+ for (i = 0; i < argc; ++i) {
+ /* check for hrX or frX */
+ if (strlen(argv[i]) != 3
+ || argv[i][1] != 'r'
+ || (argv[i][0] != 'h' && argv[i][0] != 'f')
+ || argv[i][2] < 0x30
+ || argv[i][2] > 0x39)
+ goto error;
+
+ data->audio_support[i] = talloc_zero(data->audio_support,
+ struct gsm_audio_support);
+ data->audio_support[i]->ver = atoi(argv[i] + 2);
+
+ if (strncmp("hr", argv[i], 2) == 0) {
+ data->audio_support[i]->hr = 1;
+ saw_hr = 1;
+ } else if (strncmp("fr", argv[i], 2) == 0) {
+ data->audio_support[i]->hr = 0;
+ saw_fr = 1;
+ }
+
+ if (saw_hr && saw_fr) {
+ vty_out(vty, "Can not have full-rate and half-rate codec.%s",
+ VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+
+ return CMD_SUCCESS;
+
+error:
+ vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
+ argv[i], VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+}
+
+DEFUN(cfg_net_msc_dest,
+ cfg_net_msc_dest_cmd,
+ "dest A.B.C.D <1-65000> <0-255>",
+ "Add a destination to a MUX/MSC\n"
+ "IP Address\n" "Port\n" "DSCP\n")
+{
+ struct bsc_msc_dest *dest;
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ dest = talloc_zero(osmo_bsc_data(vty), struct bsc_msc_dest);
+ if (!dest) {
+ vty_out(vty, "%%Failed to create structure.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ dest->ip = talloc_strdup(dest, argv[0]);
+ if (!dest->ip) {
+ vty_out(vty, "%%Failed to copy dest ip.%s", VTY_NEWLINE);
+ talloc_free(dest);
+ return CMD_WARNING;
+ }
+
+ dest->port = atoi(argv[1]);
+ dest->dscp = atoi(argv[2]);
+ llist_add_tail(&dest->list, &data->dests);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_dest,
+ cfg_net_msc_no_dest_cmd,
+ "no dest A.B.C.D <1-65000> <0-255>",
+ NO_STR "Remove a destination to a MUX/MSC\n"
+ "IP Address\n" "Port\n" "DSCP\n")
+{
+ struct bsc_msc_dest *dest, *tmp;
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ int port = atoi(argv[1]);
+ int dscp = atoi(argv[2]);
+
+ llist_for_each_entry_safe(dest, tmp, &data->dests, list) {
+ if (port != dest->port || dscp != dest->dscp
+ || strcmp(dest->ip, argv[0]) != 0)
+ continue;
+
+ llist_del(&dest->list);
+ talloc_free(dest);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_ping_time,
+ cfg_net_msc_no_ping_time_cmd,
+ "no timeout-ping",
+ NO_STR "Disable the ping/pong handling on A-link\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->ping_timeout = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_ping_time,
+ cfg_net_msc_ping_time_cmd,
+ "timeout-ping <1-2147483647>",
+ "Set the PING interval, negative for not sending PING\n"
+ "Timeout in seconds\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->ping_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_pong_time,
+ cfg_net_msc_pong_time_cmd,
+ "timeout-pong <1-2147483647>",
+ "Set the time to wait for a PONG\n" "Timeout in seconds\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->pong_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_advanced_ping,
+ cfg_net_msc_advanced_ping_cmd,
+ "timeout-ping advanced",
+ "Ping timeout handling\nEnable advanced mode during SCCP\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ if (data->ping_timeout == -1) {
+ vty_out(vty, "%%ping handling is disabled. Enable it first.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ data->advanced_ping = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_net_msc_advanced_ping,
+ cfg_no_net_msc_advanced_ping_cmd,
+ "no timeout-ping advanced",
+ NO_STR "Ping timeout handling\nEnable advanced mode during SCCP\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->advanced_ping = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_welcome_ussd,
+ cfg_net_msc_welcome_ussd_cmd,
+ "bsc-welcome-text .TEXT",
+ "Set the USSD notification to be sent\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_welcome_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_welcome_ussd,
+ cfg_net_msc_no_welcome_ussd_cmd,
+ "no bsc-welcome-text",
+ NO_STR "Clear the USSD notification to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_welcome_txt);
+ data->ussd_welcome_txt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_lost_ussd,
+ cfg_net_msc_lost_ussd_cmd,
+ "bsc-msc-lost-text .TEXT",
+ "Set the USSD notification to be sent on MSC connection loss\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_msc_lost_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_lost_ussd,
+ cfg_net_msc_no_lost_ussd_cmd,
+ "no bsc-msc-lost-text",
+ NO_STR "Clear the USSD notification to be sent on MSC connection loss\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_msc_lost_txt);
+ data->ussd_msc_lost_txt = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_grace_ussd,
+ cfg_net_msc_grace_ussd_cmd,
+ "bsc-grace-text .TEXT",
+ "Set the USSD notification to be sent when the MSC has entered the grace period\n" "Text to be sent\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ char *str = argv_concat(argv, argc, 0);
+ if (!str)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_grace_txt, str);
+ talloc_free(str);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_no_grace_ussd,
+ cfg_net_msc_no_grace_ussd_cmd,
+ "no bsc-grace-text",
+ NO_STR "Clear the USSD notification to be sent when the MSC has entered the grace period\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ talloc_free(data->ussd_grace_txt);
+ data->ussd_grace_txt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_missing_msc_ussd,
+ cfg_net_bsc_missing_msc_ussd_cmd,
+ "missing-msc-text .TEXT",
+ "Set the USSD notification to be send when a MSC has not been found.\n" "Text to be sent\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(data, &data->ussd_no_msc_txt, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_no_missing_msc_text,
+ cfg_net_bsc_no_missing_msc_text_cmd,
+ "no missing-msc-text",
+ NO_STR "Clear the USSD notification to be send when a MSC has not been found.\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+ talloc_free(data->ussd_no_msc_txt);
+ data->ussd_no_msc_txt = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_msc_type,
+ cfg_net_msc_type_cmd,
+ "type (normal|local)",
+ "Select the MSC type\n"
+ "Plain GSM MSC\n" "Special MSC for local call routing\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+
+ if (strcmp(argv[0], "normal") == 0)
+ data->type = MSC_CON_TYPE_NORMAL;
+ else if (strcmp(argv[0], "local") == 0)
+ data->type = MSC_CON_TYPE_LOCAL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_emerg,
+ cfg_net_msc_emerg_cmd,
+ "allow-emergency (allow|deny)",
+ "Allow CM ServiceRequests with type emergency\n"
+ "Allow\n" "Deny\n")
+{
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->allow_emerg = strcmp("allow", argv[0]) == 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_msc_local_prefix,
+ cfg_net_msc_local_prefix_cmd,
+ "local-prefix REGEXP",
+ "Prefix for local numbers\n" "REGEXP used\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) {
+ vty_out(vty, "%%Failed to parse the regexp: '%s'%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define AMR_CONF_STR "AMR Multirate Configuration\n"
+#define AMR_COMMAND(name) \
+ DEFUN(cfg_net_msc_amr_##name, \
+ cfg_net_msc_amr_##name##_cmd, \
+ "amr-config " #name "k (allowed|forbidden)", \
+ AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n") \
+{ \
+ struct bsc_msc_data *msc = bsc_msc_data(vty); \
+ \
+ msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; \
+ return CMD_SUCCESS; \
+}
+
+AMR_COMMAND(12_2)
+AMR_COMMAND(10_2)
+AMR_COMMAND(7_95)
+AMR_COMMAND(7_40)
+AMR_COMMAND(6_70)
+AMR_COMMAND(5_90)
+AMR_COMMAND(5_15)
+AMR_COMMAND(4_75)
+
+DEFUN(cfg_msc_acc_lst_name,
+ cfg_msc_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_msc_data *msc = bsc_msc_data(vty);
+
+ osmo_talloc_replace_string(msc, &msc->acc_lst_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_msc_no_acc_lst_name,
+ cfg_msc_no_acc_lst_name_cmd,
+ "no access-list-name",
+ NO_STR "Remove the access list from the NAT.\n")
+{
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+
+ if (msc->acc_lst_name) {
+ talloc_free(msc->acc_lst_name);
+ msc->acc_lst_name = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_text,
+ cfg_net_bsc_mid_call_text_cmd,
+ "mid-call-text .TEXT",
+ "Set the USSD notification to be send.\n" "Text to be sent\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ char *txt = argv_concat(argv, argc, 0);
+ if (!txt)
+ return CMD_WARNING;
+
+ osmo_talloc_replace_string(data, &data->mid_call_txt, txt);
+ talloc_free(txt);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_bsc_mid_call_timeout,
+ cfg_net_bsc_mid_call_timeout_cmd,
+ "mid-call-timeout NR",
+ "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->mid_call_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_socket,
+ cfg_net_rf_socket_cmd,
+ "bsc-rf-socket PATH",
+ "Set the filename for the RF control interface.\n" "RF Control path\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+
+ osmo_talloc_replace_string(data, &data->rf_ctrl_name, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_rf_off_time,
+ cfg_net_rf_off_time_cmd,
+ "bsc-auto-rf-off <1-65000>",
+ "Disable RF on MSC Connection\n" "Timeout\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->auto_off_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_no_rf_off_time,
+ cfg_net_no_rf_off_time_cmd,
+ "no bsc-auto-rf-off",
+ NO_STR "Disable RF on MSC Connection\n")
+{
+ struct osmo_bsc_data *data = osmo_bsc_data(vty);
+ data->auto_off_timeout = -1;
+ 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 osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ osmo_talloc_replace_string(bsc, &bsc->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 "Remove the access list from the BSC\n")
+{
+ struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
+
+ if (bsc->acc_lst_name) {
+ talloc_free(bsc->acc_lst_name);
+ bsc->acc_lst_name = NULL;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_statistics,
+ show_statistics_cmd,
+ "show statistics",
+ SHOW_STR "Statistics about the BSC\n")
+{
+ openbsc_vty_print_statistics(vty, bsc_gsmnet);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mscs,
+ show_mscs_cmd,
+ "show mscs",
+ SHOW_STR "MSC Connections and State\n")
+{
+ struct bsc_msc_data *msc;
+ llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
+ vty_out(vty, "MSC Nr: %d is connected: %d auth: %d.%s",
+ msc->nr,
+ msc->msc_con ? msc->msc_con->is_connected : -1,
+ msc->msc_con ? msc->msc_con->is_authenticated : -1,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_pos,
+ show_pos_cmd,
+ "show position",
+ SHOW_STR "Position information of the BTS\n")
+{
+ struct gsm_bts *bts;
+ struct bts_location *curloc;
+ struct tm time;
+ char timestr[50];
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ if (llist_empty(&bts->loc_list)) {
+ vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ if (gmtime_r(&curloc->tstamp, &time) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ if (asctime_r(&time, timestr) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ /* Last character in asctime is \n */
+ timestr[strlen(timestr)-1] = 0;
+
+ vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr,
+ get_value_string(bts_loc_fix_names, curloc->valid), timestr,
+ VTY_NEWLINE);
+ vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon,
+ curloc->height, VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(gen_position_trap,
+ gen_position_trap_cmd,
+ "generate-location-state-trap <0-255>",
+ "Generate location state report\n"
+ "BTS to report\n")
+{
+ int bts_nr;
+ struct gsm_bts *bts;
+ struct gsm_network *net = bsc_gsmnet;
+
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(net, bts_nr);
+ bsc_gen_location_state_trap(bts);
+ return CMD_SUCCESS;
+}
+
+DEFUN(logging_fltr_imsi,
+ logging_fltr_imsi_cmd,
+ "logging filter imsi IMSI",
+ LOGGING_STR FILTER_STR
+ "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+ struct bsc_subscr *bsc_subscr;
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ const char *imsi = argv[0];
+
+ bsc_subscr = bsc_subscr_find_by_imsi(bsc_gsmnet->bsc_subscribers, imsi);
+
+ if (!bsc_subscr) {
+ vty_out(vty, "%%no subscriber with IMSI(%s)%s",
+ imsi, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ log_set_filter_bsc_subscr(tgt, bsc_subscr);
+ return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+ install_element(CONFIG_NODE, &cfg_net_msc_cmd);
+ install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
+
+ install_node(&bsc_node, config_write_bsc);
+ vty_install_default(BSC_NODE);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_socket_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd);
+ install_element(BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
+ install_element(BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
+
+ install_node(&msc_node, config_write_msc);
+ vty_install_default(MSC_NODE);
+ install_element(MSC_NODE, &cfg_net_bsc_token_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_key_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_no_key_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_lac_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_ci_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd);
+ install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_advanced_ping_cmd);
+ install_element(MSC_NODE, &cfg_no_net_msc_advanced_ping_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_type_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd);
+ install_element(MSC_NODE, &cfg_msc_acc_lst_name_cmd);
+ install_element(MSC_NODE, &cfg_msc_no_acc_lst_name_cmd);
+
+ install_element_ve(&show_statistics_cmd);
+ install_element_ve(&show_mscs_cmd);
+ install_element_ve(&show_pos_cmd);
+ install_element_ve(&logging_fltr_imsi_cmd);
+
+ install_element(ENABLE_NODE, &gen_position_trap_cmd);
+
+ install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+ return 0;
+}