diff options
Diffstat (limited to 'openbsc/src/osmo-bsc')
-rw-r--r-- | openbsc/src/osmo-bsc/Makefile.am | 14 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_api.c | 174 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_audio.c | 70 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_bssap.c | 548 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_filter.c | 170 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_grace.c | 107 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_main.c | 261 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_msc.c | 368 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_rf.c | 364 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_sccp.c | 288 | ||||
-rw-r--r-- | openbsc/src/osmo-bsc/osmo_bsc_vty.c | 311 |
11 files changed, 2675 insertions, 0 deletions
diff --git a/openbsc/src/osmo-bsc/Makefile.am b/openbsc/src/osmo-bsc/Makefile.am new file mode 100644 index 000000000..735d440a1 --- /dev/null +++ b/openbsc/src/osmo-bsc/Makefile.am @@ -0,0 +1,14 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(COVERAGE_CFLAGS) +AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAGE_LDFLAGS) + +bin_PROGRAMS = osmo-bsc + + +osmo_bsc_SOURCES = osmo_bsc_main.c osmo_bsc_rf.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 +# once again since TRAU uses CC symbol :( +osmo_bsc_LDADD = ../bsc/libbsc.a ../msc/libmsc.a ../bsc/libbsc.a \ + ../abis/libabis.a ../trau/libtrau.a ../common/libcommon.a \ + $(LIBOSMOSCCP_LIBS) diff --git a/openbsc/src/osmo-bsc/osmo_bsc_api.c b/openbsc/src/osmo-bsc/osmo_bsc_api.c new file mode 100644 index 000000000..b8cbcf2f3 --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_api.c @@ -0,0 +1,174 @@ +/* (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/osmo_bsc.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/debug.h> + +#include <osmocore/protocol/gsm_08_08.h> +#include <osmocore/gsm0808.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 uint16_t get_network_code_for_msc(struct gsm_network *net) +{ + if (net->msc_data->core_ncc != -1) + return net->msc_data->core_ncc; + return net->network_code; +} + +static uint16_t get_country_code_for_msc(struct gsm_network *net) +{ + if (net->msc_data->core_mcc != -1) + return net->msc_data->core_mcc; + return net->country_code; +} + +static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) +{ + struct msgb *resp; + return_when_not_connected(conn); + + 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); +} + +/* + * 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 msgb *resp; + uint16_t network_code = get_network_code_for_msc(conn->bts->network); + uint16_t country_code = get_country_code_for_msc(conn->bts->network); + + /* allocate resource for a new connection */ + if (bsc_create_new_connection(conn) != 0) + return BSC_API_CONN_POL_REJECT; + + bsc_scan_bts_msg(conn, msg); + resp = gsm0808_create_layer3(msg, network_code, country_code, + conn->bts->location_area_code, + conn->bts->cell_identity); + if (!resp) { + LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n"); + bsc_delete_connection(conn->sccp_con); + return BSC_API_CONN_POL_REJECT; + } + + if (bsc_open_connection(conn->sccp_con, resp) != 0) { + bsc_delete_connection(conn->sccp_con); + msgb_free(resp); + return BSC_API_CONN_POL_REJECT; + } + + return BSC_API_CONN_POL_ACCEPT; +} + +static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +{ + struct msgb *resp; + return_when_not_connected(conn); + + 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); + + 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); + + 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 msgb *resp; + return_when_not_connected_val(conn, 1); + + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); + return 0; + } + + bsc_queue_for_msc(conn->sccp_con, resp); + return 0; +} + +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, +}; + +struct bsc_api *osmo_bsc_api() +{ + return &bsc_handler; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_audio.c b/openbsc/src/osmo-bsc/osmo_bsc_audio.c new file mode 100644 index 000000000..515cfa7fb --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_audio.c @@ -0,0 +1,70 @@ +/* + * 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/osmo_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: + /* 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) +{ + net->hardcoded_rtp_payload = 98; + register_signal_handler(SS_ABISIP, handle_abisip_signal, net); + return 0; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_bssap.c b/openbsc/src/osmo-bsc/osmo_bsc_bssap.c new file mode 100644 index 000000000..f8711314d --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_bssap.c @@ -0,0 +1,548 @@ +/* GSM 08.08 BSSMAP 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/osmo_bsc.h> +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/mgcp.h> +#include <openbsc/paging.h> + +#include <osmocore/gsm0808.h> +#include <osmocore/protocol/gsm_08_08.h> + +#include <arpa/inet.h> + +static uint16_t read_data16(const uint8_t *data) +{ + uint16_t res; + + memcpy(&res, data, sizeof(res)); + return res; +} + +/* + * 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 gsm_network *net, + 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 gsm_network *net, + struct msgb *msg, unsigned int payload_length) +{ + struct gsm_subscriber *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, "Mandantory 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, "Mandantory CELL IDENTIFIER LIST not present.\n"); + return -1; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI)) { + gsm48_mi_to_string(mi_string, sizeof(mi_string), + TLVP_VAL(&tp, GSM0808_IE_TMSI), TLVP_LEN(&tp, GSM0808_IE_TMSI)); + tmsi = strtoul(mi_string, NULL, 10); + } + + + /* + * 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 = ntohs(read_data16(&data[1])); + } else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) { + LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", 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 = subscr_get_or_create(net, 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_DEBUG, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac); + paging_request(net, subscr, chan_needed, NULL, NULL); + 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_DEBUG, "Releasing all transactions on %p\n", conn); + gsm0808_clear(conn->conn); + 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; + } + +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 gsm_network *network; + struct tlv_parsed tp; + uint8_t *data; + uint16_t cic; + 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; + } + + network = conn->conn->bts->network; + 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, "Mandantory 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; + } + + cic = ntohs(read_data16(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE))); + timeslot = cic & 0x1f; + multiplex = (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; + } + + if (data[1] != GSM0808_SPEECH_FULL_PREF && data[1] != GSM0808_SPEECH_HALF_PREF) { + LOGP(DMSC, LOGL_ERROR, "ChannelType full not allowed: %d\n", data[1]); + 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; + for (supported = 0; + chan_mode == GSM48_CMODE_SIGN && supported < network->msc_data->audio_length; + ++supported) { + + int perm_val = audio_support_to_gsm88(network->msc_data->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, + network->msc_data->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 gsm_network *net, + struct msgb *msg, unsigned int length) +{ + int ret = 0; + + if (length < 1) { + LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length); + return -1; + } + + switch (msg->l4h[0]) { + case BSS_MAP_MSG_RESET_ACKNOWLEDGE: + ret = bssmap_handle_reset_ack(net, msg, length); + break; + case BSS_MAP_MSG_PAGING: + if (bsc_grace_allow_new_connection(net)) + ret = bssmap_handle_paging(net, 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; + } + + 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_DEBUG, "Unimplemented msg type: %d\n", 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; + + 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: %u got: %u\n", sizeof(*header), length); + LOGP(DMSC, LOGL_ERROR, "hex: %s\n", 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", hexdump(msg->l3h, length)); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "DTAP message: SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0); + + /* forward the data */ + gsm48 = gsm48_msgb_alloc(); + 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 */ + bsc_scan_msc_msg(conn->conn, gsm48); + return gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1); +} + +int bsc_handle_udt(struct gsm_network *network, + struct bsc_msc_connection *conn, + struct msgb *msgb, unsigned int length) +{ + struct bssmap_header *bs; + + LOGP(DMSC, LOGL_DEBUG, "Incoming SCCP message ftom MSC: %s\n", + 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(network, msgb, length - sizeof(*bs)); + break; + default: + LOGP(DMSC, LOGL_ERROR, "Unimplemented msg type: %d\n", 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_DEBUG, "Unimplemented msg type: %d\n", msg->l3h[0]); + } + + return -1; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_filter.c b/openbsc/src/osmo-bsc/osmo_bsc_filter.c new file mode 100644 index 000000000..d2735a63a --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_filter.c @@ -0,0 +1,170 @@ +/* (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/osmo_bsc.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/gsm_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; + } +} + +/* we will need to stop the paging request */ +static int handle_page_resp(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 gsm_subscriber *subscr; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) { + LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg)); + return -1; + } + + 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_type=0x%02x MI(%s)\n", + mi_type, mi_string); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + subscr = subscr_active_by_tmsi(conn->bts->network, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + subscr = subscr_active_by_imsi(conn->bts->network, mi_string); + break; + default: + subscr = NULL; + break; + } + + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n"); + return -1; + } + + paging_request_stop(conn->bts, subscr, conn, msg); + subscr_put(subscr); + return 0; +} + +/** + * 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 = gh->proto_discr & 0x0f; + uint8_t mtype = gh->msg_type & 0xbf; + + 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 void send_welcome_ussd(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net; + net = conn->bts->network; + + if (!net->msc_data->ussd_welcome_txt) + return; + + gsm0480_send_ussdNotify(conn, 1, net->msc_data->ussd_welcome_txt); + gsm0480_send_releaseComplete(conn); +} + +/** + * Messages coming back from the MSC. + */ +int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm_network *net; + struct gsm48_loc_area_id *lai; + struct gsm48_hdr *gh; + uint8_t mtype; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n"); + return -1; + } + + gh = (struct gsm48_hdr *) msgb_l3(msg); + mtype = gh->msg_type & 0xbf; + net = conn->bts->network; + + if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) { + if (net->msc_data->core_ncc != -1 || + net->msc_data->core_mcc != -1) { + if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) { + 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) + send_welcome_ussd(conn); + } + + return 0; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_grace.c b/openbsc/src/osmo-bsc/osmo_bsc_grace.c new file mode 100644 index 000000000..f699cf39c --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_grace.c @@ -0,0 +1,107 @@ +/* + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/signal.h> + +int bsc_grace_allow_new_connection(struct gsm_network *network) +{ + if (!network->msc_data->rf_ctl) + return 1; + return network->msc_data->rf_ctl->policy == S_RF_ON; +} + +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; + + gsm0480_send_ussdNotify(conn, 0, text); + gsm0480_send_releaseComplete(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->msc_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->msc_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) +{ + register_signal_handler(SS_RF, handle_rf_signal, NULL); +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_main.c b/openbsc/src/osmo-bsc/osmo_bsc_main.c new file mode 100644 index 000000000..18756e52f --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_main.c @@ -0,0 +1,261 @@ +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (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/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/signal.h> +#include <openbsc/vty.h> + +#include <osmocore/talloc.h> +#include <osmocore/process.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" + +static struct log_target *stderr_target; +struct gsm_network *bsc_gsmnet = 0; +static const char *config_file = "openbsc.cfg"; +static const char *rf_ctl = NULL; +extern const char *openbsc_copyright; +static int daemonize = 0; + +extern int bsc_bootstrap_network(int (*layer4)(struct gsm_network *, struct msgb *), const char *cfg_file); + +static void print_usage() +{ + printf("Usage: bsc_msc_ip\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(stderr_target, 0); + break; + case 'd': + log_parse_category_mask(stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'P': + ipacc_rtp_direct = 0; + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + case 'r': + rf_ctl = optarg; + break; + default: + /* ignore */ + break; + } + } +} + +extern int bts_model_unknown_init(void); +extern int bts_model_bs11_init(void); +extern int bts_model_nanobts_init(void); + +extern enum node_type 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) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + bsc_shutdown_net(bsc_gsmnet); + dispatch_signal(SS_GLOBAL, S_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->msc_data) + return; + if (!bsc_gsmnet->msc_data->msc_con) + return; + if (!bsc_gsmnet->msc_data->msc_con->is_connected) + return; + bsc_msc_lost(bsc_gsmnet->msc_data->msc_con); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + int rc; + + log_init(&log_info); + tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc"); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + + bts_model_unknown_init(); + bts_model_bs11_init(); + bts_model_nanobts_init(); + + /* enable filters */ + log_set_all_filter(stderr_target, 1); + + /* This needs to precede handle_options() */ + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + bsc_vty_init(); + + /* parse options */ + handle_options(argc, argv); + + /* seed the PRNG */ + srand(time(NULL)); + + /* initialize SCCP */ + sccp_set_log_area(DSCCP); + + + rc = bsc_bootstrap_network(NULL, config_file); + if (rc < 0) { + fprintf(stderr, "Bootstrapping the network failed. exiting.\n"); + exit(1); + } + bsc_api_init(bsc_gsmnet, osmo_bsc_api()); + + if (rf_ctl) { + struct osmo_msc_data *data = bsc_gsmnet->msc_data; + data->rf_ctl = osmo_bsc_rf_create(rf_ctl, bsc_gsmnet); + if (!data->rf_ctl) { + fprintf(stderr, "Failed to create the RF service.\n"); + exit(1); + } + } + + if (osmo_bsc_msc_init(bsc_gsmnet) != 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); + signal(SIGPIPE, SIG_IGN); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + bsc_select_main(0); + } + + return 0; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_msc.c b/openbsc/src/osmo-bsc/osmo_bsc_msc.c new file mode 100644 index 000000000..2e8cf0579 --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_msc.c @@ -0,0 +1,368 @@ +/* + * Handle the connection to the MSC. This include ping/timeout/reconnect + * (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (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_nat.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/ipaccess.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/signal.h> + +#include <osmocore/gsm0808.h> + +#include <osmocom/sccp/sccp.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_id_get_response(struct osmo_msc_data *data, int fd); +static void send_ping(struct osmo_msc_data *data); + +/* + * MGCP forwarding code + */ +static int mgcp_do_read(struct bsc_fd *fd) +{ + struct osmo_msc_data *data = (struct osmo_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 bsc_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 osmo_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 (write_queue_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 osmo_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; + } + + write_queue_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 (bsc_register_fd(&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) +{ + ipaccess_prepend_header(msg, proto); + if (write_queue_enqueue(&conn->write_queue, msg) != 0) { + LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto); + msgb_free(msg); + return -1; + } + + return 0; +} + +static int msc_alink_do_write(struct bsc_fd *fd, struct msgb *msg) +{ + int ret; + + LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg)); + LOGP(DMI, LOGL_DEBUG, "MSC TX %s\n", 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 int ipaccess_a_fd_cb(struct bsc_fd *bfd) +{ + int error; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + struct osmo_msc_data *data = (struct osmo_msc_data *) bfd->data; + + if (!msg) { + if (error == 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", error); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "From MSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]); + + /* handle base message handling */ + hh = (struct ipaccess_head *) msg->data; + ipaccess_rcvmsg_base(msg, bfd); + + /* initialize the networking. This includes sending a GSM08.08 message */ + if (hh->proto == IPAC_PROTO_IPACCESS) { + 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); + } else if (msg->l2h[0] == IPAC_MSGT_PONG) { + bsc_del_timer(&data->pong_timer); + } + } else if (hh->proto == IPAC_PROTO_SCCP) { + sccp_system_incoming(msg); + } else if (hh->proto == IPAC_PROTO_MGCP_OLD) { + mgcp_forward(data, msg); + } + + msgb_free(msg); + return 0; +} + +static void send_ping(struct osmo_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 msc_ping_timeout_cb(void *_data) +{ + struct osmo_msc_data *data = (struct osmo_msc_data *) _data; + if (data->ping_timeout < 0) + return; + + send_ping(data); + + /* send another ping in 20 seconds */ + bsc_schedule_timer(&data->ping_timer, data->ping_timeout, 0); + + /* also start a pong timer */ + bsc_schedule_timer(&data->pong_timer, data->pong_timeout, 0); +} + +static void msc_pong_timeout_cb(void *_data) +{ + struct osmo_msc_data *data = (struct osmo_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 osmo_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 osmo_msc_data *) con->write_queue.bfd.data; + msc_ping_timeout_cb(data); + + sig.data = data; + dispatch_signal(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 osmo_msc_data *data; + + LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n"); + + data = (struct osmo_msc_data *) msc->write_queue.bfd.data; + bsc_del_timer(&data->ping_timer); + bsc_del_timer(&data->pong_timer); + + sig.data = data; + dispatch_signal(SS_MSC, S_MSC_LOST, &sig); + + msc->is_authenticated = 0; + bsc_msc_schedule_connect(msc); +} + +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); + msgb_free(msg); + conn->is_authenticated = 1; + } +} + +static void send_id_get_response(struct osmo_msc_data *data, int fd) +{ + struct msgb *msg; + + msg = bsc_msc_id_get_resp(data->bsc_token); + if (!msg) + return; + msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS); +} + +int osmo_bsc_msc_init(struct gsm_network *network) +{ + struct osmo_msc_data *data = network->msc_data; + + if (mgcp_create_port(data) != 0) + return -1; + + data->msc_con = bsc_msc_create(data->msc_ip, + data->msc_port, + data->msc_ip_dscp); + if (!data->msc_con) { + LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n"); + return -1; + } + + data->ping_timer.cb = msc_ping_timeout_cb; + data->ping_timer.data = data; + data->pong_timer.cb = msc_pong_timeout_cb; + data->pong_timer.data = 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; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_rf.c b/openbsc/src/osmo-bsc/osmo_bsc_rf.c new file mode 100644 index 000000000..5652c9df5 --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_rf.c @@ -0,0 +1,364 @@ +/* RF Ctl handling socket */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/signal.h> +#include <openbsc/osmo_msc_data.h> + +#include <osmocore/talloc.h> +#include <osmocore/protocol/gsm_12_21.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include <errno.h> +#include <unistd.h> + +#define RF_CMD_QUERY '?' +#define RF_CMD_OFF '0' +#define RF_CMD_ON '1' +#define RF_CMD_D_OFF 'd' +#define RF_CMD_ON_G 'g' + +static int lock_each_trx(struct gsm_network *net, int lock) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_trx_lock_rf(trx, lock); + } + } + + return 0; +} + +static void send_resp(struct osmo_bsc_rf_conn *conn, char send) +{ + struct msgb *msg; + + msg = msgb_alloc(10, "RF Query"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate response msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = send; + + if (write_queue_enqueue(&conn->queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); + msgb_free(msg); + return; + } + + return; +} + + +/* + * Send a + * 'g' when we are in grace mode + * '1' when one TRX is online, + * '0' otherwise + */ +static void handle_query(struct osmo_bsc_rf_conn *conn) +{ + struct gsm_bts *bts; + char send = RF_CMD_OFF; + + if (conn->rf->policy == S_RF_GRACE) + return send_resp(conn, RF_CMD_ON_G); + + llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nm_state.availability == NM_AVSTATE_OK && + trx->nm_state.operational != NM_STATE_LOCKED) { + send = RF_CMD_ON; + break; + } + } + } + + send_resp(conn, send); +} + +static void rf_check_cb(void *_data) +{ + struct gsm_bts *bts; + struct osmo_bsc_rf *rf = _data; + + llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* don't bother to check a booting or missing BTS */ + if (!bts->oml_link || !is_ipaccess_bts(bts)) + continue; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nm_state.availability != NM_AVSTATE_OK || + trx->nm_state.operational != NM_OPSTATE_ENABLED || + trx->nm_state.administrative != NM_STATE_UNLOCKED) { + LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); + ipaccess_drop_oml(bts); + break; + } + } + } +} + +static void send_signal(struct osmo_bsc_rf *rf, int val) +{ + struct rf_signal_data sig; + sig.net = rf->gsm_network; + + rf->policy = val; + dispatch_signal(SS_RF, val, &sig); +} + +static int switch_rf_off(struct osmo_bsc_rf *rf) +{ + lock_each_trx(rf->gsm_network, 1); + send_signal(rf, S_RF_OFF); + + return 0; +} + +static void grace_timeout(void *_data) +{ + struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; + + LOGP(DINP, LOGL_NOTICE, "Grace timeout. Disabling the TRX.\n"); + switch_rf_off(rf); +} + +static int enter_grace(struct osmo_bsc_rf *rf) +{ + rf->grace_timeout.cb = grace_timeout; + rf->grace_timeout.data = rf; + bsc_schedule_timer(&rf->grace_timeout, rf->gsm_network->msc_data->mid_call_timeout, 0); + LOGP(DINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", + rf->gsm_network->msc_data->mid_call_timeout); + + send_signal(rf, S_RF_GRACE); + return 0; +} + +static void rf_delay_cmd_cb(void *data) +{ + struct osmo_bsc_rf *rf = data; + + switch (rf->last_request) { + case RF_CMD_D_OFF: + rf->last_state_command = "RF Direct Off"; + bsc_del_timer(&rf->rf_check); + bsc_del_timer(&rf->grace_timeout); + switch_rf_off(rf); + break; + case RF_CMD_ON: + rf->last_state_command = "RF Direct On"; + bsc_del_timer(&rf->grace_timeout); + lock_each_trx(rf->gsm_network, 0); + send_signal(rf, S_RF_ON); + bsc_schedule_timer(&rf->rf_check, 3, 0); + break; + case RF_CMD_OFF: + rf->last_state_command = "RF Scheduled Off"; + bsc_del_timer(&rf->rf_check); + enter_grace(rf); + break; + } +} + +static int rf_read_cmd(struct bsc_fd *fd) +{ + struct osmo_bsc_rf_conn *conn = fd->data; + char buf[1]; + int rc; + + rc = read(fd->fd, buf, sizeof(buf)); + if (rc != sizeof(buf)) { + LOGP(DINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); + bsc_unregister_fd(fd); + close(fd->fd); + write_queue_clear(&conn->queue); + talloc_free(conn); + return -1; + } + + switch (buf[0]) { + case RF_CMD_QUERY: + handle_query(conn); + break; + case RF_CMD_D_OFF: + case RF_CMD_ON: + case RF_CMD_OFF: + conn->rf->last_request = buf[0]; + if (!bsc_timer_pending(&conn->rf->delay_cmd)) + bsc_schedule_timer(&conn->rf->delay_cmd, 1, 0); + break; + default: + conn->rf->last_state_command = "Unknown command"; + LOGP(DINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); + break; + } + + return 0; +} + +static int rf_write_cmd(struct bsc_fd *fd, struct msgb *msg) +{ + int rc; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int rf_ctl_accept(struct bsc_fd *bfd, unsigned int what) +{ + struct osmo_bsc_rf_conn *conn; + struct osmo_bsc_rf *rf = bfd->data; + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + int fd; + + fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); + if (fd < 0) { + LOGP(DINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", + errno, strerror(errno)); + return -1; + } + + conn = talloc_zero(rf, struct osmo_bsc_rf_conn); + if (!conn) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate mem.\n"); + close(fd); + return -1; + } + + write_queue_init(&conn->queue, 10); + conn->queue.bfd.data = conn; + conn->queue.bfd.fd = fd; + conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; + conn->queue.read_cb = rf_read_cmd; + conn->queue.write_cb = rf_write_cmd; + conn->rf = rf; + + if (bsc_register_fd(&conn->queue.bfd) != 0) { + close(fd); + talloc_free(conn); + return -1; + } + + return 0; +} + +struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) +{ + unsigned int namelen; + struct sockaddr_un local; + struct bsc_fd *bfd; + struct osmo_bsc_rf *rf; + int rc; + + rf = talloc_zero(NULL, struct osmo_bsc_rf); + if (!rf) { + LOGP(DINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); + return NULL; + } + + bfd = &rf->listen; + bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (bfd->fd < 0) { + LOGP(DINP, LOGL_ERROR, "Can not create socket. %d/%s\n", + errno, strerror(errno)); + return NULL; + } + + local.sun_family = AF_UNIX; + strncpy(local.sun_path, path, sizeof(local.sun_path)); + local.sun_path[sizeof(local.sun_path) - 1] = '\0'; + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", + local.sun_path, errno, strerror(errno)); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + if (listen(bfd->fd, 0) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + bfd->when = BSC_FD_READ; + bfd->cb = rf_ctl_accept; + bfd->data = rf; + + if (bsc_register_fd(bfd) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to register bfd.\n"); + close(bfd->fd); + talloc_free(rf); + return NULL; + } + + rf->gsm_network = net; + rf->policy = S_RF_ON; + rf->last_state_command = ""; + + /* check the rf state */ + rf->rf_check.data = rf; + rf->rf_check.cb = rf_check_cb; + + /* delay cmd handling */ + rf->delay_cmd.data = rf; + rf->delay_cmd.cb = rf_delay_cmd_cb; + + return rf; +} + diff --git a/openbsc/src/osmo-bsc/osmo_bsc_sccp.c b/openbsc/src/osmo-bsc/osmo_bsc_sccp.c new file mode 100644 index 000000000..1abb47394 --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_sccp.c @@ -0,0 +1,288 @@ +/* Interaction with the SCCP subsystem */ +/* + * (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/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/debug.h> +#include <openbsc/ipaccess.h> +#include <openbsc/signal.h> + +#include <osmocore/gsm0808.h> +#include <osmocore/talloc.h> +#include <osmocore/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); + 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; + + bsc_del_timer(&con_data->sccp_cc_timeout); + bsc_schedule_timer(&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); + 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); + bsc_schedule_timer(&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 *data) +{ + struct gsm_network *net = (struct gsm_network *) data; + msc_queue_write(net->msc_data->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 gsm_network *net = (struct gsm_network *) data; + return bsc_handle_udt(net, net->msc_data->msc_con, 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; +} + +int bsc_create_new_connection(struct gsm_subscriber_connection *conn) +{ + struct gsm_network *net; + struct osmo_bsc_sccp_con *bsc_con; + struct sccp_connection *sccp; + + net = conn->bts->network; + if (!net->msc_data->msc_con->is_authenticated) { + LOGP(DMSC, LOGL_ERROR, "Not connected to a MSC. Not forwarding data.\n"); + return -1; + } + + if (!bsc_grace_allow_new_connection(net)) { + LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n"); + return -1; + } + + sccp = sccp_connection_socket(); + if (!sccp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n"); + return -ENOMEM; + } + + 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 -1; + } + + /* callbacks */ + sccp->state_cb = msc_outgoing_sccp_state; + sccp->data_cb = msc_outgoing_sccp_data; + sccp->data_ctx = bsc_con; + + /* prepare the timers */ + bsc_con->sccp_it_timeout.cb = sccp_it_timeout; + bsc_con->sccp_it_timeout.data = bsc_con; + bsc_con->sccp_cc_timeout.cb = sccp_cc_timeout; + bsc_con->sccp_cc_timeout.data = bsc_con; + + INIT_LLIST_HEAD(&bsc_con->sccp_queue); + + bsc_con->sccp = sccp; + bsc_con->msc_con = net->msc_data->msc_con; + bsc_con->conn = conn; + llist_add(&bsc_con->entry, &active_connections); + conn->sccp_con = bsc_con; + return 0; +} + +int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg) +{ + bsc_schedule_timer(&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); + bsc_del_timer(&sccp->sccp_it_timeout); + bsc_del_timer(&sccp->sccp_cc_timeout); + talloc_free(sccp); + return 0; +} + +static void bsc_close_connections(struct bsc_msc_connection *msc_con) +{ + struct osmo_bsc_sccp_con *con, *tmp; + + llist_for_each_entry_safe(con, tmp, &active_connections, entry) + bsc_sccp_force_free(con); +} + +static int handle_msc_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct osmo_msc_data *data; + + if (subsys != SS_MSC) + return 0; + + data = (struct osmo_msc_data *) signal_data; + if (signal == S_MSC_LOST) + bsc_close_connections(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); + + register_signal_handler(SS_MSC, handle_msc_signal, gsmnet); + + return 0; +} diff --git a/openbsc/src/osmo-bsc/osmo_bsc_vty.c b/openbsc/src/osmo-bsc/osmo_bsc_vty.c new file mode 100644 index 000000000..166774275 --- /dev/null +++ b/openbsc/src/osmo-bsc/osmo_bsc_vty.c @@ -0,0 +1,311 @@ +/* Osmo BSC VTY Configuration */ +/* (C) 2009-2010 by Holger Hans Peter Freyther + * (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/gsm_data.h> +#include <openbsc/osmo_msc_data.h> +#include <openbsc/vty.h> + +#include <osmocore/talloc.h> + + +#define IPA_STR "IP.ACCESS specific\n" + +extern struct gsm_network *bsc_gsmnet; + +static struct osmo_msc_data *osmo_msc_data(struct vty *vty) +{ + return bsc_gsmnet->msc_data; +} + +static struct cmd_node msc_node = { + MSC_NODE, + "%s(msc)#", + 1, +}; + +DEFUN(cfg_net_msc, cfg_net_msc_cmd, + "msc", "Configure MSC details") +{ + vty->index = bsc_gsmnet; + vty->node = MSC_NODE; + + return CMD_SUCCESS; +} + +static int config_write_msc(struct vty *vty) +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + vty_out(vty, " msc%s", VTY_NEWLINE); + if (data->bsc_token) + vty_out(vty, " token %s%s", data->bsc_token, VTY_NEWLINE); + if (data->core_ncc != -1) + vty_out(vty, " core-mobile-network-code %d%s", + data->core_ncc, VTY_NEWLINE); + if (data->core_mcc != -1) + vty_out(vty, " core-mobile-country-code %d%s", + data->core_mcc, VTY_NEWLINE); + vty_out(vty, " ip.access rtp-base %d%s", data->rtp_base, VTY_NEWLINE); + vty_out(vty, " ip %s%s", data->msc_ip, VTY_NEWLINE); + vty_out(vty, " port %d%s", data->msc_port, VTY_NEWLINE); + vty_out(vty, " ip-dscp %d%s", data->msc_ip_dscp, VTY_NEWLINE); + vty_out(vty, " timeout-ping %d%s", data->ping_timeout, VTY_NEWLINE); + vty_out(vty, " timeout-pong %d%s", data->pong_timeout, VTY_NEWLINE); + if (data->mid_call_txt) + vty_out(vty, "mid-call-text %s%s", data->mid_call_txt, VTY_NEWLINE); + vty_out(vty, " mid-call-timeout %d%s", data->mid_call_timeout, VTY_NEWLINE); + if (data->ussd_welcome_txt) + vty_out(vty, " bsc-welcome-text %s%s", data->ussd_welcome_txt, VTY_NEWLINE); + + if (data->audio_length != 0) { + int i; + + vty_out(vty, " codec_list "); + for (i = 0; i < data->audio_length; ++i) { + if (i != 0) + vty_out(vty, ", "); + + if (data->audio_support[i]->hr) + vty_out(vty, "hr%.1u", data->audio_support[i]->ver); + else + vty_out(vty, "fr%.1u", data->audio_support[i]->ver); + } + vty_out(vty, "%s", 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") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + bsc_replace_string(data, &data->bsc_token, argv[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 backbone\n" "NCC value\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->core_ncc = 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 backbone\n" "MCC value\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->core_mcc = 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 osmo_msc_data *data = osmo_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\n") +{ + struct osmo_msc_data *data = osmo_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(data, 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_ip, + cfg_net_msc_ip_cmd, + "ip A.B.C.D", "Set the MSC/MUX IP address.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + + bsc_replace_string(data, &data->msc_ip, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_port, + cfg_net_msc_port_cmd, + "port <1-65000>", + "Set the MSC/MUX port.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->msc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + + +DEFUN(cfg_net_msc_prio, + cfg_net_msc_prio_cmd, + "ip-dscp <0-255>", + "Set the IP_TOS socket attribite") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->msc_ip_dscp = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_ping_time, + cfg_net_msc_ping_time_cmd, + "timeout-ping NR", + "Set the PING interval, negative for not sending PING") +{ + struct osmo_msc_data *data = osmo_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 NR", + "Set the time to wait for a PONG.") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->pong_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_mid_call_text, + cfg_net_msc_mid_call_text_cmd, + "mid-call-text .TEXT", + "Set the USSD notifcation to be send.\n" "Text to be sent\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + bsc_replace_string(data, &data->mid_call_txt, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_mid_call_timeout, + cfg_net_msc_mid_call_timeout_cmd, + "mid-call-timeout NR", + "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n") +{ + struct osmo_msc_data *data = osmo_msc_data(vty); + data->mid_call_timeout = atoi(argv[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 osmo_msc_data *data = osmo_msc_data(vty); + char *str = argv_concat(argv, argc, 0); + if (!str) + return CMD_WARNING; + + bsc_replace_string(data, &data->ussd_welcome_txt, str); + talloc_free(str); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(void) +{ + install_element(GSMNET_NODE, &cfg_net_msc_cmd); + install_node(&msc_node, config_write_msc); + install_default(MSC_NODE); + install_element(MSC_NODE, &cfg_net_bsc_token_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_rtp_base_cmd); + install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd); + install_element(MSC_NODE, &cfg_net_msc_ip_cmd); + install_element(MSC_NODE, &cfg_net_msc_port_cmd); + install_element(MSC_NODE, &cfg_net_msc_prio_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_mid_call_text_cmd); + install_element(MSC_NODE, &cfg_net_msc_mid_call_timeout_cmd); + install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd); + + return 0; +} |