diff options
Diffstat (limited to 'openbsc/src')
32 files changed, 5990 insertions, 106 deletions
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am index 2c1d37a04..79db36426 100644 --- a/openbsc/src/Makefile.am +++ b/openbsc/src/Makefile.am @@ -3,7 +3,8 @@ AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) AM_LDFLAGS = $(LIBOSMOCORE_LIBS) sbin_PROGRAMS = bsc_hack bs11_config ipaccess-find ipaccess-config \ - isdnsync bsc_mgcp ipaccess-proxy + isdnsync bsc_mgcp ipaccess-proxy \ + bsc_msc_ip bsc_nat noinst_LIBRARIES = libbsc.a libmsc.a libvty.a noinst_HEADERS = vty/cardshell.h @@ -17,12 +18,12 @@ libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_data.c gsm_04_08_utils.c \ input/misdn.c input/ipaccess.c \ talloc_ctx.c system_information.c rest_octets.c \ rtp_proxy.c bts_siemens_bs11.c bts_ipaccess_nanobts.c \ - bts_unknown.c bsc_version.c bsc_api.c vty_interface_cmds.c + bts_unknown.c meas_rep.c telnet_interface.c bsc_version.c bsc_api.c vty_interface_cmds.c -libmsc_a_SOURCES = gsm_subscriber.c db.c telnet_interface.c \ +libmsc_a_SOURCES = gsm_subscriber.c db.c \ mncc.c gsm_04_08.c gsm_04_11.c transaction.c \ token_auth.c rrlp.c gsm_04_80.c ussd.c silent_call.c \ - handover_logic.c handover_decision.c meas_rep.c + handover_logic.c handover_decision.c libvty_a_SOURCES = vty/buffer.c vty/command.c vty/vector.c vty/vty.c @@ -33,6 +34,10 @@ bsc_hack_LDADD = libmsc.a libbsc.a libmsc.a libvty.a -ldl -ldbi $(LIBCRYPT) bs11_config_SOURCES = bs11_config.c abis_nm.c gsm_data.c debug.c \ rs232.c bts_siemens_bs11.c +bsc_msc_ip_SOURCES = bssap.c bsc_msc_ip.c bsc_init.c vty_interface.c vty_interface_bsc.c \ + bsc_msc.c bsc_msc_rf.c +bsc_msc_ip_LDADD = libbsc.a libvty.a libsccp.a + ipaccess_find_SOURCES = ipaccess/ipaccess-find.c @@ -46,3 +51,9 @@ bsc_mgcp_SOURCES = mgcp/mgcp_main.c mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgc bsc_mgcp_LDADD = libvty.a ipaccess_proxy_SOURCES = ipaccess/ipaccess-proxy.c debug.c + +bsc_nat_SOURCES = nat/bsc_nat.c nat/bsc_filter.c nat/bsc_sccp.c \ + nat/bsc_nat_utils.c nat/bsc_nat_vty.c nat/bsc_mgcp_utils.c \ + mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c \ + bsc_msc.c bssap.c +bsc_nat_LDADD = libvty.a libbsc.a libsccp.a diff --git a/openbsc/src/abis_rsl.c b/openbsc/src/abis_rsl.c index 53b29823e..282198472 100644 --- a/openbsc/src/abis_rsl.c +++ b/openbsc/src/abis_rsl.c @@ -568,12 +568,33 @@ int rsl_deact_sacch(struct gsm_lchan *lchan) return abis_rsl_sendmsg(msg); } +static void error_timeout_cb(void *data) +{ + struct gsm_lchan *lchan = data; + if (lchan->state != LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n", + gsm_lchan_name(lchan), lchan->state); + return; + } + + /* go back to the none state */ + LOGP(DRSL, LOGL_NOTICE, "%s is back in operation.\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_NONE); +} + /* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */ -int rsl_rf_chan_release(struct gsm_lchan *lchan) +static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error) { struct abis_rsl_dchan_hdr *dh; - struct msgb *msg = rsl_msgb_alloc(); + struct msgb *msg; + + if (lchan->state == LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_NOTICE, "%s is in error state not sending release.\n", + gsm_lchan_name(lchan)); + return -1; + } + msg = rsl_msgb_alloc(); dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL); dh->chan_nr = lchan2chan_nr(lchan); @@ -581,7 +602,20 @@ int rsl_rf_chan_release(struct gsm_lchan *lchan) msg->lchan = lchan; msg->trx = lchan->ts->trx; - DEBUGP(DRSL, "%s RF Channel Release CMD\n", gsm_lchan_name(lchan)); + DEBUGP(DRSL, "%s RF Channel Release CMD due error %d\n", gsm_lchan_name(lchan), error); + + if (error) { + /* + * the nanoBTS sends RLL release indications after the channel release. This can + * be a problem when we have reassigned the channel to someone else and then can + * not figure out who used this channel. + */ + rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); + lchan->error_timer.data = lchan; + lchan->error_timer.cb = error_timeout_cb; + bsc_schedule_timer(&lchan->error_timer, + msg->trx->bts->network->T3111 + 2, 0); + } /* BTS will respond by RF CHAN REL ACK */ return abis_rsl_sendmsg(msg); @@ -718,14 +752,15 @@ int rsl_establish_request(struct gsm_lchan *lchan, u_int8_t link_id) RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE, which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls lchan_free() */ -int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id) +int rsl_release_request(struct gsm_lchan *lchan, u_int8_t link_id, u_int8_t reason) { struct msgb *msg; msg = rsl_rll_simple(RSL_MT_REL_REQ, lchan2chan_nr(lchan), link_id, 0); - msgb_tv_put(msg, RSL_IE_RELEASE_MODE, 0); /* normal release */ + /* 0 is normal release, 1 is local end */ + msgb_tv_put(msg, RSL_IE_RELEASE_MODE, reason); /* FIXME: start some timer in case we don't receive a REL ACK ? */ @@ -811,7 +846,7 @@ static int rsl_rx_conn_fail(struct msgb *msg) LOGPC(DRSL, LOGL_NOTICE, "\n"); /* FIXME: only free it after channel release ACK */ counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail); - return rsl_rf_chan_release(msg->lchan); + return rsl_rf_chan_release(msg->lchan, 1); } static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru, @@ -983,11 +1018,14 @@ static int abis_rsl_rx_dchan(struct msgb *msg) break; case RSL_MT_RF_CHAN_REL_ACK: DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", ts_name); - if (msg->lchan->state != LCHAN_S_REL_REQ) + if (msg->lchan->state != LCHAN_S_REL_REQ && msg->lchan->state != LCHAN_S_REL_ERR) LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n", gsm_lchan_name(msg->lchan), gsm_lchans_name(msg->lchan->state)); - rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE); + bsc_del_timer(&msg->lchan->T3111); + /* we have an error timer pending to release that */ + if (msg->lchan->state != LCHAN_S_REL_ERR) + rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE); lchan_free(msg->lchan); break; case RSL_MT_MODE_MODIFY_ACK: @@ -1079,7 +1117,15 @@ static void t3101_expired(void *data) { struct gsm_lchan *lchan = data; - rsl_rf_chan_release(lchan); + rsl_rf_chan_release(lchan, 1); +} + +/* If T3111 expires, we will send the RF Channel Request */ +static void t3111_expired(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_rf_chan_release(lchan, 0); } /* MS has requested a channel on the RACH */ @@ -1094,6 +1140,7 @@ static int rsl_rx_chan_rqd(struct msgb *msg) struct gsm_lchan *lchan; u_int8_t rqd_ta; int ret; + int is_lu; u_int16_t arfcn; u_int8_t ts_number, subch; @@ -1111,13 +1158,19 @@ static int rsl_rx_chan_rqd(struct msgb *msg) /* determine channel type (SDCCH/TCH_F/TCH_H) based on * request reference RA */ - lctype = get_ctype_by_chreq(bts, rqd_ref->ra, bts->network->neci); - chreq_reason = get_reason_by_chreq(bts, rqd_ref->ra, bts->network->neci); + lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra); + chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci); counter_inc(bts->network->stats.chreq.total); + /* + * We want LOCATION UPDATES to succeed and will assign a TCH + * if we have no SDCCH available. + */ + is_lu = !!(chreq_reason == GSM_CHREQ_REASON_LOCATION_UPD); + /* check availability / allocate channel */ - lchan = lchan_alloc(bts, lctype); + lchan = lchan_alloc(bts, lctype, is_lu); if (!lchan) { LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: no resources for %s 0x%x\n", msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra); @@ -1252,12 +1305,38 @@ static int rsl_rx_rll_err_ind(struct msgb *msg) if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) { counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err); - return rsl_rf_chan_release(msg->lchan); + return rsl_rf_chan_release(msg->lchan, 1); } return 0; } +static void rsl_handle_release(struct gsm_lchan *lchan) +{ + int sapi; + struct gsm_bts *bts; + + /* maybe we have only brought down one RLL */ + if (lchan->state != LCHAN_S_REL_REQ) + return; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + LOGP(DRSL, LOGL_NOTICE, "%s waiting for SAPI=%d to be released.\n", + gsm_lchan_name(lchan), sapi); + return; + } + + + + /* wait a bit to send the RF Channel Release */ + lchan->T3111.cb = t3111_expired; + lchan->T3111.data = lchan; + bts = lchan->ts->trx->bts; + bsc_schedule_timer(&lchan->T3111, bts->network->T3111, 0); +} + /* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST 0x02, 0x06, 0x01, 0x20, @@ -1309,20 +1388,16 @@ static int abis_rsl_rx_rll(struct msgb *msg) msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_REL_IND); - /* we can now releae the channel on the BTS/Abis side */ - /* FIXME: officially we need to start T3111 and wait for - * some grace period */ - rsl_rf_chan_release(msg->lchan); + rsl_handle_release(msg->lchan); + rsl_lchan_rll_release(msg->lchan, rllh->link_id); break; case RSL_MT_REL_CONF: /* BTS informs us of having received UA from MS, * in response to DISC that we've sent earlier */ DEBUGPC(DRLL, "RELEASE CONFIRMATION\n"); msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; - /* we can now releae the channel on the BTS/Abis side */ - /* FIXME: officially we need to start T3111 and wait for - * some grace period */ - rsl_rf_chan_release(msg->lchan); + rsl_handle_release(msg->lchan); + rsl_lchan_rll_release(msg->lchan, rllh->link_id); break; case RSL_MT_ERROR_IND: rc = rsl_rx_rll_err_ind(msg); @@ -1342,31 +1417,11 @@ static u_int8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan) { switch (lchan->tch_mode) { case GSM48_CMODE_SPEECH_V1: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x00; - case GSM_LCHAN_TCH_H: - return 0x03; - default: - break; - } + return 0x00; case GSM48_CMODE_SPEECH_EFR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x01; - /* there's no half-rate EFR */ - default: - break; - } + return 0x01; case GSM48_CMODE_SPEECH_AMR: - switch (lchan->type) { - case GSM_LCHAN_TCH_F: - return 0x02; - case GSM_LCHAN_TCH_H: - return 0x05; - default: - break; - } + return 0x02; default: break; } diff --git a/openbsc/src/bs11_config.c b/openbsc/src/bs11_config.c index a7493b422..8193ecd34 100644 --- a/openbsc/src/bs11_config.c +++ b/openbsc/src/bs11_config.c @@ -870,3 +870,8 @@ int main(int argc, char **argv) exit(0); } + +/* dummy to be able to compile */ +void gsm_net_update_ctype(struct gsm_network *net) +{ +} diff --git a/openbsc/src/bsc-nat.cfg b/openbsc/src/bsc-nat.cfg new file mode 100644 index 000000000..18b022753 --- /dev/null +++ b/openbsc/src/bsc-nat.cfg @@ -0,0 +1,25 @@ +! +! BSC NAT configuration hand edited +! ! +password foo +! +line vty + no login +! +nat + msc ip 10.0.0.23 + bsc 0 + token zecke + location_area_code 3 + bsc 1 + token roch + location_area_code 4 +mgcp + local ip 10.0.0.23 +! bts ip 0.0.0.0 + bind ip 127.0.0.1 + bind port 2427 + bind early 1 + rtp base 4000 + number endpoints 31 + call agent ip 127.0.0.1 diff --git a/openbsc/src/bsc_init.c b/openbsc/src/bsc_init.c index a39bc198a..44c4d319a 100644 --- a/openbsc/src/bsc_init.c +++ b/openbsc/src/bsc_init.c @@ -427,6 +427,7 @@ int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj, case NM_OC_BTS: bts = obj; if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + printf("STARTING BTS...\n"); patch_nm_tables(bts); abis_nm_set_bts_attr(bts, nanobts_attr_bts, sizeof(nanobts_attr_bts)); @@ -442,6 +443,7 @@ int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj, trx = ts->trx; if (new_state->operational == 1 && new_state->availability == NM_AVSTATE_DEPENDENCY) { + printf("STARTING OC Channel...\n"); patch_nm_tables(trx->bts); enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan); diff --git a/openbsc/src/bsc_msc.c b/openbsc/src/bsc_msc.c new file mode 100644 index 000000000..0d45f9943 --- /dev/null +++ b/openbsc/src/bsc_msc.c @@ -0,0 +1,229 @@ +/* Routines to talk to the MSC using the IPA Protocol */ +/* + * (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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bsc_msc.h> +#include <openbsc/debug.h> + +#include <osmocore/write_queue.h> +#include <osmocore/talloc.h> + +#include <arpa/inet.h> +#include <sys/socket.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static void connection_loss(struct bsc_msc_connection *con) +{ + struct bsc_fd *fd; + + fd = &con->write_queue.bfd; + + close(fd->fd); + fd->fd = -1; + fd->cb = write_queue_bfd_cb; + fd->when = 0; + + con->is_connected = 0; + con->connection_loss(con); +} + +static int bsc_msc_except(struct bsc_fd *bfd) +{ + struct write_queue *wrt; + struct bsc_msc_connection *con; + + LOGP(DMSC, LOGL_ERROR, "Exception on the BFD. Closing down.\n"); + + wrt = container_of(bfd, struct write_queue, bfd); + con = container_of(wrt, struct bsc_msc_connection, write_queue); + + connection_loss(con); + return 0; +} + +/* called in the case of a non blocking connect */ +static int msc_connection_connect(struct bsc_fd *fd, unsigned int what) +{ + int rc; + int val; + struct bsc_msc_connection *con; + struct write_queue *queue; + + socklen_t len = sizeof(val); + + if ((what & BSC_FD_WRITE) == 0) { + LOGP(DMSC, LOGL_ERROR, "Callback but not readable.\n"); + return -1; + } + + queue = container_of(fd, struct write_queue, bfd); + con = container_of(queue, struct bsc_msc_connection, write_queue); + + /* check the socket state */ + rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len); + if (rc != 0) { + LOGP(DMSC, LOGL_ERROR, "getsockopt for the MSC socket failed.\n"); + goto error; + } + if (val != 0) { + LOGP(DMSC, LOGL_ERROR, "Not connected to the MSC: %d\n", val); + goto error; + } + + + /* go to full operation */ + fd->cb = write_queue_bfd_cb; + fd->when = BSC_FD_READ | BSC_FD_EXCEPT; + + con->is_connected = 1; + LOGP(DMSC, LOGL_NOTICE, "(Re)Connected to the MSC.\n"); + if (con->connected) + con->connected(con); + return 0; + +error: + bsc_unregister_fd(fd); + connection_loss(con); + return -1; +} +static void setnonblocking(struct bsc_fd *fd) +{ + int flags; + + flags = fcntl(fd->fd, F_GETFL); + if (flags < 0) { + perror("fcntl get failed"); + close(fd->fd); + fd->fd = -1; + return; + } + + flags |= O_NONBLOCK; + flags = fcntl(fd->fd, F_SETFL, flags); + if (flags < 0) { + perror("fcntl get failed"); + close(fd->fd); + fd->fd = -1; + return; + } +} + +int bsc_msc_connect(struct bsc_msc_connection *con) +{ + struct bsc_fd *fd; + struct sockaddr_in sin; + int on = 1, ret; + + LOGP(DMSC, LOGL_NOTICE, "Attempting to connect MSC at %s:%d\n", con->ip, con->port); + + con->is_connected = 0; + + fd = &con->write_queue.bfd; + fd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + fd->data = NULL; + fd->priv_nr = 1; + + if (fd->fd < 0) { + perror("Creating TCP socket failed"); + return fd->fd; + } + + /* make it non blocking */ + setnonblocking(fd); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(con->port); + inet_aton(con->ip, &sin.sin_addr); + + setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin)); + + if (ret == -1 && errno == EINPROGRESS) { + LOGP(DMSC, LOGL_ERROR, "MSC Connection in progress\n"); + fd->when = BSC_FD_WRITE; + fd->cb = msc_connection_connect; + } else if (ret < 0) { + perror("Connection failed"); + connection_loss(con); + return ret; + } else { + fd->when = BSC_FD_READ | BSC_FD_EXCEPT; + fd->cb = write_queue_bfd_cb; + con->is_connected = 1; + if (con->connected) + con->connected(con); + } + + ret = bsc_register_fd(fd); + if (ret < 0) { + perror("Registering the fd failed"); + close(fd->fd); + return ret; + } + + return ret; +} + + +struct bsc_msc_connection *bsc_msc_create(const char *ip, int port) +{ + struct bsc_msc_connection *con; + + con = talloc_zero(NULL, struct bsc_msc_connection); + if (!con) { + LOGP(DMSC, LOGL_FATAL, "Failed to create the MSC connection.\n"); + return NULL; + } + + con->ip = ip; + con->port = port; + write_queue_init(&con->write_queue, 100); + con->write_queue.except_cb = bsc_msc_except; + return con; +} + +void bsc_msc_lost(struct bsc_msc_connection *con) +{ + write_queue_clear(&con->write_queue); + bsc_unregister_fd(&con->write_queue.bfd); + connection_loss(con); +} + +static void reconnect_msc(void *_msc) +{ + struct bsc_msc_connection *con = _msc; + + LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n"); + bsc_msc_connect(con); +} + +void bsc_msc_schedule_connect(struct bsc_msc_connection *con) +{ + LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n"); + con->reconnect_timer.cb = reconnect_msc; + con->reconnect_timer.data = con; + bsc_schedule_timer(&con->reconnect_timer, 5, 0); +} diff --git a/openbsc/src/bsc_msc_ip.c b/openbsc/src/bsc_msc_ip.c new file mode 100644 index 000000000..d63041401 --- /dev/null +++ b/openbsc/src/bsc_msc_ip.c @@ -0,0 +1,1117 @@ +/* The BSC Process to handle GSM08.08 (A-Interface) */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009 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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <unistd.h> +#include <time.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> +#include <sys/stat.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <openbsc/debug.h> +#include <openbsc/e1_input.h> +#include <openbsc/ipaccess.h> +#include <openbsc/bssap.h> +#include <openbsc/paging.h> +#include <openbsc/signal.h> +#include <openbsc/chan_alloc.h> +#include <openbsc/bsc_msc.h> +#include <openbsc/bsc_nat.h> +#include <openbsc/bsc_msc_rf.h> + +#include <osmocore/select.h> +#include <osmocore/talloc.h> +#include <osmocore/write_queue.h> + +#include <sccp/sccp.h> + +/* SCCP helper */ +#define SCCP_IT_TIMER 60 + +/* MCC and MNC for the Location Area Identifier */ +static struct log_target *stderr_target; +struct gsm_network *bsc_gsmnet = 0; +static const char *config_file = "openbsc.cfg"; +static char *msc_address = NULL; +static struct bsc_msc_connection *msc_con; +static struct in_addr local_addr; +static LLIST_HEAD(active_connections); +static struct write_queue mgcp_agent; +static const char *rf_ctl = NULL; +extern int ipacc_rtp_direct; + +extern int bsc_bootstrap_network(int (*layer4)(struct gsm_network *, int, void *), const char *cfg_file); +extern int bsc_shutdown_net(struct gsm_network *net); + + +struct llist_head *bsc_sccp_connections() +{ + return &active_connections; +} + +struct bss_sccp_connection_data *bss_sccp_create_data() +{ + struct bss_sccp_connection_data *data; + + data = _talloc_zero(tall_bsc_ctx, + sizeof(struct bss_sccp_connection_data), + "bsc<->msc"); + if (!data) + return NULL; + + INIT_LLIST_HEAD(&data->sccp_queue); + INIT_LLIST_HEAD(&data->gsm_queue); + llist_add_tail(&data->active_connections, &active_connections); + return data; +} + +void bss_sccp_free_data(struct bss_sccp_connection_data *data) +{ + bsc_del_timer(&data->T10); + bsc_del_timer(&data->sccp_it); + if (data->sccp) + bsc_free_queued(data->sccp); + bts_free_queued(data); + llist_del(&data->active_connections); + talloc_free(data); +} + +static void sccp_it_fired(void *_data) +{ + struct bss_sccp_connection_data *data = + (struct bss_sccp_connection_data *) _data; + + sccp_connection_send_it(data->sccp); + bsc_schedule_timer(&data->sccp_it, SCCP_IT_TIMER, 0); +} + + +/* GSM subscriber drop-ins */ +extern struct llist_head *subscr_bsc_active_subscriber(void); +struct gsm_subscriber *find_subscriber(u_int8_t type, const char *mi_string) +{ + struct gsm_subscriber *subscr; + u_int32_t tmsi = GSM_RESERVED_TMSI; + if (type == GSM_MI_TYPE_TMSI) { + tmsi = tmsi_from_string(mi_string); + if (tmsi == GSM_RESERVED_TMSI) { + LOGP(DMSC, LOGL_ERROR, "The TMSI is the reserved one.\n"); + return NULL; + } + } + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (type == GSM_MI_TYPE_TMSI && tmsi == subscr->tmsi) { + return subscr_get(subscr); + } else if (type == GSM_MI_TYPE_IMSI && strcmp(mi_string, subscr->imsi) == 0) { + return subscr_get(subscr); + } + } + + LOGP(DMSC, LOGL_ERROR, "No subscriber has been found.\n"); + return NULL; +} + + + +/* SCCP handling */ +void msc_outgoing_sccp_data(struct sccp_connection *conn, struct msgb *msg, unsigned int len) +{ + struct gsm_lchan *lchan; + struct bssmap_header *bs; + + if (len < 1) { + LOGP(DMSC, LOGL_ERROR, "The header is too short.\n"); + return; + } + + lchan = sccp_get_lchan(conn->data_ctx); + if (!lchan) { + LOGP(DMSC, LOGL_ERROR, "SCCP data without lchan for type: 0x%x\n", msg->l3h[0]); + return; + } + + /* that is bad */ + if (!lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "SCCP data for lchan without msc data type: 0x%x\n", + msg->l3h[0]); + return; + } + + switch (msg->l3h[0]) { + case BSSAP_MSG_BSS_MANAGEMENT: + msg->l4h = &msg->l3h[sizeof(*bs)]; + msg->lchan = lchan; + bssmap_rcvmsg_dt1(conn, msg, len - sizeof(*bs)); + break; + case BSSAP_MSG_DTAP: + dtap_rcvmsg(lchan, msg, len); + break; + default: + LOGP(DMSC, LOGL_DEBUG, "Unimplemented msg type: %d\n", msg->l3h[0]); + } +} + +void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state) +{ + if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + LOGP(DMSC, LOGL_DEBUG, "Freeing sccp conn: %p state: %d\n", conn, conn->connection_state); + if (sccp_get_lchan(conn->data_ctx) != NULL) { + struct gsm_lchan *lchan = sccp_get_lchan(conn->data_ctx); + + LOGP(DMSC, LOGL_ERROR, "ERROR: The lchan is still associated\n."); + + lchan->msc_data = NULL; + put_subscr_con(&lchan->conn, 0); + } + + bss_sccp_free_data((struct bss_sccp_connection_data *)conn->data_ctx); + sccp_connection_free(conn); + return; + } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) { + struct bss_sccp_connection_data *con_data; + + LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn); + + /* start the inactivity test timer */ + con_data = (struct bss_sccp_connection_data *) conn->data_ctx; + con_data->sccp_it.cb = sccp_it_fired; + con_data->sccp_it.data = con_data; + bsc_schedule_timer(&con_data->sccp_it, SCCP_IT_TIMER, 0); + + bsc_send_queued(conn); + } +} + +/* + * General COMPLETE LAYER3 INFORMATION handling for + * PAGING RESPONSE, LOCATION UPDATING REQUEST, CM REESTABLISHMENT REQUEST, + * CM SERVICE REQUEST, IMSI DETACH, IMMEDIATE SETUP. + * + * IMMEDIATE SETUP is coming from GROUP CC that is not yet + * supported... + */ +static int open_sccp_connection(struct msgb *layer3) +{ + struct bss_sccp_connection_data *con_data; + struct sccp_connection *sccp_connection; + struct msgb *data; + + /* When not connected to a MSC. We will simply close things down. */ + if (!msc_con->is_authenticated) { + LOGP(DMSC, LOGL_ERROR, "Not connected to a MSC. Not forwarding data.\n"); + use_subscr_con(&layer3->lchan->conn); + put_subscr_con(&layer3->lchan->conn, 0); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "Opening new layer3 connection\n"); + sccp_connection = sccp_connection_socket(); + if (!sccp_connection) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n"); + return -ENOMEM; + } + + data = bssmap_create_layer3(layer3); + if (!data) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate complete layer3.\n"); + sccp_connection_free(sccp_connection); + return -ENOMEM; + } + + con_data = bss_sccp_create_data(); + if (!con_data) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate bss<->msc data.\n"); + sccp_connection_free(sccp_connection); + msgb_free(data); + return -ENOMEM; + } + + /* initialize the bridge */ + con_data->lchan = layer3->lchan; + con_data->sccp = sccp_connection; + + sccp_connection->state_cb = msc_outgoing_sccp_state; + sccp_connection->data_cb = msc_outgoing_sccp_data; + sccp_connection->data_ctx = con_data; + layer3->lchan->msc_data = con_data; + + /* FIXME: Use transaction for this */ + use_subscr_con(&layer3->lchan->conn); + sccp_connection_connect(sccp_connection, &sccp_ssn_bssap, data); + msgb_free(data); + + return 1; +} + +/* figure out if this is the inial layer3 message */ +static int send_dtap_or_open_connection(struct msgb *msg) +{ + if (msg->lchan->msc_data) { + struct msgb *dtap = dtap_create_msg(msg, 0); + if (!dtap) { + LOGP(DMSC, LOGL_ERROR, "Creating a DTAP message failed.\n"); + return -1; + } + + bsc_queue_connection_write(lchan_get_sccp(msg->lchan), dtap); + return 1; + } else { + return open_sccp_connection(msg); + } +} + +/* Receive a PAGING RESPONSE message from the MS */ +static int handle_paging_response(struct msgb *msg) +{ + struct gsm_subscriber *subscr; + char mi_string[GSM48_MI_SIZE]; + u_int8_t mi_type; + + gsm48_paging_extract_mi(msg, mi_string, &mi_type); + LOGP(DMSC, LOGL_DEBUG, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n", + mi_type, mi_string); + + subscr = find_subscriber(mi_type, mi_string); + if (!subscr) + return -EINVAL; + + /* force the paging to stop at every bts */ + subscr->lac = GSM_LAC_RESERVED_ALL_BTS; + if (gsm48_handle_paging_resp(msg, subscr) != 0) { + LOGP(DMSC, LOGL_ERROR, "Paging failed.\n"); + return -1; + } + + /* open a new transaction and SCCP connection */ + return send_dtap_or_open_connection(msg); +} + +/* Receive a CIPHER MODE COMPLETE from the MS */ +static int handle_cipher_m_complete(struct msgb *msg) +{ + struct msgb *resp; + + if (!msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No MSC data for CIPHER MODE COMPLETE.\n"); + return -1; + } + + LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n"); + resp = bssmap_create_cipher_complete(msg); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Creating MSC response failed.\n"); + return -1; + } + + + /* handled this message */ + bts_unblock_queue(msg->lchan->msc_data); + bsc_queue_connection_write(lchan_get_sccp(msg->lchan), resp); + return 1; +} + +/* Receive a ASSIGNMENT COMPLETE */ +static int handle_ass_compl(struct msgb *msg) +{ + struct gsm_lchan *old_chan; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGP(DMSC, LOGL_DEBUG, "ASSIGNMENT COMPLETE from MS, forwarding to MSC\n"); + + if (!msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No MSC data\n"); + put_subscr_con(&msg->lchan->conn, 0); + return -1; + } + + if (msg->lchan->msc_data->secondary_lchan != msg->lchan) { + LOGP(DMSC, LOGL_ERROR, "Wrong assignment complete.\n"); + put_subscr_con(&msg->lchan->conn, 0); + return -1; + } + + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGP(DMSC, LOGL_ERROR, "assignment compl invalid: %d\n", + msgb_l3len(msg) - sizeof(*gh)); + put_subscr_con(&msg->lchan->conn, 0); + return -1; + } + + /* swap the channels and release the old */ + old_chan = msg->lchan->msc_data->lchan; + msg->lchan->msc_data->lchan = msg->lchan; + msg->lchan->msc_data->secondary_lchan = NULL; + old_chan->msc_data = NULL; + + /* give up the old channel to not do a SACCH deactivate */ + subscr_put(old_chan->conn.subscr); + old_chan->conn.subscr = NULL; + put_subscr_con(&old_chan->conn, 1); + + /* activate audio on it... */ + if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && msg->lchan->tch_mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(msg->lchan); + + gsm0808_send_assignment_compl(msg->lchan, gh->data[0]); + return 1; +} + +/* + * Receive a ASSIGNMENT FAILURE. If the message is failed + * to be parsed the T10 timer will send the failure. + */ +static int handle_ass_fail(struct msgb *msg) +{ + u_int8_t *rr_cause; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGP(DMSC, LOGL_ERROR, "ASSIGNMENT FAILURE from MS, forwarding to MSC\n"); + if (!msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No MSC data\n"); + put_subscr_con(&msg->lchan->conn, 0); + return -1; + } + + /* assignment failure comes on the old link */ + if (msg->lchan->msc_data->lchan != msg->lchan) { + LOGP(DMSC, LOGL_NOTICE, "Failure should come on the old link.\n"); + msg->lchan->msc_data = NULL; + put_subscr_con(&msg->lchan->conn, 0); + return -1; + } + + /* Giving up the secondary will happen in bssap */ + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGP(DMSC, LOGL_ERROR, "assignment failure invalid: %d\n", + msgb_l3len(msg) - sizeof(*gh)); + rr_cause = NULL; + } else { + rr_cause = &gh->data[0]; + } + + /* this will also free the secondary channel */ + gsm0808_send_assignment_failure(msg->lchan, + GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, rr_cause); + return 1; +} + +/* + * Receive a GSM04.08 MODIFY ACK. Actually we have to check + * the content to see if this was a success or not. + */ +static int handle_modify_ack(struct msgb *msg) +{ + int rc; + + if (!msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No MSC data for modify ack.\n"); + return -1; + } + + /* modify RSL */ + rc = gsm48_rx_rr_modif_ack(msg); + if (rc < 0) + gsm0808_send_assignment_failure(msg->lchan, + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); + else + gsm0808_send_assignment_compl(msg->lchan, 0); + + return 1; +} + +/* Receive a GSM 04.08 Radio Resource (RR) message */ +static int gsm0408_rcv_rr(struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gh->msg_type) { + case GSM48_MT_RR_PAG_RESP: + rc = handle_paging_response(msg); + break; + case GSM48_MT_RR_MEAS_REP: + /* ignore measurement for now */ + rc = -1; + break; + case GSM48_MT_RR_CIPH_M_COMPL: + rc = handle_cipher_m_complete(msg); + break; + case GSM48_MT_RR_ASS_COMPL: + rc = handle_ass_compl(msg); + break; + case GSM48_MT_RR_ASS_FAIL: + rc = handle_ass_fail(msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: + rc = handle_modify_ack(msg); + break; + default: + break; + } + + return rc; +} + +/* Receive a GSM 04.08 Mobility Management (MM) message */ +static int gsm0408_rcv_mm(struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + int rc = 0; + + switch (gh->msg_type & 0xbf) { + case GSM48_MT_MM_LOC_UPD_REQUEST: + case GSM48_MT_MM_CM_REEST_REQ: + case GSM48_MT_MM_CM_SERV_REQ: + case GSM48_MT_MM_IMSI_DETACH_IND: + rc = send_dtap_or_open_connection(msg); + break; + default: + break; + } + + return rc; +} + +int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + u_int8_t pdisc = gh->proto_discr & 0x0f; + int rc = 0; + + switch (pdisc) { + case GSM48_PDISC_RR: + rc = gsm0408_rcv_rr(msg); + break; + case GSM48_PDISC_MM: + rc = gsm0408_rcv_mm(msg); + break; + default: + break; + } + + /* + * if we have a sccp connection and didn't handle the message + * forward it to the MSC using DTAP + */ + if (rc == 0 && msg->lchan->msc_data && lchan_get_sccp(msg->lchan)) { + struct msgb *dtap = dtap_create_msg(msg, link_id); + if (!dtap) { + LOGP(DMSC, LOGL_ERROR, "Creating a DTAP message failed.\n"); + return -1; + } + + bsc_queue_connection_write(lchan_get_sccp(msg->lchan), dtap); + } + + return rc; +} + +/* handle ipaccess signals */ +static int handle_abisip_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_lchan *lchan = signal_data; + struct gsm_bts_trx_ts *ts; + int rc; + + if (subsys != SS_ABISIP) + return 0; + + ts = lchan->ts; + + switch (signal) { + case S_ABISIP_CRCX_ACK: + /* we can ask it to connect now */ + if (lchan->msc_data) { + LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n", + lchan->msc_data->rtp_port, lchan->abis_ip.conn_id); + + int rtp_payload = ts->trx->bts->network->rtp_payload; + if (rtp_payload == 0) + rtp_payload = lchan->abis_ip.rtp_payload2; + + rc = rsl_ipacc_mdcx(lchan, ntohl(local_addr.s_addr), + lchan->msc_data->rtp_port, + rtp_payload); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to send connect: %d\n", rc); + return rc; + } + } + break; + case S_ABISIP_DLCX_IND: + break; + } + + return 0; +} + +static void print_usage() +{ + printf("Usage: bsc_msc_ip\n"); +} + +/* + * SCCP handling + */ +static int msc_queue_write(struct msgb *msg, int proto) +{ + ipaccess_prepend_header(msg, proto); + if (write_queue_enqueue(&msc_con->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_sccp_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->l2h, msgb_l2len(msg))); + + ret = write(msc_con->write_queue.bfd.fd, msg->data, msg->len); + if (ret < msg->len) + perror("MSC: Failed to send SCCP"); + + return ret; +} + +static void msc_sccp_write_ipa(struct msgb *msg, void *data) +{ + msc_queue_write(msg, IPAC_PROTO_SCCP); +} + +/* + * mgcp forwarding is below + */ +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 int mgcp_do_read(struct bsc_fd *fd) +{ + 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(mgcp, NAT_IPAC_PROTO_MGCP); + return 0; +} + +static void mgcp_forward(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(&mgcp_agent, mgcp) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n"); + msgb_free(mgcp); + } +} +static int mgcp_create_port(void) +{ + int on; + struct sockaddr_in addr; + + mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (mgcp_agent.bfd.fd < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + return -1; + } + + on = 1; + setsockopt(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(mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n"); + close(mgcp_agent.bfd.fd); + mgcp_agent.bfd.fd = -1; + return -1; + } + + /* connect to the remote */ + addr.sin_port = htons(2427); + if (connect(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(mgcp_agent.bfd.fd); + mgcp_agent.bfd.fd = -1; + return -1; + } + + write_queue_init(&mgcp_agent, 10); + mgcp_agent.bfd.when = BSC_FD_READ; + mgcp_agent.read_cb = mgcp_do_read; + mgcp_agent.write_cb = mgcp_do_write; + + if (bsc_register_fd(&mgcp_agent.bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n"); + close(mgcp_agent.bfd.fd); + mgcp_agent.bfd.fd = -1; + return -1; + } + + + return 0; +} + + +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 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(bsc_gsmnet, msgb, length - sizeof(*bs)); + break; + default: + LOGP(DMSC, LOGL_ERROR, "Unimplemented msg type: %d\n", bs->type); + } + + return 0; +} + + +/* + * network initialisation + */ +static void initialize_if_needed(void) +{ + struct msgb *msg; + + + if (!msc_con->is_authenticated) { + /* send a gsm 08.08 reset message from here */ + msg = bssmap_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); + msc_con->is_authenticated = 1; + } +} + +static void send_id_get_response(int fd) +{ + struct msgb *msg; + if (!bsc_gsmnet) { + LOGP(DMSC, LOGL_ERROR, "The network is not initialized yet.\n"); + return; + } + + if (!bsc_gsmnet->bsc_token) { + LOGP(DMSC, LOGL_ERROR, "The bsc token is not set.\n"); + return; + } + + msg = msgb_alloc_headroom(4096, 128, "id resp"); + + msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP); + msgb_l16tv_put(msg, strlen(bsc_gsmnet->bsc_token) + 1, + IPAC_IDTAG_UNITNAME, (u_int8_t *) bsc_gsmnet->bsc_token); + msc_queue_write(msg, IPAC_PROTO_IPACCESS); +} + +/* + * 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 bss_sccp_connection_data *bss, *tmp; + + LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n"); + + llist_for_each_entry_safe(bss, tmp, &active_connections, active_connections) { + if (bss->lchan) { + bss->lchan->msc_data = NULL; + put_subscr_con(&bss->lchan->conn, 0); + bss->lchan = NULL; + } + + if (bss->secondary_lchan) { + bss->secondary_lchan->msc_data = NULL; + put_subscr_con(&bss->secondary_lchan->conn, 0); + bss->secondary_lchan = NULL; + } + + /* force the close by poking stuff */ + if (bss->sccp) { + sccp_connection_force_free(bss->sccp); + bss->sccp = NULL; + } + + bss_sccp_free_data(bss); + } + + msc->is_authenticated = 0; + bsc_msc_schedule_connect(msc); +} + +/* + * callback with IP access data + */ +static int ipaccess_a_fd_cb(struct bsc_fd *bfd) +{ + int error; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + + if (!msg) { + if (error == 0) { + LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n"); + bsc_msc_lost(msc_con); + return -1; + } + + fprintf(stderr, "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(); + else if (msg->l2h[0] == IPAC_MSGT_ID_GET) { + send_id_get_response(bfd->fd); + } + } else if (hh->proto == IPAC_PROTO_SCCP) { + sccp_system_incoming(msg); + } else if (hh->proto == NAT_IPAC_PROTO_MGCP) { + mgcp_forward(msg); + } + + msgb_free(msg); + return 0; +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -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(" -m --msc=IP. The address of the MSC.\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"); +} + +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'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"msc", 1, 0, 'm'}, + {"local", 1, 0, 'l'}, + {"log-level", 1, 0, 'e'}, + {"rf-ctl", 1, 0, 'r'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:sTc:m:l:e:r:", + 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 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'P': + ipacc_rtp_direct = 0; + break; + case 'm': + msc_address = optarg; + break; + case 'l': + inet_aton(optarg, &local_addr); + break; + case 'e': + log_set_log_level(stderr_target, atoi(optarg)); + break; + case 'r': + rf_ctl = optarg; + break; + default: + /* ignore */ + break; + } + } +} + +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + if (bsc_gsmnet) { + bsc_shutdown_net(bsc_gsmnet); + 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_full(tall_bsc_ctx, stderr); + break; + case SIGUSR2: + if (!msc_con || !msc_con->is_connected) + return; + bsc_msc_lost(msc_con); + break; + default: + break; + } +} + +static void test_mode() +{ + static const u_int8_t assignment_req[] = { 0x01, 0x0b, 0x03, 0x01, 0x0b, 0x25, 0x01, 0x00, 0x01 }; + struct gsm_lchan lchan; + struct sccp_connection conn; + struct bss_sccp_connection_data data; + + struct gsm_bts_trx_ts trx_ts; + struct gsm_bts_trx trx; + struct gsm_bts bts; + int rc; + + /* initialize */ + fprintf(stderr, "Bootstraping the network. Sending GSM08.08 reset.\n"); + rc = bsc_bootstrap_network(NULL, config_file); + if (rc < 0) { + fprintf(stderr, "Bootstrapping the network failed. exiting.\n"); + exit(1); + } + + bts.network = bsc_gsmnet; + trx.bts = &bts; + trx_ts.trx = &trx; + lchan.ts = &trx_ts; + + /* create fake data connection */ + data.lchan = &lchan; + data.sccp = &conn; + lchan.msc_data = &data; + conn.data_ctx = &data; + + + struct msgb *msg = msgb_alloc(400, "test-msg"); + msg->lchan = &lchan; + + msg->l4h = msgb_put(msg, ARRAY_SIZE(assignment_req)); + memcpy(msg->l4h, assignment_req, ARRAY_SIZE(assignment_req)); + bssmap_rcvmsg_dt1(&conn, msg, ARRAY_SIZE(assignment_req)); +} + +extern int bts_model_unknown_init(void); +extern int bts_model_bs11_init(void); +extern int bts_model_nanobts_init(void); + +int main(int argc, char **argv) +{ + char *msc; + 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); + + /* parse options */ + handle_options(argc, argv); + + /* seed the PRNG */ + srand(time(NULL)); + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + /* attempt to register the local mgcp forward */ + if (mgcp_create_port() != 0) { + fprintf(stderr, "Failed to bind local MGCP port\n"); + exit(1); + } + + /* initialize sccp */ + sccp_system_init(msc_sccp_write_ipa, NULL); + sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL); + sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, NULL); + + /* initialize ipaccess handling */ + register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL); + + fprintf(stderr, "Bootstraping the network. Sending GSM08.08 reset.\n"); + rc = bsc_bootstrap_network(NULL, config_file); + if (rc < 0) { + fprintf(stderr, "Bootstrapping the network failed. exiting.\n"); + exit(1); + } + + if (rf_ctl) { + struct bsc_msc_rf *rf; + rf = bsc_msc_rf_create(rf_ctl, bsc_gsmnet); + if (!rf) { + fprintf(stderr, "Failed to create the RF service.\n"); + exit(1); + } + } + + /* setup MSC Connection handling */ + msc = bsc_gsmnet->msc_ip; + if (msc_address) + msc = msc_address; + + msc_con = bsc_msc_create(msc, bsc_gsmnet->msc_port); + if (!msc_con) { + fprintf(stderr, "Creating a bsc_msc_connection failed.\n"); + exit(1); + } + + msc_con->connection_loss = msc_connection_was_lost; + msc_con->write_queue.read_cb = ipaccess_a_fd_cb; + msc_con->write_queue.write_cb = msc_sccp_do_write; + bsc_msc_connect(msc_con); + + + + while (1) { + bsc_select_main(0); + } +} diff --git a/openbsc/src/bsc_msc_rf.c b/openbsc/src/bsc_msc_rf.c new file mode 100644 index 000000000..ac79e534a --- /dev/null +++ b/openbsc/src/bsc_msc_rf.c @@ -0,0 +1,249 @@ +/* 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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bsc_msc_rf.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_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' + +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; +} + +/* + * Send a '1' when one TRX is online, otherwise send 0 + */ +static void handle_query(struct bsc_msc_rf_conn *conn) +{ + struct msgb *msg; + struct gsm_bts *bts; + char send = '0'; + + llist_for_each_entry(bts, &conn->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 = '1'; + break; + } + } + } + + 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; +} + +static int rf_read_cmd(struct bsc_fd *fd) +{ + struct bsc_msc_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_OFF: + lock_each_trx(conn->gsm_network, 1); + break; + case RF_CMD_ON: + lock_each_trx(conn->gsm_network, 0); + break; + default: + 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 bsc_msc_rf_conn *conn; + struct bsc_msc_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 bsc_msc_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->gsm_network = rf->gsm_network; + + if (bsc_register_fd(&conn->queue.bfd) != 0) { + close(fd); + talloc_free(conn); + return -1; + } + + return 0; +} + +struct bsc_msc_rf *bsc_msc_rf_create(const char *path, struct gsm_network *net) +{ + unsigned int namelen; + struct sockaddr_un local; + struct bsc_fd *bfd; + struct bsc_msc_rf *rf; + int rc; + + rf = talloc_zero(NULL, struct bsc_msc_rf); + if (!rf) { + LOGP(DINP, LOGL_ERROR, "Failed to create bsc_msc_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; + + return rf; +} + diff --git a/openbsc/src/bsc_rll.c b/openbsc/src/bsc_rll.c index 9a4f5aae4..368c2665b 100644 --- a/openbsc/src/bsc_rll.c +++ b/openbsc/src/bsc_rll.c @@ -55,7 +55,7 @@ static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type) conn = &rllr->lchan->conn; llist_del(&rllr->list); - put_subscr_con(conn); + put_subscr_con(conn, 0); rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type); talloc_free(rllr); } diff --git a/openbsc/src/bssap.c b/openbsc/src/bssap.c new file mode 100644 index 000000000..1c84073da --- /dev/null +++ b/openbsc/src/bssap.c @@ -0,0 +1,1337 @@ +/* GSM 08.08 BSSMAP handling */ +/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009 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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bssap.h> +#include <openbsc/bsc_rll.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/debug.h> +#include <openbsc/mgcp.h> +#include <openbsc/signal.h> +#include <openbsc/paging.h> +#include <openbsc/chan_alloc.h> + +#include <osmocore/gsm0808.h> + +#include <sccp/sccp.h> + +#include <arpa/inet.h> +#include <assert.h> + + +#define BSSMAP_MSG_SIZE 512 +#define BSSMAP_MSG_HEADROOM 128 + +static void bts_queue_send(struct msgb *msg, int link_id); +static void bssmap_free_secondary(struct bss_sccp_connection_data *data); + + +static u_int16_t get_network_code_for_msc(struct gsm_network *net) +{ + if (net->core_network_code > 0) + return net->core_network_code; + return net->network_code; +} + +static u_int16_t get_country_code_for_msc(struct gsm_network *net) +{ + if (net->core_country_code > 0) + return net->core_country_code; + return net->country_code; +} + +static int bssmap_paging_cb(unsigned int hooknum, unsigned int event, struct msgb *msg, void *data, void *param) +{ + LOGP(DPAG, LOGL_DEBUG, "Paging is complete.\n"); + return 0; +} + +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 tlv_parsed tp; + char mi_string[GSM48_MI_SIZE]; + u_int32_t tmsi = GSM_RESERVED_TMSI; + unsigned int lac = GSM_LAC_RESERVED_ALL_BTS; + u_int8_t data_length; + const u_int8_t *data; + struct gsm_subscriber *subscr; + u_int8_t chan_needed = RSL_CHANNEED_ANY; + int paged; + + 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) { + unsigned int *_lac = (unsigned int *)&data[1]; + lac = ntohs(*_lac); + } 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"); + } + + LOGP(DMSC, LOGL_DEBUG, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac); + subscr = subscr_get_or_create(net, mi_string); + if (!subscr) + return -1; + + /* reassign the tmsi, trust the net over our internal state */ + subscr->tmsi = tmsi; + subscr->lac = lac; + paged = paging_request(net, subscr, chan_needed, bssmap_paging_cb, subscr); + LOGP(DPAG, LOGL_DEBUG, "Paged IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x on #bts: %d\n", mi_string, tmsi, tmsi, lac, paged); + + subscr_put(subscr); + return -1; +} + +/* GSM 08.08 § 3.1.9.1 and 3.2.1.21... release our gsm_lchan and send message */ +static int bssmap_handle_clear_command(struct sccp_connection *conn, + struct msgb *msg, unsigned int payload_length) +{ + struct msgb *resp; + + /* TODO: handle the cause of this package */ + + if (msg->lchan) { + LOGP(DMSC, LOGL_DEBUG, "Releasing all transactions on %p\n", conn); + bsc_del_timer(&msg->lchan->msc_data->T10); + msg->lchan->msc_data->lchan = NULL; + + /* we might got killed during an assignment */ + bssmap_free_secondary(msg->lchan->msc_data); + + msg->lchan->msc_data = NULL; + put_subscr_con(&msg->lchan->conn, 0); + } + + /* send the clear complete message */ + resp = bssmap_create_clear_complete(); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n"); + return -1; + } + + bsc_queue_connection_write(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 sccp_connection *conn, + struct msgb *msg, unsigned int payload_length) +{ + u_int16_t len; + struct gsm_network *network = NULL; + const u_int8_t *data; + struct tlv_parsed tp; + struct msgb *resp; + int reject_cause = -1; + int include_imeisv = 1; + + if (!msg->lchan || !msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + goto reject; + } + + if (msg->lchan->msc_data->ciphering_handled) { + LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n"); + goto reject; + } + + msg->lchan->msc_data->ciphering_handled = 1; + msg->lchan->msc_data->block_gsm = 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 = msg->lchan->ts->trx->bts->network; + data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION); + + if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) { + msg->lchan->encr.alg_id = RSL_ENC_ALG_A5(0); + } else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) { + msg->lchan->encr.alg_id = RSL_ENC_ALG_A5(1); + msg->lchan->encr.key_len = len - 1; + memcpy(msg->lchan->encr.key, &data[1], len - 1); + } else { + LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n"); + goto reject; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)) { + include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1; + } + + return gsm48_send_rr_ciph_mode(msg->lchan, include_imeisv); + +reject: + if (msg->lchan && msg->lchan->msc_data) + msg->lchan->msc_data->block_gsm = 0; + + resp = bssmap_create_cipher_reject(reject_cause); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n"); + return -1; + } + + bsc_queue_connection_write(conn, resp); + return -1; +} + +/* + * handle network failures... and free the secondary lchan + */ +static void bssmap_free_secondary(struct bss_sccp_connection_data *data) +{ + struct gsm_lchan *lchan; + + if (!data || !data->secondary_lchan) + return; + + lchan = data->secondary_lchan; + if (lchan->msc_data != data) { + LOGP(DMSC, LOGL_ERROR, "MSC data does not match on lchan and cb.\n"); + data->secondary_lchan = NULL; + } + + /* give up additional data */ + lchan->msc_data->secondary_lchan = NULL; + if (lchan->msc_data->lchan == lchan) + lchan->msc_data->lchan = NULL; + lchan->msc_data = NULL; + + /* give up the new channel to not do a SACCH deactivate */ + subscr_put(lchan->conn.subscr); + lchan->conn.subscr = NULL; + put_subscr_con(&lchan->conn, 1); +} + +/* + * Handle the network configurable T10 parameter + */ +static void bssmap_t10_fired(void *_conn) +{ + struct bss_sccp_connection_data *msc_data; + struct sccp_connection *conn = (struct sccp_connection *) _conn; + struct msgb *resp; + + LOGP(DMSC, LOGL_ERROR, "T10 fired, assignment failed: %p\n", conn); + + /* free the secondary channel if we have one */ + msc_data = conn->data_ctx; + bssmap_free_secondary(msc_data); + + resp = bssmap_create_assignment_failure( + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Allocation failure: %p\n", conn); + return; + } + + bsc_queue_connection_write(conn, resp); +} + +/* + * 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; + } + + assert(0); +} + +/* + * The assignment request has started T10. We need to be faster than this + * or an assignment failure will be sent... + * + * 1.) allocate a new lchan + * 2.) copy the encryption key and other data from the + * old to the new channel. + * 3.) RSL Channel Activate this channel and wait + * + * -> Signal handler for the LCHAN + * 4.) Send GSM 04.08 assignment command to the MS + * + * -> Assignment Complete + * 5.) Release the SDCCH, continue signalling on the new link + */ +static int handle_new_assignment(struct msgb *msg, int full_rate, int chan_mode) +{ + struct bss_sccp_connection_data *msc_data; + struct gsm_bts *bts; + struct gsm_lchan *new_lchan; + int chan_type; + + msc_data = msg->lchan->msc_data; + bts = msg->lchan->ts->trx->bts; + chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; + + new_lchan = lchan_alloc(bts, chan_type, 0); + + if (!new_lchan) { + LOGP(DMSC, LOGL_NOTICE, "No free channel.\n"); + return -1; + } + + /* copy old data to the new channel */ + memcpy(&new_lchan->encr, &msg->lchan->encr, sizeof(new_lchan->encr)); + new_lchan->ms_power = msg->lchan->ms_power; + new_lchan->bs_power = msg->lchan->bs_power; + new_lchan->conn.subscr = subscr_get(msg->lchan->conn.subscr); + + /* copy new data to it */ + use_subscr_con(&new_lchan->conn); + new_lchan->tch_mode = chan_mode; + new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + + /* handle AMR correctly */ + if (chan_mode == GSM48_CMODE_SPEECH_AMR) { + new_lchan->mr_conf.ver = 1; + new_lchan->mr_conf.icmi = 1; + new_lchan->mr_conf.m5_90 = 1; + } + + if (rsl_chan_activate_lchan(new_lchan, 0x1, 0, 0) < 0) { + LOGP(DHO, LOGL_ERROR, "could not activate channel\n"); + lchan_free(new_lchan); + return -1; + } + + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); + msc_data->secondary_lchan = new_lchan; + new_lchan->msc_data = msc_data; + return 0; +} + +/* + * Any failure will be caught with the T10 timer ticking... + */ +static void continue_new_assignment(struct gsm_lchan *new_lchan) +{ + if (!new_lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No BSS data found.\n"); + put_subscr_con(&new_lchan->conn, 0); + return; + } + + if (new_lchan->msc_data->secondary_lchan != new_lchan) { + LOGP(DMSC, LOGL_ERROR, "This is not the secondary channel?\n"); + new_lchan->msc_data = NULL; + put_subscr_con(&new_lchan->conn, 0); + return; + } + + LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", new_lchan); + gsm48_send_rr_ass_cmd(new_lchan->msc_data->lchan, new_lchan, 0x3); +} + +/* + * Handle the assignment request message. + * + * See §3.2.1.1 for the message type + */ +static int bssmap_handle_assignm_req(struct sccp_connection *conn, + struct msgb *msg, unsigned int length) +{ + struct gsm_network *network; + struct tlv_parsed tp; + struct bss_sccp_connection_data *msc_data; + u_int8_t *data; + u_int16_t cic; + u_int8_t timeslot; + u_int8_t multiplex; + enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN; + int i, supported, port, full_rate = -1; + + if (!msg->lchan || !msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + return -1; + } + + msc_data = msg->lchan->msc_data; + network = msg->lchan->ts->trx->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(*(u_int16_t *)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 = (u_int8_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->audio_length; + ++supported) { + + int perm_val = audio_support_to_gsm88(network->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; + } + + /* modify the channel now */ + msc_data->T10.cb = bssmap_t10_fired; + msc_data->T10.data = conn; + bsc_schedule_timer(&msc_data->T10, GSM0808_T10_VALUE); + + /* the mgcp call agent starts counting at one. a bit of a weird mapping */ + port = mgcp_timeslot_to_endpoint(multiplex, timeslot); + msc_data->rtp_port = rtp_calculate_port(port, + network->rtp_base_port); + + if (msg->lchan->type == GSM_LCHAN_SDCCH) { + /* start to assign a new channel, if it works */ + if (handle_new_assignment(msg, full_rate, chan_mode) == 0) + return 0; + else + goto reject; + } else { + LOGP(DMSC, LOGL_ERROR, "Sending ChanModify for speech on: sccp: %p mode: 0x%x on port %d %d/0x%x port: %u\n", + conn, chan_mode, port, multiplex, timeslot, msc_data->rtp_port); + + if (chan_mode == GSM48_CMODE_SPEECH_AMR) { + msg->lchan->mr_conf.ver = 1; + msg->lchan->mr_conf.icmi = 1; + msg->lchan->mr_conf.m5_90 = 1; + } + + return gsm48_lchan_modify(msg->lchan, chan_mode); + } + +reject: + gsm0808_send_assignment_failure(msg->lchan, + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); + return -1; +} + +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: + ret = bssmap_handle_paging(net, msg, length); + break; + } + + return ret; +} + +int bssmap_rcvmsg_dt1(struct sccp_connection *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; +} + +int dtap_rcvmsg(struct gsm_lchan *lchan, struct msgb *msg, unsigned int length) +{ + struct dtap_header *header; + struct msgb *gsm48; + u_int8_t *data; + u_int8_t link_id; + + if (!lchan) { + LOGP(DMSC, LOGL_ERROR, "No lchan 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->lchan = lchan; + gsm48->trx = gsm48->lchan->ts->trx; + gsm48->l3h = gsm48->data; + data = msgb_put(gsm48, length - sizeof(*header)); + memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header)); + + /* + * This is coming from the network. We need to regenerate the + * LAI for the Location Update Accept packet and maybe more + * as well. + */ + if (gsm48->trx->bts->network->core_network_code > 0 || + gsm48->trx->bts->network->core_country_code > 0) { + if (msgb_l3len(gsm48) >= sizeof(struct gsm48_loc_area_id) + 1) { + struct gsm48_hdr *gh = (struct gsm48_hdr *)gsm48->l3h; + if (gh->msg_type == GSM48_MT_MM_LOC_UPD_ACCEPT) { + struct gsm_network *net = gsm48->trx->bts->network; + struct gsm48_loc_area_id *lai = (struct gsm48_loc_area_id *) &gh->data[0]; + gsm48_generate_lai(lai, net->country_code, + net->network_code, + gsm48->trx->bts->location_area_code); + } + } + } + + link_id = header->link_id; + + /* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */ + if ((lchan->type == GSM_LCHAN_TCH_F || + lchan->type == GSM_LCHAN_TCH_H) && (link_id & 0x7) != 0) + link_id |= 0x40; + + bts_queue_send(gsm48, link_id); + return 0; +} + +/* Create messages */ +struct msgb *bssmap_create_layer3(struct msgb *msg_l3) +{ + u_int8_t *data; + u_int16_t *ci; + struct msgb* msg; + struct gsm48_loc_area_id *lai; + struct gsm_bts *bts = msg_l3->lchan->ts->trx->bts; + u_int16_t network_code = get_network_code_for_msc(bts->network); + u_int16_t country_code = get_country_code_for_msc(bts->network); + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, + "bssmap cmpl l3"); + if (!msg) + return NULL; + + /* create the bssmap header */ + msg->l3h = msgb_put(msg, 2); + msg->l3h[0] = 0x0; + + /* create layer 3 header */ + data = msgb_put(msg, 1); + data[0] = BSS_MAP_MSG_COMPLETE_LAYER_3; + + /* create the cell header */ + data = msgb_put(msg, 3); + data[0] = GSM0808_IE_CELL_IDENTIFIER; + data[1] = 1 + sizeof(*lai) + 2; + data[2] = CELL_IDENT_WHOLE_GLOBAL; + + lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai)); + gsm48_generate_lai(lai, country_code, + network_code, bts->location_area_code); + + ci = (u_int16_t *) msgb_put(msg, 2); + *ci = htons(bts->cell_identity); + + /* copy the layer3 data */ + data = msgb_put(msg, msgb_l3len(msg_l3) + 2); + data[0] = GSM0808_IE_LAYER_3_INFORMATION; + data[1] = msgb_l3len(msg_l3); + memcpy(&data[2], msg_l3->l3h, data[1]); + + /* update the size */ + msg->l3h[1] = msgb_l3len(msg) - 2; + + return msg; +} + +struct msgb *bssmap_create_reset(void) +{ + struct msgb *msg = msgb_alloc(30, "bssmap: reset"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 6); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 0x04; + msg->l3h[2] = 0x30; + msg->l3h[3] = 0x04; + msg->l3h[4] = 0x01; + msg->l3h[5] = 0x20; + return msg; +} + +struct msgb *bssmap_create_clear_complete(void) +{ + struct msgb *msg = msgb_alloc(30, "bssmap: clear complete"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 3); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 1; + msg->l3h[2] = BSS_MAP_MSG_CLEAR_COMPLETE; + + return msg; +} + +struct msgb *bssmap_create_cipher_complete(struct msgb *layer3) +{ + struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, + "cipher-complete"); + if (!msg) + return NULL; + + /* send response with BSS override for A5/1... cheating */ + msg->l3h = msgb_put(msg, 3); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 0xff; + msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_COMPLETE; + + /* include layer3 in case we have at least two octets */ + if (layer3 && msgb_l3len(layer3) > 2) { + msg->l4h = msgb_put(msg, msgb_l3len(layer3) + 2); + msg->l4h[0] = GSM0808_IE_LAYER_3_MESSAGE_CONTENTS; + msg->l4h[1] = msgb_l3len(layer3); + memcpy(&msg->l4h[2], layer3->l3h, msgb_l3len(layer3)); + } + + /* and the optional BSS message */ + msg->l4h = msgb_put(msg, 2); + msg->l4h[0] = GSM0808_IE_CHOSEN_ENCR_ALG; + msg->l4h[1] = layer3->lchan->encr.alg_id; + + /* update the size */ + msg->l3h[1] = msgb_l3len(msg) - 2; + return msg; +} + +struct msgb *bssmap_create_cipher_reject(u_int8_t cause) +{ + struct msgb *msg = msgb_alloc(30, "bssmap: clear complete"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 3); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 2; + msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_REJECT; + msg->l3h[3] = cause; + + return msg; +} + +struct msgb *bssmap_create_classmark_update(const u_int8_t *classmark_data, u_int8_t length) +{ + struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, + "classmark-update"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 3); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 0xff; + msg->l3h[2] = BSS_MAP_MSG_CLASSMARK_UPDATE; + + msg->l4h = msgb_put(msg, length); + memcpy(msg->l4h, classmark_data, length); + + /* update the size */ + msg->l3h[1] = msgb_l3len(msg) - 2; + return msg; +} + +struct msgb *bssmap_create_sapi_reject(u_int8_t link_id) +{ + struct msgb *msg = msgb_alloc(30, "bssmap: sapi 'n' reject"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 5); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 3; + msg->l3h[2] = BSS_MAP_MSG_SAPI_N_REJECT; + msg->l3h[3] = link_id; + msg->l3h[4] = GSM0808_CAUSE_BSS_NOT_EQUIPPED; + + return msg; +} + +static u_int8_t chan_mode_to_speech(struct gsm_lchan *lchan) +{ + int mode = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + mode = 1; + break; + case GSM48_CMODE_SPEECH_EFR: + mode = 0x11; + break; + case GSM48_CMODE_SPEECH_AMR: + mode = 0x21; + break; + case GSM48_CMODE_SIGN: + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + default: + LOGP(DMSC, LOGL_ERROR, "Using non speech mode: %d\n", mode); + return 0; + break; + } + + if (lchan->type == GSM_LCHAN_TCH_H) + mode |= 0x4; + + return mode; +} + +/* 3.2.2.33 */ +static u_int8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) +{ + u_int8_t channel_mode = 0, channel = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + channel_mode = 0x9; + break; + case GSM48_CMODE_SIGN: + channel_mode = 0x8; + break; + case GSM48_CMODE_DATA_14k5: + channel_mode = 0xe; + break; + case GSM48_CMODE_DATA_12k0: + channel_mode = 0xb; + break; + case GSM48_CMODE_DATA_6k0: + channel_mode = 0xc; + break; + case GSM48_CMODE_DATA_3k6: + channel_mode = 0xd; + break; + } + + switch (lchan->type) { + case GSM_LCHAN_NONE: + channel = 0x0; + break; + case GSM_LCHAN_SDCCH: + channel = 0x1; + break; + case GSM_LCHAN_TCH_F: + channel = 0x8; + break; + case GSM_LCHAN_TCH_H: + channel = 0x9; + break; + case GSM_LCHAN_UNKNOWN: + LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan); + break; + } + + return channel_mode << 4 | channel; +} + +struct msgb *bssmap_create_assignment_completed(struct gsm_lchan *lchan, u_int8_t rr_cause) +{ + u_int8_t *data; + u_int8_t speech_mode; + + struct msgb *msg = msgb_alloc(35, "bssmap: ass compl"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 3); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 0xff; + msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_COMPLETE; + + /* write 3.2.2.22 */ + data = msgb_put(msg, 2); + data[0] = GSM0808_IE_RR_CAUSE; + data[1] = rr_cause; + + /* write cirtcuit identity code 3.2.2.2 */ + /* write cell identifier 3.2.2.17 */ + /* write chosen channel 3.2.2.33 when BTS picked it */ + data = msgb_put(msg, 2); + data[0] = GSM0808_IE_CHOSEN_CHANNEL; + data[1] = lchan_to_chosen_channel(lchan); + + /* write chosen encryption algorithm 3.2.2.44 */ + data = msgb_put(msg, 2); + data[0] = GSM0808_IE_CHOSEN_ENCR_ALG; + data[1] = lchan->encr.alg_id; + + /* write circuit pool 3.2.2.45 */ + /* write speech version chosen: 3.2.2.51 when BTS picked it */ + speech_mode = chan_mode_to_speech(lchan); + if (speech_mode != 0) { + data = msgb_put(msg, 2); + data[0] = GSM0808_IE_SPEECH_VERSION; + data[1] = speech_mode; + } + + /* write LSA identifier 3.2.2.15 */ + + + /* update the size */ + msg->l3h[1] = msgb_l3len(msg) - 2; + return msg; +} + +struct msgb *bssmap_create_assignment_failure(u_int8_t cause, u_int8_t *rr_cause) +{ + u_int8_t *data; + struct msgb *msg = msgb_alloc(35, "bssmap: ass fail"); + if (!msg) + return NULL; + + msg->l3h = msgb_put(msg, 6); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 0xff; + msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_FAILURE; + msg->l3h[3] = GSM0808_IE_CAUSE; + msg->l3h[4] = 1; + msg->l3h[5] = cause; + + /* RR cause 3.2.2.22 */ + if (rr_cause) { + data = msgb_put(msg, 2); + data[0] = GSM0808_IE_RR_CAUSE; + data[1] = *rr_cause; + } + + /* Circuit pool 3.22.45 */ + /* Circuit pool list 3.2.2.46 */ + + /* update the size */ + msg->l3h[1] = msgb_l3len(msg) - 2; + return msg; +} + +struct msgb *dtap_create_msg(struct msgb *msg_l3, u_int8_t link_id) +{ + struct dtap_header *header; + u_int8_t *data; + struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, + "dtap"); + if (!msg) + return NULL; + + /* DTAP header */ + msg->l3h = msgb_put(msg, sizeof(*header)); + header = (struct dtap_header *) &msg->l3h[0]; + header->type = BSSAP_MSG_DTAP; + header->link_id = link_id; + header->length = msgb_l3len(msg_l3); + + /* Payload */ + data = msgb_put(msg, header->length); + memcpy(data, msg_l3->l3h, header->length); + + return msg; +} + +static int bssap_handle_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct msgb *msg; + struct gsm_lchan *lchan; + struct sccp_connection *conn; + + /* + * If we have a SCCP Connection we need to inform the MSC about + * the resource error and then drop the lchan<->sccp association. + */ + switch (subsys) { + case SS_LCHAN: + lchan = (struct gsm_lchan *)signal_data; + + if (!lchan || !lchan->msc_data) + return 0; + switch (signal) { + case S_LCHAN_UNEXPECTED_RELEASE: + /* handle this through the T10 timeout */ + if (lchan->msc_data->lchan != lchan) { + if (lchan->msc_data->secondary_lchan == lchan) { + LOGP(DMSC, LOGL_NOTICE, "Setting secondary to NULL.\n"); + lchan->msc_data->secondary_lchan = NULL; + lchan->msc_data = NULL; + } + return 0; + } + + bsc_del_timer(&lchan->msc_data->T10); + conn = lchan->msc_data->sccp; + lchan->msc_data->lchan = NULL; + lchan->msc_data = NULL; + + msg = msgb_alloc(30, "sccp: clear request"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate clear request.\n"); + return 0; + } + + msg->l3h = msgb_put(msg, 2 + 4); + msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT; + msg->l3h[1] = 4; + + msg->l3h[2] = BSS_MAP_MSG_CLEAR_RQST; + msg->l3h[3] = GSM0808_IE_CAUSE; + msg->l3h[4] = 1; + msg->l3h[5] = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE; + + LOGP(DMSC, LOGL_NOTICE, "Sending clear request on unexpected channel release.\n"); + bsc_queue_connection_write(conn, msg); + break; + case S_LCHAN_ACTIVATE_ACK: + continue_new_assignment(lchan); + break; + } + break; + } + + return 0; +} + +/* + * queue handling for BSS AP + */ +void bsc_queue_connection_write(struct sccp_connection *conn, struct msgb *msg) +{ + struct bss_sccp_connection_data *data; + + data = (struct bss_sccp_connection_data *)conn->data_ctx; + + if (conn->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", conn); + msgb_free(msg); + } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED + && data->sccp_queue_size == 0) { + sccp_connection_write(conn, msg); + msgb_free(msg); + } else if (data->sccp_queue_size > 10) { + LOGP(DMSC, LOGL_ERROR, "Dropping packet on %p due queue overflow\n", conn); + msgb_free(msg); + } else { + LOGP(DMSC, LOGL_DEBUG, "Queuing packet on %p. Queue size: %d\n", conn, data->sccp_queue_size); + ++data->sccp_queue_size; + msgb_enqueue(&data->sccp_queue, msg); + } +} + +void bsc_free_queued(struct sccp_connection *conn) +{ + struct bss_sccp_connection_data *data; + struct msgb *msg; + + data = (struct bss_sccp_connection_data *)conn->data_ctx; + while (!llist_empty(&data->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&data->sccp_queue); + msgb_free(msg); + } + + data->sccp_queue_size = 0; +} + +void bsc_send_queued(struct sccp_connection *conn) +{ + struct bss_sccp_connection_data *data; + struct msgb *msg; + + data = (struct bss_sccp_connection_data *)conn->data_ctx; + + while (!llist_empty(&data->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&data->sccp_queue); + sccp_connection_write(conn, msg); + msgb_free(msg); + --data->sccp_queue_size; + } +} + +/* RLL callback */ +static void rll_ind_cb(struct gsm_lchan *lchan, u_int8_t link_id, + void *_data, enum bsc_rllr_ind rllr_ind) +{ + struct sccp_source_reference ref = sccp_src_ref_from_int((u_int32_t) _data); + struct bss_sccp_connection_data *data = lchan->msc_data; + + if (!data || !data->sccp) { + LOGP(DMSC, LOGL_ERROR, "Time-out/Establish after sccp release? Ind: %d lchan: %p\n", + rllr_ind, lchan); + return; + } + + if (memcmp(&data->sccp->source_local_reference, &ref, sizeof(ref)) != 0) { + LOGP(DMSC, LOGL_ERROR, "Wrong SCCP connection. Not handling RLL callback: %u %u\n", + sccp_src_ref_to_int(&ref), + sccp_src_ref_to_int(&data->sccp->source_local_reference)); + return; + } + + switch (rllr_ind) { + case BSC_RLLR_IND_EST_CONF: + /* nothing to do */ + bts_send_queued(data); + break; + case BSC_RLLR_IND_REL_IND: + case BSC_RLLR_IND_ERR_IND: + case BSC_RLLR_IND_TIMEOUT: { + /* reject queued messages */ + struct msgb *sapi_reject; + + bts_free_queued(data); + sapi_reject = bssmap_create_sapi_reject(link_id); + if (!sapi_reject){ + LOGP(DMSC, LOGL_ERROR, "Failed to create SAPI reject\n"); + return; + } + + bsc_queue_connection_write(data->sccp, sapi_reject); + break; + } + } +} + +/* decide if we need to queue because of SAPI != 0 */ +static void bts_queue_send(struct msgb *msg, int link_id) +{ + + struct bss_sccp_connection_data *data; + + if (!msg->lchan || !msg->lchan->msc_data) { + LOGP(DMSC, LOGL_ERROR, "BAD: Wrongly configured lchan: %p\n", msg->lchan); + msgb_free(msg); + } + + data = msg->lchan->msc_data; + + if (!data->block_gsm && data->gsm_queue_size == 0) { + if (msg->lchan->sapis[link_id & 0x7] != LCHAN_SAPI_UNUSED) { + rsl_data_request(msg, link_id); + } else { + msg->smsh = (unsigned char*) link_id; + msgb_enqueue(&data->gsm_queue, msg); + ++data->gsm_queue_size; + + /* establish link */ + rll_establish(msg->lchan, link_id & 0x7, + rll_ind_cb, + (void *)sccp_src_ref_to_int(&data->sccp->source_local_reference)); + } + } else if (data->gsm_queue_size == 10) { + LOGP(DMSC, LOGL_ERROR, "Queue full on %p. Dropping GSM0408.\n", data->sccp); + msgb_free(msg); + } else { + LOGP(DMSC, LOGL_DEBUG, "Queueing GSM0408 message on %p. Queue size: %d\n", + data->sccp, data->gsm_queue_size + 1); + + msg->smsh = (unsigned char*) link_id; + msgb_enqueue(&data->gsm_queue, msg); + ++data->gsm_queue_size; + } +} + +void bts_free_queued(struct bss_sccp_connection_data *data) +{ + struct msgb *msg; + + while (!llist_empty(&data->gsm_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&data->gsm_queue); + msgb_free(msg); + } + + data->gsm_queue_size = 0; +} + +void bts_send_queued(struct bss_sccp_connection_data *data) +{ + struct msgb *msg; + + while (!llist_empty(&data->gsm_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&data->gsm_queue); + rsl_data_request(msg, (int) msg->smsh); + } + + data->gsm_queue_size = 0; +} + +void bts_unblock_queue(struct bss_sccp_connection_data *data) +{ + struct msgb *msg; + LLIST_HEAD(head); + + /* move the messages to a new list */ + data->block_gsm = 0; + data->gsm_queue_size = 0; + while (!llist_empty(&data->gsm_queue)) { + msg = msgb_dequeue(&data->gsm_queue); + msgb_enqueue(&head, msg); + } + + /* now queue them again to send RSL establish and such */ + while (!llist_empty(&head)) { + msg = msgb_dequeue(&head); + bts_queue_send(msg, (int) msg->smsh); + } +} + +void gsm0808_send_assignment_failure(struct gsm_lchan *lchan, u_int8_t cause, u_int8_t *rr_value) +{ + struct msgb *resp; + + bsc_del_timer(&lchan->msc_data->T10); + bssmap_free_secondary(lchan->msc_data); + resp = bssmap_create_assignment_failure(cause, rr_value); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Allocation failure: %p\n", lchan_get_sccp(lchan)); + return; + } + + bsc_queue_connection_write(lchan_get_sccp(lchan), resp); +} + +void gsm0808_send_assignment_compl(struct gsm_lchan *lchan, u_int8_t rr_cause) +{ + struct msgb *resp; + + bsc_del_timer(&lchan->msc_data->T10); + resp = bssmap_create_assignment_completed(lchan, rr_cause); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Creating MSC response failed: %p\n", lchan_get_sccp(lchan)); + return; + } + + bsc_queue_connection_write(lchan_get_sccp(lchan), resp); +} + +static __attribute__((constructor)) void on_dso_load_bssap(void) +{ + register_signal_handler(SS_LCHAN, bssap_handle_lchan_signal, NULL); +} diff --git a/openbsc/src/chan_alloc.c b/openbsc/src/chan_alloc.c index 107abdc92..40d655409 100644 --- a/openbsc/src/chan_alloc.c +++ b/openbsc/src/chan_alloc.c @@ -33,8 +33,6 @@ #include <openbsc/debug.h> #include <openbsc/signal.h> -static void auto_release_channel(void *_lchan); - static int ts_is_usable(struct gsm_bts_trx_ts *ts) { /* FIXME: How does this behave for BS-11 ? */ @@ -225,7 +223,8 @@ _lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) } /* Allocate a logical channel */ -struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type) +struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, + int allow_bigger) { struct gsm_lchan *lchan = NULL; enum gsm_phys_chan_config first, second; @@ -243,6 +242,19 @@ struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type) lchan = _lc_find_bts(bts, first); if (lchan == NULL) lchan = _lc_find_bts(bts, second); + + /* allow to assign bigger channels */ + if (allow_bigger) { + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + type = GSM_LCHAN_TCH_H; + } + + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + type = GSM_LCHAN_TCH_F; + } + } break; case GSM_LCHAN_TCH_F: lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); @@ -268,16 +280,13 @@ struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type) /* clear multi rate config */ memset(&lchan->mr_conf, 0, sizeof(lchan->mr_conf)); + /* clear any msc reference */ + lchan->msc_data = NULL; + /* clear per MSC/BSC data */ memset(&lchan->conn, 0, sizeof(lchan->conn)); - - /* Configure the time and start it so it will be closed */ lchan->conn.lchan = lchan; lchan->conn.bts = lchan->ts->trx->bts; - lchan->conn.release_timer.cb = auto_release_channel; - lchan->conn.release_timer.data = lchan; - bsc_schedule_timer(&lchan->conn.release_timer, LCHAN_RELEASE_TIMEOUT); - } else { struct challoc_signal_data sig; sig.bts = bts; @@ -307,8 +316,6 @@ void lchan_free(struct gsm_lchan *lchan) lchan->conn.use_count = 0; } - /* stop the timer */ - bsc_del_timer(&lchan->conn.release_timer); bsc_del_timer(&lchan->T3101); /* clear cached measuement reports */ @@ -319,7 +326,6 @@ void lchan_free(struct gsm_lchan *lchan) } for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) lchan->neigh_meas[i].arfcn = 0; - lchan->conn.silent_call = 0; sig.lchan = lchan; @@ -338,22 +344,43 @@ void lchan_free(struct gsm_lchan *lchan) void lchan_reset(struct gsm_lchan *lchan) { bsc_del_timer(&lchan->T3101); + bsc_del_timer(&lchan->T3111); + bsc_del_timer(&lchan->error_timer); lchan->type = GSM_LCHAN_NONE; lchan->state = LCHAN_S_NONE; } - -/* Consider releasing the channel now */ -int lchan_auto_release(struct gsm_lchan *lchan) +static int _lchan_release_next_sapi(struct gsm_lchan *lchan) { - if (lchan->conn.use_count > 0) { + int sapi; + + for (sapi = 1; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + u_int8_t link_id; + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + + link_id = sapi; + if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + rsl_release_request(lchan, link_id, lchan->release_reason); return 0; } + return 1; +} + +static void _lchan_handle_release(struct gsm_lchan *lchan) +{ + /* Ask for SAPI != 0 to be freed first and stop if we need to wait */ + if (_lchan_release_next_sapi(lchan) == 0) + return; + /* Assume we have GSM04.08 running and send a release */ if (lchan->conn.subscr) { + ++lchan->conn.use_count; gsm48_send_rr_release(lchan); + --lchan->conn.use_count; } /* spoofed? message */ @@ -361,19 +388,44 @@ int lchan_auto_release(struct gsm_lchan *lchan) LOGP(DRLL, LOGL_ERROR, "Channel count is negative: %d\n", lchan->conn.use_count); - DEBUGP(DRLL, "%s Recycling Channel\n", gsm_lchan_name(lchan)); + rsl_release_request(lchan, 0, lchan->release_reason); rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); - rsl_release_request(lchan, 0); - return 1; } -/* Auto release the channel when the use count is zero */ -static void auto_release_channel(void *_lchan) +/* called from abis rsl */ +int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id) { - struct gsm_lchan *lchan = _lchan; + if (lchan->state != LCHAN_S_REL_REQ) + return -1; - if (!lchan_auto_release(lchan)) - bsc_schedule_timer(&lchan->conn.release_timer, LCHAN_RELEASE_TIMEOUT); + if ((link_id & 0x7) != 0) + _lchan_handle_release(lchan); + return 0; +} + + +/* + * Start the channel release procedure now. We will start by shutting + * down SAPI!=0, then we will deactivate the SACCH and finish by releasing + * the last SAPI at which point the RSL code will send the channel release + * for us. We should guard the whole shutdown by T3109 or similiar and then + * update the fixme inside gsm_04_08_utils.c + * When we request to release the RLL and we don't get an answer within T200 + * the BTS will send us an Error indication which we will handle by closing + * the channel and be done. + */ +int _lchan_release(struct gsm_lchan *lchan, u_int8_t release_reason) +{ + if (lchan->conn.use_count > 0) { + DEBUGP(DRLL, "BUG: _lchan_release called without zero use_count.\n"); + return 0; + } + + DEBUGP(DRLL, "%s Recycling Channel\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); + lchan->release_reason = release_reason; + _lchan_handle_release(lchan); + return 1; } struct gsm_lchan* lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr) { diff --git a/openbsc/src/debug.c b/openbsc/src/debug.c index a55d79013..aaf8f88fc 100644 --- a/openbsc/src/debug.c +++ b/openbsc/src/debug.c @@ -146,6 +146,11 @@ static const struct log_info_cat default_categories[] = { .description = "Reference Counting", .enabled = 0, .loglevel = LOGL_NOTICE, }, + [DNAT] = { + .name = "DNAT", + .description = "BSC MUX/NAT", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; enum log_ctxt { diff --git a/openbsc/src/e1_input.c b/openbsc/src/e1_input.c index fba59a784..b1dfe9b1d 100644 --- a/openbsc/src/e1_input.c +++ b/openbsc/src/e1_input.c @@ -420,7 +420,17 @@ e1inp_sign_link_create(struct e1inp_ts *ts, enum e1inp_sign_type type, void e1inp_sign_link_destroy(struct e1inp_sign_link *link) { + struct msgb *msg; + llist_del(&link->list); + while (!llist_empty(&link->tx_list)) { + msg = msgb_dequeue(&link->tx_list); + msgb_free(msg); + } + + if (link->ts->type == E1INP_TS_TYPE_SIGN) + bsc_del_timer(&link->ts->sign.tx_timer); + talloc_free(link); } diff --git a/openbsc/src/gsm_04_08.c b/openbsc/src/gsm_04_08.c index b0e55414f..f8602a330 100644 --- a/openbsc/src/gsm_04_08.c +++ b/openbsc/src/gsm_04_08.c @@ -104,12 +104,12 @@ static void release_loc_updating_req(struct gsm_subscriber_connection *conn) bsc_del_timer(&conn->loc_operation->updating_timer); talloc_free(conn->loc_operation); conn->loc_operation = 0; - put_subscr_con(conn); + put_subscr_con(conn, 0); } static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn) { - use_subscr_con(conn) + use_subscr_con(conn); release_loc_updating_req(conn); conn->loc_operation = talloc_zero(tall_locop_ctx, @@ -122,7 +122,6 @@ static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb int rc; db_subscriber_alloc_tmsi(conn->subscr); - release_loc_updating_req(conn); rc = gsm0408_loc_upd_acc(msg->lchan, conn->subscr->tmsi); if (msg->lchan->ts->trx->bts->network->send_mm_info) { /* send MM INFO with network name */ @@ -134,9 +133,7 @@ static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb * trigger further action like SMS delivery */ subscr_update(conn->subscr, msg->trx->bts, GSM_SUBSCRIBER_UPDATE_ATTACHED); - - /* try to close channel ASAP */ - lchan_auto_release(conn->lchan); + release_loc_updating_req(conn); return rc; } @@ -298,9 +295,8 @@ static void loc_upd_rej_cb(void *data) struct gsm_lchan *lchan = conn->lchan; struct gsm_bts *bts = lchan->ts->trx->bts; - release_loc_updating_req(conn); gsm0408_loc_upd_rej(lchan, bts->network->reject_cause); - lchan_auto_release(lchan); + release_loc_updating_req(conn); } static void schedule_reject(struct gsm_subscriber_connection *conn) @@ -722,8 +718,6 @@ static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg) * imagine an IMSI DETACH happening during an active call! */ /* subscriber is detached: should we release lchan? */ - lchan_auto_release(msg->lchan); - return 0; } @@ -2070,7 +2064,6 @@ static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg) MNCC_REL_CNF, &rel); /* FIXME: in case of multiple calls, we can't simply * hang up here ! */ - lchan_auto_release(msg->lchan); break; default: rc = mncc_recvmsg(trans->subscr->net, trans, diff --git a/openbsc/src/gsm_04_08_utils.c b/openbsc/src/gsm_04_08_utils.c index b770b52fc..dbbc7fbb2 100644 --- a/openbsc/src/gsm_04_08_utils.c +++ b/openbsc/src/gsm_04_08_utils.c @@ -164,13 +164,46 @@ static const enum gsm_chreq_reason_t reason_by_chreq[] = { [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER, }; -enum gsm_chan_t get_ctype_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci) +/* verify that the two tables match */ +static_assert(sizeof(ctype_by_chreq) == + sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size); + +/* + * Update channel types for request based on policy. E.g. in the + * case of a TCH/H network/bsc use TCH/H for the emergency calls, + * for early assignment assign a SDCCH and some other options. + */ +void gsm_net_update_ctype(struct gsm_network *network) +{ + /* copy over the data */ + memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq)); + + /* + * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it + * is better to iterate over the BTS/TRX and check if no TCH/F is available + * and then set it to TCH/H. + */ + if (network->neci) + network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H; + + if (network->pag_any_tch) { + if (network->neci) { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H; + } else { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F; + } + } +} + +enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, u_int8_t ra) { int i; int length; const struct chreq *chreq; - if (neci) { + if (network->neci) { chreq = chreq_type_neci1; length = ARRAY_SIZE(chreq_type_neci1); } else { @@ -182,13 +215,13 @@ enum gsm_chan_t get_ctype_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci) for (i = 0; i < length; i++) { const struct chreq *chr = &chreq[i]; if ((ra & chr->mask) == chr->val) - return ctype_by_chreq[chr->type]; + return network->ctype_by_chreq[chr->type]; } LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra); return GSM_LCHAN_SDCCH; } -enum gsm_chreq_reason_t get_reason_by_chreq(struct gsm_bts *bts, u_int8_t ra, int neci) +enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_t ra, int neci) { int i; int length; diff --git a/openbsc/src/gsm_04_11.c b/openbsc/src/gsm_04_11.c index 511ad47e7..0c12f15f5 100644 --- a/openbsc/src/gsm_04_11.c +++ b/openbsc/src/gsm_04_11.c @@ -757,7 +757,7 @@ static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans, /* release channel if done */ #warning "BROKEN. The SAPI will be released automatically by the BSC" if (!sms) - rsl_release_request(msg->lchan, trans->sms.link_id); + rsl_release_request(msg->lchan, trans->sms.link_id, 0); return 0; } @@ -833,7 +833,7 @@ static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans, if (sms) gsm411_send_sms_lchan(trans->conn, sms); else - rsl_release_request(msg->lchan, trans->sms.link_id); + rsl_release_request(msg->lchan, trans->sms.link_id, 0); #warning "BROKEN: The SAPI=3 will be released automatically by the BSC" return rc; diff --git a/openbsc/src/gsm_data.c b/openbsc/src/gsm_data.c index 4d8fa1747..392f4ea96 100644 --- a/openbsc/src/gsm_data.c +++ b/openbsc/src/gsm_data.c @@ -88,6 +88,7 @@ static const struct value_string lchan_s_names[] = { { LCHAN_S_ACTIVE, "ACTIVE" }, { LCHAN_S_INACTIVE, "INACTIVE" }, { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, { 0, NULL } }; @@ -287,6 +288,15 @@ struct gsm_network *gsm_network_init(u_int16_t country_code, u_int16_t network_c net->mncc_recv = mncc_recv; + gsm_net_update_ctype(net); + + net->core_country_code = -1; + net->core_network_code = -1; + net->rtp_base_port = 4000; + + net->msc_ip = talloc_strdup(net, "127.0.0.1"); + net->msc_port = 5000; + return net; } diff --git a/openbsc/src/gsm_subscriber_base.c b/openbsc/src/gsm_subscriber_base.c index 40c3bbda3..13cdfbe08 100644 --- a/openbsc/src/gsm_subscriber_base.c +++ b/openbsc/src/gsm_subscriber_base.c @@ -31,6 +31,7 @@ #include <openbsc/gsm_subscriber.h> #include <openbsc/paging.h> #include <openbsc/debug.h> +#include <openbsc/chan_alloc.h> LLIST_HEAD(active_subscribers); void *tall_subscr_ctx; @@ -88,6 +89,7 @@ static int subscr_paging_cb(unsigned int hooknum, unsigned int event, request->cbfn(hooknum, event, msg, data, request->param); subscr->in_callback = 0; + subscr_put(request->subscr); talloc_free(request); return 0; } @@ -165,7 +167,7 @@ void subscr_get_channel(struct gsm_subscriber *subscr, } memset(request, 0, sizeof(*request)); - request->subscr = subscr; + request->subscr = subscr_get(subscr); request->channel_type = type; request->cbfn = cbfn; request->param = param; @@ -206,9 +208,28 @@ void subscr_put_channel(struct gsm_lchan *lchan) * will listen to the paging requests before we timeout */ - put_subscr_con(conn); + put_subscr_con(conn, 0); if (lchan->conn.subscr && !llist_empty(&lchan->conn.subscr->requests)) subscr_send_paging_request(lchan->conn.subscr); } +struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net, + const char *imsi) +{ + struct gsm_subscriber *subscr; + + llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) { + if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net) + return subscr_get(subscr); + } + + subscr = subscr_alloc(); + if (!subscr) + return NULL; + + strcpy(subscr->imsi, imsi); + subscr->net = net; + return subscr; +} + diff --git a/openbsc/src/handover_logic.c b/openbsc/src/handover_logic.c index b2ffe4616..b9417f187 100644 --- a/openbsc/src/handover_logic.c +++ b/openbsc/src/handover_logic.c @@ -99,7 +99,7 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) counter_inc(bts->network->stats.handover.attempted); - new_lchan = lchan_alloc(bts, old_lchan->type); + new_lchan = lchan_alloc(bts, old_lchan->type, 0); if (!new_lchan) { LOGP(DHO, LOGL_NOTICE, "No free channel\n"); counter_inc(bts->network->stats.handover.no_channel); @@ -231,7 +231,6 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) trans_lchan_change(&ho->old_lchan->conn, &new_lchan->conn); rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE); - lchan_auto_release(ho->old_lchan); /* do something to re-route the actual speech frames ! */ @@ -259,7 +258,7 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) bsc_del_timer(&ho->T3103); llist_del(&ho->list); conn = &ho->new_lchan->conn; - put_subscr_con(conn); + put_subscr_con(conn, 0); talloc_free(ho); return 0; diff --git a/openbsc/src/mgcp/mgcp_network.c b/openbsc/src/mgcp/mgcp_network.c index 5a982608e..a4ab70c97 100644 --- a/openbsc/src/mgcp/mgcp_network.c +++ b/openbsc/src/mgcp/mgcp_network.c @@ -129,8 +129,10 @@ static int rtp_data_cb(struct bsc_fd *fd, unsigned int what) } /* do not forward aynthing... maybe there is a packet from the bts */ - if (endp->ci == CI_UNUSED) + if (endp->ci == CI_UNUSED) { + LOGP(DMGCP, LOGL_DEBUG, "Unknown message on endpoint: 0x%x\n", ENDPOINT_NUMBER(endp)); return -1; + } /* * Figure out where to forward it to. This code assumes that we diff --git a/openbsc/src/nat/bsc_filter.c b/openbsc/src/nat/bsc_filter.c new file mode 100644 index 000000000..891d4555d --- /dev/null +++ b/openbsc/src/nat/bsc_filter.c @@ -0,0 +1,216 @@ +/* BSC Multiplexer/NAT */ + +/* + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bsc_nat.h> +#include <openbsc/bssap.h> +#include <openbsc/ipaccess.h> +#include <openbsc/debug.h> + +#include <osmocore/talloc.h> + +#include <sccp/sccp.h> + +/* + * The idea is to have a simple struct describing a IPA packet with + * SCCP SSN and the GSM 08.08 payload and decide. We will both have + * a white and a blacklist of packets we want to handle. + * + * TODO: Implement a "NOT" in the filter language. + */ + +#define ALLOW_ANY -1 + +#define FILTER_TO_BSC 1 +#define FILTER_TO_MSC 2 +#define FILTER_TO_BOTH 3 + + +struct bsc_pkt_filter { + int ipa_proto; + int dest_ssn; + int bssap; + int gsm; + int filter_dir; +}; + +static struct bsc_pkt_filter black_list[] = { + /* filter reset messages to the MSC */ + { IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET, FILTER_TO_MSC }, + + /* filter reset ack messages to the BSC */ + { IPAC_PROTO_SCCP, SCCP_SSN_BSSAP, 0, BSS_MAP_MSG_RESET_ACKNOWLEDGE, FILTER_TO_BSC }, + + /* filter ip access */ + { IPAC_PROTO_IPACCESS, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_MSC }, +}; + +static struct bsc_pkt_filter white_list[] = { + /* allow IPAC_PROTO_SCCP messages to both sides */ + { IPAC_PROTO_SCCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH }, + + /* allow MGCP messages to both sides */ + { NAT_IPAC_PROTO_MGCP, ALLOW_ANY, ALLOW_ANY, ALLOW_ANY, FILTER_TO_BOTH }, +}; + +struct bsc_nat_parsed* bsc_nat_parse(struct msgb *msg) +{ + struct sccp_parse_result result; + struct bsc_nat_parsed *parsed; + struct ipaccess_head *hh; + + /* quick fail */ + if (msg->len < 4) + return NULL; + + parsed = talloc_zero(msg, struct bsc_nat_parsed); + if (!parsed) + return NULL; + + /* more init */ + parsed->ipa_proto = parsed->called_ssn = parsed->calling_ssn = -1; + parsed->sccp_type = parsed->bssap = parsed->gsm_type = -1; + + /* start parsing */ + hh = (struct ipaccess_head *) msg->data; + parsed->ipa_proto = hh->proto; + + msg->l2h = &hh->data[0]; + + /* do a size check on the input */ + if (ntohs(hh->len) != msgb_l2len(msg)) { + LOGP(DINP, LOGL_ERROR, "Wrong input length?\n"); + talloc_free(parsed); + return NULL; + } + + /* analyze sccp down here */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + memset(&result, 0, sizeof(result)); + if (sccp_parse_header(msg, &result) != 0) { + talloc_free(parsed); + return 0; + } + + if (msg->l3h && msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Not enough space or GSM payload\n"); + talloc_free(parsed); + return 0; + } + + parsed->sccp_type = sccp_determine_msg_type(msg); + parsed->src_local_ref = result.source_local_reference; + parsed->dest_local_ref = result.destination_local_reference; + parsed->called_ssn = result.called.ssn; + parsed->calling_ssn = result.calling.ssn; + + /* in case of connection confirm we have no payload */ + if (msg->l3h) { + parsed->bssap = msg->l3h[0]; + parsed->gsm_type = msg->l3h[2]; + } + } + + return parsed; +} + +int bsc_nat_filter_ipa(int dir, struct msgb *msg, struct bsc_nat_parsed *parsed) +{ + int i; + + /* go through the blacklist now */ + for (i = 0; i < ARRAY_SIZE(black_list); ++i) { + /* ignore the rule? */ + if (black_list[i].filter_dir != FILTER_TO_BOTH + && black_list[i].filter_dir != dir) + continue; + + /* the proto is not blacklisted */ + if (black_list[i].ipa_proto != ALLOW_ANY + && black_list[i].ipa_proto != parsed->ipa_proto) + continue; + + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + /* the SSN is not blacklisted */ + if (black_list[i].dest_ssn != ALLOW_ANY + && black_list[i].dest_ssn != parsed->called_ssn) + continue; + + /* bssap */ + if (black_list[i].bssap != ALLOW_ANY + && black_list[i].bssap != parsed->bssap) + continue; + + /* gsm */ + if (black_list[i].gsm != ALLOW_ANY + && black_list[i].gsm != parsed->gsm_type) + continue; + + /* blacklisted */ + LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i); + return 1; + } else { + /* blacklisted, we have no content sniffing yet */ + LOGP(DNAT, LOGL_INFO, "Blacklisted with rule %d\n", i); + return 1; + } + } + + /* go through the whitelust now */ + for (i = 0; i < ARRAY_SIZE(white_list); ++i) { + /* ignore the rule? */ + if (white_list[i].filter_dir != FILTER_TO_BOTH + && white_list[i].filter_dir != dir) + continue; + + /* the proto is not whitelisted */ + if (white_list[i].ipa_proto != ALLOW_ANY + && white_list[i].ipa_proto != parsed->ipa_proto) + continue; + + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + /* the SSN is not whitelisted */ + if (white_list[i].dest_ssn != ALLOW_ANY + && white_list[i].dest_ssn != parsed->called_ssn) + continue; + + /* bssap */ + if (white_list[i].bssap != ALLOW_ANY + && white_list[i].bssap != parsed->bssap) + continue; + + /* gsm */ + if (white_list[i].gsm != ALLOW_ANY + && white_list[i].gsm != parsed->gsm_type) + continue; + + /* whitelisted */ + LOGP(DNAT, LOGL_INFO, "Whitelisted with rule %d\n", i); + return 0; + } else { + /* whitelisted */ + return 0; + } + } + + return 1; +} diff --git a/openbsc/src/nat/bsc_mgcp_utils.c b/openbsc/src/nat/bsc_mgcp_utils.c new file mode 100644 index 000000000..0e34e6885 --- /dev/null +++ b/openbsc/src/nat/bsc_mgcp_utils.c @@ -0,0 +1,491 @@ +/* + * (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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bsc_nat.h> +#include <openbsc/gsm_data.h> +#include <openbsc/bssap.h> +#include <openbsc/debug.h> +#include <openbsc/mgcp.h> +#include <openbsc/mgcp_internal.h> + +#include <osmocore/talloc.h> +#include <osmocore/gsm0808.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <unistd.h> + +int bsc_mgcp_assign(struct sccp_connections *con, struct msgb *msg) +{ + struct tlv_parsed tp; + u_int16_t cic; + u_int8_t timeslot; + u_int8_t multiplex; + + if (!msg->l3h) { + LOGP(DNAT, LOGL_ERROR, "Assignment message should have l3h pointer.\n"); + return -1; + } + + if (msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Assignment message has not enough space for GSM0808.\n"); + return -1; + } + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + LOGP(DNAT, LOGL_ERROR, "Circuit identity code not found in assignment message.\n"); + return -1; + } + + cic = ntohs(*(u_int16_t *)TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); + timeslot = cic & 0x1f; + multiplex = (cic & ~0x1f) >> 5; + + con->msc_timeslot = (32 * multiplex) + timeslot; + con->bsc_timeslot = con->msc_timeslot; + return 0; +} + +void bsc_mgcp_clear(struct sccp_connections *con) +{ + con->msc_timeslot = -1; + con->bsc_timeslot = -1; +} + +void bsc_mgcp_free_endpoint(struct bsc_nat *nat, int i) +{ + if (nat->bsc_endpoints[i].transaction_id) { + talloc_free(nat->bsc_endpoints[i].transaction_id); + nat->bsc_endpoints[i].transaction_id = NULL; + } + + nat->bsc_endpoints[i].bsc = NULL; + mgcp_free_endp(&nat->mgcp_cfg->endpoints[i]); +} + +void bsc_mgcp_free_endpoints(struct bsc_nat *nat) +{ + int i; + + for (i = 1; i < nat->mgcp_cfg->number_endpoints; ++i) + bsc_mgcp_free_endpoint(nat, i); +} + +struct sccp_connections *bsc_mgcp_find_con(struct bsc_nat *nat, int endpoint) +{ + struct sccp_connections *con = NULL; + struct sccp_connections *sccp; + + llist_for_each_entry(sccp, &nat->sccp_connections, list_entry) { + if (sccp->msc_timeslot == -1) + continue; + if (mgcp_timeslot_to_endpoint(0, sccp->msc_timeslot) != endpoint) + continue; + + con = sccp; + } + + if (con) + return con; + + LOGP(DMGCP, LOGL_ERROR, "Failed to find the connection.\n"); + return NULL; +} + +int bsc_mgcp_policy_cb(struct mgcp_config *cfg, int endpoint, int state, const char *transaction_id) +{ + struct bsc_nat *nat; + struct bsc_endpoint *bsc_endp; + struct sccp_connections *sccp; + struct mgcp_endpoint *mgcp_endp; + struct msgb *bsc_msg; + + nat = cfg->data; + bsc_endp = &nat->bsc_endpoints[endpoint]; + mgcp_endp = &nat->mgcp_cfg->endpoints[endpoint]; + + sccp = bsc_mgcp_find_con(nat, endpoint); + + if (!sccp) { + LOGP(DMGCP, LOGL_ERROR, "Did not find BSC for a new connection on 0x%x for %d\n", endpoint, state); + + switch (state) { + case MGCP_ENDP_CRCX: + return MGCP_POLICY_REJECT; + break; + case MGCP_ENDP_DLCX: + return MGCP_POLICY_CONT; + break; + case MGCP_ENDP_MDCX: + return MGCP_POLICY_CONT; + break; + default: + LOGP(DMGCP, LOGL_FATAL, "Unhandled state: %d\n", state); + return MGCP_POLICY_CONT; + break; + } + } + + if (bsc_endp->transaction_id) { + LOGP(DMGCP, LOGL_ERROR, "One transaction with id '%s' on 0x%x\n", + bsc_endp->transaction_id, endpoint); + talloc_free(bsc_endp->transaction_id); + } + + /* we need to generate a new and patched message */ + bsc_msg = bsc_mgcp_rewrite((char *) nat->mgcp_msg, nat->mgcp_length, + nat->mgcp_cfg->source_addr, mgcp_endp->rtp_port); + if (!bsc_msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to patch the msg.\n"); + return MGCP_POLICY_CONT; + } + + + bsc_endp->transaction_id = talloc_strdup(nat, transaction_id); + bsc_endp->bsc = sccp->bsc; + bsc_endp->pending_delete = 0; + + /* we need to update some bits */ + if (state == MGCP_ENDP_CRCX) { + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + if (getpeername(sccp->bsc->write_queue.bfd.fd, (struct sockaddr *) &sock, &len) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Can not get the peername...%d/%s\n", + errno, strerror(errno)); + } else { + mgcp_endp->bts = sock.sin_addr; + } + } else if (state == MGCP_ENDP_DLCX) { + /* we will free the endpoint now in case the BSS does not respond */ + bsc_mgcp_clear(sccp); + bsc_endp->pending_delete = 1; + mgcp_free_endp(mgcp_endp); + } + + bsc_write(sccp->bsc, bsc_msg, NAT_IPAC_PROTO_MGCP); + return MGCP_POLICY_DEFER; +} + +/* + * We have received a msg from the BSC. We will see if we know + * this transaction and if it belongs to the BSC. Then we will + * need to patch the content to point to the local network and we + * need to update the I: that was assigned by the BSS. + */ +void bsc_mgcp_forward(struct bsc_connection *bsc, struct msgb *msg) +{ + struct msgb *output; + struct bsc_endpoint *bsc_endp = NULL; + struct mgcp_endpoint *endp = NULL; + int i, code; + char transaction_id[60]; + + /* Some assumption that our buffer is big enough.. and null terminate */ + if (msgb_l2len(msg) > 2000) { + LOGP(DMGCP, LOGL_ERROR, "MGCP message too long.\n"); + return; + } + + msg->l2h[msgb_l2len(msg)] = '\0'; + + if (bsc_mgcp_parse_response((const char *) msg->l2h, &code, transaction_id) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to parse response code.\n"); + return; + } + + for (i = 1; i < bsc->nat->mgcp_cfg->number_endpoints; ++i) { + if (bsc->nat->bsc_endpoints[i].bsc != bsc) + continue; + /* no one listening? a bug? */ + if (!bsc->nat->bsc_endpoints[i].transaction_id) + continue; + if (strcmp(transaction_id, bsc->nat->bsc_endpoints[i].transaction_id) != 0) + continue; + + endp = &bsc->nat->mgcp_cfg->endpoints[i]; + bsc_endp = &bsc->nat->bsc_endpoints[i]; + break; + } + + if (!bsc_endp) { + LOGP(DMGCP, LOGL_ERROR, "Could not find active endpoint: %s for msg: '%s'\n", + transaction_id, (const char *) msg->l2h); + return; + } + + /* make it point to our endpoint if it was not deleted */ + if (bsc_endp->pending_delete) { + bsc_endp->bsc = NULL; + bsc_endp->pending_delete = 0; + } else { + endp->ci = bsc_mgcp_extract_ci((const char *) msg->l2h); + } + + /* free some stuff */ + talloc_free(bsc_endp->transaction_id); + bsc_endp->transaction_id = NULL; + + /* + * rewrite the information. In case the endpoint was deleted + * there should be nothing for us to rewrite so putting endp->rtp_port + * with the value of 0 should be no problem. + */ + output = bsc_mgcp_rewrite((char * ) msg->l2h, msgb_l2len(msg), + bsc->nat->mgcp_cfg->source_addr, endp->rtp_port); + + if (!output) { + LOGP(DMGCP, LOGL_ERROR, "Failed to rewrite MGCP msg.\n"); + return; + } + + if (write_queue_enqueue(&bsc->nat->mgcp_queue, output) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to queue MGCP msg.\n"); + msgb_free(output); + } +} + +int bsc_mgcp_parse_response(const char *str, int *code, char transaction[60]) +{ + /* we want to parse two strings */ + return sscanf(str, "%3d %59s\n", code, transaction) != 2; +} + +int bsc_mgcp_extract_ci(const char *str) +{ + int ci; + char *res = strstr(str, "I: "); + if (!res) + return CI_UNUSED; + + if (sscanf(res, "I: %d", &ci) != 1) + return CI_UNUSED; + return ci; +} + +/* we need to replace some strings... */ +struct msgb *bsc_mgcp_rewrite(char *input, int length, const char *ip, int port) +{ + static const char *ip_str = "c=IN IP4 "; + static const char *aud_str = "m=audio "; + + char buf[128]; + char *running, *token; + struct msgb *output; + + if (length > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Input is too long.\n"); + return NULL; + } + + output = msgb_alloc_headroom(4096, 128, "MGCP rewritten"); + if (!output) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate new MGCP msg.\n"); + return NULL; + } + + running = input; + output->l2h = output->data; + for (token = strsep(&running, "\n"); running; token = strsep(&running, "\n")) { + int len = strlen(token); + int cr = len > 0 && token[len - 1] == '\r'; + + if (strncmp(ip_str, token, (sizeof ip_str) - 1) == 0) { + output->l3h = msgb_put(output, strlen(ip_str)); + memcpy(output->l3h, ip_str, strlen(ip_str)); + output->l3h = msgb_put(output, strlen(ip)); + memcpy(output->l3h, ip, strlen(ip)); + + if (cr) { + output->l3h = msgb_put(output, 2); + output->l3h[0] = '\r'; + output->l3h[1] = '\n'; + } else { + output->l3h = msgb_put(output, 1); + output->l3h[0] = '\n'; + } + } else if (strncmp(aud_str, token, (sizeof aud_str) - 1) == 0) { + int payload; + if (sscanf(token, "m=audio %*d RTP/AVP %d", &payload) != 1) { + LOGP(DMGCP, LOGL_ERROR, "Could not parsed audio line.\n"); + msgb_free(output); + return NULL; + } + + snprintf(buf, sizeof(buf)-1, "m=audio %d RTP/AVP %d%s", + port, payload, cr ? "\r\n" : "\n"); + buf[sizeof(buf)-1] = '\0'; + + output->l3h = msgb_put(output, strlen(buf)); + memcpy(output->l3h, buf, strlen(buf)); + } else { + output->l3h = msgb_put(output, len + 1); + memcpy(output->l3h, token, len); + output->l3h[len] = '\n'; + } + } + + return output; +} + +static int mgcp_do_read(struct bsc_fd *fd) +{ + struct bsc_nat *nat; + struct msgb *msg, *resp; + int rc; + + nat = fd->data; + + rc = read(fd->fd, nat->mgcp_msg, sizeof(nat->mgcp_msg) - 1); + if (rc <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read errno: %d\n", errno); + return -1; + } + + nat->mgcp_msg[rc] = '\0'; + nat->mgcp_length = rc; + + msg = msgb_alloc(sizeof(nat->mgcp_msg), "MGCP GW Read"); + if (!msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create buffer.\n"); + return -1; + } + + msg->l2h = msgb_put(msg, rc); + memcpy(msg->l2h, nat->mgcp_msg, msgb_l2len(msg)); + resp = mgcp_handle_message(nat->mgcp_cfg, msg); + msgb_free(msg); + + /* we do have a direct answer... e.g. AUEP */ + if (resp) { + if (write_queue_enqueue(&nat->mgcp_queue, resp) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to enqueue msg.\n"); + msgb_free(resp); + } + } + + return 0; +} + +static int mgcp_do_write(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + + rc = write(bfd->fd, msg->data, msg->len); + + if (rc != msg->len) { + LOGP(DMGCP, LOGL_ERROR, "Failed to write msg to MGCP CallAgent.\n"); + return -1; + } + + return rc; +} + +int bsc_mgcp_init(struct bsc_nat *nat) +{ + int on; + struct sockaddr_in addr; + + if (!nat->mgcp_cfg->call_agent_addr) { + LOGP(DMGCP, LOGL_ERROR, "The BSC nat requires the call agent ip to be set.\n"); + return -1; + } + + if (nat->mgcp_cfg->bts_ip) { + LOGP(DMGCP, LOGL_ERROR, "Do not set the BTS ip for the nat.\n"); + return -1; + } + + nat->mgcp_queue.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (nat->mgcp_queue.bfd.fd < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to create MGCP socket. errno: %d\n", errno); + return -1; + } + + on = 1; + setsockopt(nat->mgcp_queue.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(nat->mgcp_cfg->source_port); + inet_aton(nat->mgcp_cfg->source_addr, &addr.sin_addr); + + if (bind(nat->mgcp_queue.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to bind. errno: %d\n", errno); + close(nat->mgcp_queue.bfd.fd); + nat->mgcp_queue.bfd.fd = -1; + return -1; + } + + addr.sin_port = htons(2727); + inet_aton(nat->mgcp_cfg->call_agent_addr, &addr.sin_addr); + if (connect(nat->mgcp_queue.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to connect to: '%s'. errno: %d\n", + nat->mgcp_cfg->call_agent_addr, errno); + close(nat->mgcp_queue.bfd.fd); + nat->mgcp_queue.bfd.fd = -1; + return -1; + } + + write_queue_init(&nat->mgcp_queue, 10); + nat->mgcp_queue.bfd.when = BSC_FD_READ; + nat->mgcp_queue.bfd.data = nat; + nat->mgcp_queue.read_cb = mgcp_do_read; + nat->mgcp_queue.write_cb = mgcp_do_write; + + if (bsc_register_fd(&nat->mgcp_queue.bfd) != 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to register MGCP fd.\n"); + close(nat->mgcp_queue.bfd.fd); + nat->mgcp_queue.bfd.fd = -1; + return -1; + } + + /* some more MGCP config handling */ + nat->mgcp_cfg->audio_payload = -1; + nat->mgcp_cfg->data = nat; + nat->mgcp_cfg->policy_cb = bsc_mgcp_policy_cb; + nat->mgcp_cfg->force_realloc = 1; + nat->mgcp_cfg->bts_ip = ""; + nat->bsc_endpoints = talloc_zero_array(nat, + struct bsc_endpoint, + nat->mgcp_cfg->number_endpoints + 1); + + return 0; +} + +void bsc_mgcp_clear_endpoints_for(struct bsc_connection *bsc) +{ + int i; + for (i = 1; i < bsc->nat->mgcp_cfg->number_endpoints; ++i) { + struct bsc_endpoint *bsc_endp = &bsc->nat->bsc_endpoints[i]; + + if (bsc_endp->bsc != bsc) + continue; + + bsc_endp->bsc = NULL; + bsc_endp->pending_delete = 0; + if (bsc_endp->transaction_id) + talloc_free(bsc_endp->transaction_id); + bsc_endp->transaction_id = NULL; + mgcp_free_endp(&bsc->nat->mgcp_cfg->endpoints[i]); + } +} diff --git a/openbsc/src/nat/bsc_nat.c b/openbsc/src/nat/bsc_nat.c new file mode 100644 index 000000000..abf38ce4e --- /dev/null +++ b/openbsc/src/nat/bsc_nat.c @@ -0,0 +1,891 @@ +/* BSC Multiplexer/NAT */ + +/* + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <openbsc/debug.h> +#include <openbsc/bsc_msc.h> +#include <openbsc/bsc_nat.h> +#include <openbsc/bssap.h> +#include <openbsc/ipaccess.h> +#include <openbsc/abis_nm.h> +#include <openbsc/telnet_interface.h> + +#include <osmocore/talloc.h> + +#include <vty/vty.h> + +#include <sccp/sccp.h> + +struct log_target *stderr_target; +static const char *config_file = "bsc-nat.cfg"; +static struct in_addr local_addr; +static struct bsc_msc_connection *msc_con; +static struct bsc_fd bsc_listen; +static const char *msc_ip = NULL; + + +static struct bsc_nat *nat; +static void bsc_send_data(struct bsc_connection *bsc, const u_int8_t *data, unsigned int length, int); +static void remove_bsc_connection(struct bsc_connection *connection); +static void msc_send_reset(struct bsc_msc_connection *con); + +struct bsc_config *bsc_config_num(struct bsc_nat *nat, int num) +{ + struct bsc_config *conf; + + llist_for_each_entry(conf, &nat->bsc_configs, entry) + if (conf->nr == num) + return conf; + + return NULL; +} + +/* + * below are stubs we need to link + */ +int nm_state_event(enum nm_evt evt, u_int8_t obj_class, void *obj, + struct gsm_nm_state *old_state, struct gsm_nm_state *new_state) +{ + return -1; +} + +void input_event(int event, enum e1inp_sign_type type, struct gsm_bts_trx *trx) +{} + +int gsm0408_rcvmsg(struct msgb *msg, u_int8_t link_id) +{ + return -1; +} + +static void send_reset_ack(struct bsc_connection *bsc) +{ + static const u_int8_t gsm_reset_ack[] = { + 0x09, 0x00, 0x03, 0x07, 0x0b, 0x04, 0x43, 0x01, + 0x00, 0xfe, 0x04, 0x43, 0x5c, 0x00, 0xfe, 0x03, + 0x00, 0x01, 0x31, + }; + + bsc_send_data(bsc, gsm_reset_ack, sizeof(gsm_reset_ack), IPAC_PROTO_SCCP); +} + +static void send_id_ack(struct bsc_connection *bsc) +{ + static const u_int8_t id_ack[] = { + IPAC_MSGT_ID_ACK + }; + + bsc_send_data(bsc, id_ack, sizeof(id_ack), IPAC_PROTO_IPACCESS); +} + +static void send_id_req(struct bsc_connection *bsc) +{ + static const u_int8_t id_req[] = { + IPAC_MSGT_ID_GET, + 0x01, IPAC_IDTAG_UNIT, + 0x01, IPAC_IDTAG_MACADDR, + 0x01, IPAC_IDTAG_LOCATION1, + 0x01, IPAC_IDTAG_LOCATION2, + 0x01, IPAC_IDTAG_EQUIPVERS, + 0x01, IPAC_IDTAG_SWVERSION, + 0x01, IPAC_IDTAG_UNITNAME, + 0x01, IPAC_IDTAG_SERNR, + }; + + bsc_send_data(bsc, id_req, sizeof(id_req), IPAC_PROTO_IPACCESS); +} + +static void nat_send_rlsd(struct sccp_connections *conn) +{ + struct sccp_connection_released *rel; + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "rlsd"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(*rel)); + rel = (struct sccp_connection_released *) msg->l2h; + rel->type = SCCP_MSG_TYPE_RLSD; + rel->release_cause = SCCP_RELEASE_CAUSE_SCCP_FAILURE; + rel->destination_local_reference = conn->remote_ref; + rel->source_local_reference = conn->patched_ref; + + ipaccess_prepend_header(msg, IPAC_PROTO_SCCP); + + if (write_queue_enqueue(&msc_con->write_queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n"); + msgb_free(msg); + } +} + +static void nat_send_rlc(struct sccp_source_reference *src, + struct sccp_source_reference *dst) +{ + struct sccp_connection_release_complete *rlc; + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "rlc"); + if (!msg) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate clear command.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(*rlc)); + rlc = (struct sccp_connection_release_complete *) msg->l2h; + rlc->type = SCCP_MSG_TYPE_RLC; + rlc->destination_local_reference = *dst; + rlc->source_local_reference = *src; + + ipaccess_prepend_header(msg, IPAC_PROTO_SCCP); + + if (write_queue_enqueue(&msc_con->write_queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n"); + msgb_free(msg); + } +} + +static void send_mgcp_reset(struct bsc_connection *bsc) +{ + static const u_int8_t mgcp_reset[] = { + "RSIP 1 13@mgw MGCP 1.0\r\n" + }; + + bsc_write_mgcp(bsc, mgcp_reset, sizeof mgcp_reset - 1); +} + +/* + * Below is the handling of messages coming + * from the MSC and need to be forwarded to + * a real BSC. + */ +static void initialize_msc_if_needed() +{ + if (nat->first_contact) + return; + + nat->first_contact = 1; + msc_send_reset(msc_con); +} + +/* + * Currently we are lacking refcounting so we need to copy each message. + */ +static void bsc_send_data(struct bsc_connection *bsc, const u_int8_t *data, unsigned int length, int proto) +{ + struct msgb *msg; + + if (length > 4096 - 128) { + LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n"); + return; + } + + msg = msgb_alloc_headroom(4096, 128, "to-bsc"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, length); + memcpy(msg->data, data, length); + + bsc_write(bsc, msg, proto); +} + +static int forward_sccp_to_bts(struct msgb *msg) +{ + struct sccp_connections *con; + struct bsc_connection *bsc; + struct bsc_nat_parsed *parsed; + int proto; + + /* filter, drop, patch the message? */ + parsed = bsc_nat_parse(msg); + if (!parsed) { + LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n"); + return -1; + } + + if (bsc_nat_filter_ipa(DIR_BSC, msg, parsed)) + goto exit; + + proto = parsed->ipa_proto; + + /* Route and modify the SCCP packet */ + if (proto == IPAC_PROTO_SCCP) { + switch (parsed->sccp_type) { + case SCCP_MSG_TYPE_UDT: + /* forward UDT messages to every BSC */ + goto send_to_all; + break; + case SCCP_MSG_TYPE_RLSD: + case SCCP_MSG_TYPE_CREF: + case SCCP_MSG_TYPE_DT1: + case SCCP_MSG_TYPE_IT: + con = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + if (parsed->gsm_type == BSS_MAP_MSG_ASSIGMENT_RQST) { + counter_inc(nat->stats.sccp.calls); + + if (con) { + counter_inc(con->bsc->cfg->stats.sccp.calls); + if (bsc_mgcp_assign(con, msg) != 0) + LOGP(DNAT, LOGL_ERROR, "Failed to assign...\n"); + } else + LOGP(DNAT, LOGL_ERROR, "Assignment command but no BSC.\n"); + } + break; + case SCCP_MSG_TYPE_CC: + con = patch_sccp_src_ref_to_bsc(msg, parsed, nat); + if (!con || update_sccp_src_ref(con, parsed) != 0) + goto exit; + break; + case SCCP_MSG_TYPE_RLC: + LOGP(DNAT, LOGL_ERROR, "Unexpected release complete from MSC.\n"); + goto exit; + break; + case SCCP_MSG_TYPE_CR: + /* MSC never opens a SCCP connection, fall through */ + default: + goto exit; + } + + if (!con && parsed->sccp_type == SCCP_MSG_TYPE_RLSD) { + LOGP(DNAT, LOGL_NOTICE, "Sending fake RLC on RLSD message to network.\n"); + /* Exchange src/dest for the reply */ + nat_send_rlc(parsed->dest_local_ref, parsed->src_local_ref); + } else if (!con) + LOGP(DNAT, LOGL_ERROR, "Unknown connection for msg type: 0x%x.\n", parsed->sccp_type); + } + + talloc_free(parsed); + if (!con) + return -1; + if (!con->bsc->authenticated) { + LOGP(DNAT, LOGL_ERROR, "Selected BSC not authenticated.\n"); + return -1; + } + + bsc_send_data(con->bsc, msg->l2h, msgb_l2len(msg), proto); + return 0; + +send_to_all: + /* + * Filter Paging from the network. We do not want to send a PAGING + * Command to every BSC in our network. We will analys the PAGING + * message and then send it to the authenticated messages... + */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP && parsed->gsm_type == BSS_MAP_MSG_PAGING) { + int lac; + bsc = bsc_nat_find_bsc(nat, msg, &lac); + if (bsc) + bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto); + else + LOGP(DNAT, LOGL_ERROR, "Could not determine BSC for paging on lac: %d/0x%x\n", + lac, lac); + + goto exit; + } + /* currently send this to every BSC connected */ + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + if (!bsc->authenticated) + continue; + + bsc_send_data(bsc, msg->l2h, msgb_l2len(msg), parsed->ipa_proto); + } + +exit: + talloc_free(parsed); + return 0; +} + +static void msc_connection_was_lost(struct bsc_msc_connection *con) +{ + struct bsc_connection *bsc, *tmp; + + counter_inc(nat->stats.msc.reconn); + + LOGP(DMSC, LOGL_ERROR, "Closing all connections downstream.\n"); + llist_for_each_entry_safe(bsc, tmp, &nat->bsc_connections, list_entry) + remove_bsc_connection(bsc); + + nat->first_contact = 0; + bsc_mgcp_free_endpoints(nat); + bsc_msc_schedule_connect(con); +} + +static void msc_send_reset(struct bsc_msc_connection *msc_con) +{ + static const u_int8_t reset[] = { + 0x00, 0x12, 0xfd, + 0x09, 0x00, 0x03, 0x05, 0x07, 0x02, 0x42, 0xfe, + 0x02, 0x42, 0xfe, 0x06, 0x00, 0x04, 0x30, 0x04, + 0x01, 0x20 + }; + + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "08.08 reset"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate reset msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, sizeof(reset)); + memcpy(msg->l2h, reset, msgb_l2len(msg)); + + if (write_queue_enqueue(&msc_con->write_queue, msg) != 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to enqueue reset msg.\n"); + msgb_free(msg); + } + + LOGP(DMSC, LOGL_NOTICE, "Scheduled GSM0808 reset msg for the MSC.\n"); +} + +static int ipaccess_msc_read_cb(struct bsc_fd *bfd) +{ + int error; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + struct ipaccess_head *hh; + + if (!msg) { + if (error == 0) { + LOGP(DNAT, LOGL_FATAL, "The connection the MSC was lost, exiting\n"); + bsc_msc_lost(msc_con); + return -1; + } + + LOGP(DNAT, LOGL_ERROR, "Failed to parse ip access message: %d\n", error); + return -1; + } + + LOGP(DNAT, LOGL_DEBUG, "MSG 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 && msg->l2h[0] == IPAC_MSGT_ID_ACK) + initialize_msc_if_needed(); + else if (hh->proto == IPAC_PROTO_SCCP) + forward_sccp_to_bts(msg); + + msgb_free(msg); + return 0; +} + +static int ipaccess_msc_write_cb(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + rc = write(bfd->fd, msg->data, msg->len); + + if (rc != msg->len) { + LOGP(DNAT, LOGL_ERROR, "Failed to write MSG to MSC.\n"); + return -1; + } + + return rc; +} + +/* + * Below is the handling of messages coming + * from the BSC and need to be forwarded to + * a real BSC. + */ + +/* + * Remove the connection from the connections list, + * remove it from the patching of SCCP header lists + * as well. Maybe in the future even close connection.. + */ +static void remove_bsc_connection(struct bsc_connection *connection) +{ + struct sccp_connections *sccp_patch, *tmp; + bsc_unregister_fd(&connection->write_queue.bfd); + close(connection->write_queue.bfd.fd); + write_queue_clear(&connection->write_queue); + llist_del(&connection->list_entry); + + /* stop the timeout timer */ + bsc_del_timer(&connection->id_timeout); + + /* remove all SCCP connections */ + llist_for_each_entry_safe(sccp_patch, tmp, &nat->sccp_connections, list_entry) { + if (sccp_patch->bsc != connection) + continue; + + nat_send_rlsd(sccp_patch); + sccp_connection_destroy(sccp_patch); + } + + /* close endpoints allocated by this BSC */ + bsc_mgcp_clear_endpoints_for(connection); + + talloc_free(connection); +} + +static void ipaccess_close_bsc(void *data) +{ + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + struct bsc_connection *conn = data; + + + getpeername(conn->write_queue.bfd.fd, (struct sockaddr *) &sock, &len); + LOGP(DNAT, LOGL_ERROR, "BSC on %s didn't respond to identity request. Closing.\n", + inet_ntoa(sock.sin_addr)); + remove_bsc_connection(conn); +} + +static void ipaccess_auth_bsc(struct tlv_parsed *tvp, struct bsc_connection *bsc) +{ + struct bsc_config *conf; + const char* token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME); + + llist_for_each_entry(conf, &bsc->nat->bsc_configs, entry) { + if (strcmp(conf->token, token) == 0) { + counter_inc(conf->stats.net.reconn); + bsc->authenticated = 1; + bsc->cfg = conf; + bsc_del_timer(&bsc->id_timeout); + LOGP(DNAT, LOGL_NOTICE, "Authenticated bsc nr: %d lac: %d\n", conf->nr, conf->lac); + return; + } + } + + LOGP(DNAT, LOGL_ERROR, "No bsc found for token %s.\n", token); +} + +static int forward_sccp_to_msc(struct bsc_connection *bsc, struct msgb *msg) +{ + struct sccp_connections *con; + struct bsc_nat_parsed *parsed; + + /* Parse and filter messages */ + parsed = bsc_nat_parse(msg); + if (!parsed) { + LOGP(DNAT, LOGL_ERROR, "Can not parse msg from BSC.\n"); + msgb_free(msg); + return -1; + } + + if (bsc_nat_filter_ipa(DIR_MSC, msg, parsed)) + goto exit; + + /* + * check authentication after filtering to not reject auth + * responses coming from the BSC. We have to make sure that + * nothing from the exit path will forward things to the MSC + */ + if (!bsc->authenticated) { + LOGP(DNAT, LOGL_ERROR, "BSC is not authenticated.\n"); + msgb_free(msg); + return -1; + } + + + /* modify the SCCP entries */ + if (parsed->ipa_proto == IPAC_PROTO_SCCP) { + switch (parsed->sccp_type) { + case SCCP_MSG_TYPE_CR: + if (create_sccp_src_ref(bsc, msg, parsed) != 0) + goto exit2; + con = patch_sccp_src_ref_to_msc(msg, parsed, nat); + break; + case SCCP_MSG_TYPE_RLSD: + case SCCP_MSG_TYPE_CREF: + case SCCP_MSG_TYPE_DT1: + case SCCP_MSG_TYPE_CC: + case SCCP_MSG_TYPE_IT: + con = patch_sccp_src_ref_to_msc(msg, parsed, nat); + break; + case SCCP_MSG_TYPE_RLC: + con = patch_sccp_src_ref_to_msc(msg, parsed, nat); + remove_sccp_src_ref(bsc, msg, parsed); + break; + case SCCP_MSG_TYPE_UDT: + /* simply forward everything */ + con = NULL; + break; + default: + LOGP(DNAT, LOGL_ERROR, "Not forwarding to msc sccp type: 0x%x\n", parsed->sccp_type); + con = NULL; + goto exit2; + break; + } + } else if (parsed->ipa_proto == NAT_IPAC_PROTO_MGCP) { + bsc_mgcp_forward(bsc, msg); + goto exit2; + } else { + LOGP(DNAT, LOGL_ERROR, "Not forwarding unknown stream id: 0x%x\n", parsed->ipa_proto); + goto exit2; + } + + if (con && con->bsc != bsc) { + LOGP(DNAT, LOGL_ERROR, "Found the wrong entry.\n"); + goto exit2; + } + + /* send the non-filtered but maybe modified msg */ + if (write_queue_enqueue(&msc_con->write_queue, msg) != 0) { + LOGP(DNAT, LOGL_ERROR, "Can not queue message for the MSC.\n"); + msgb_free(msg); + } + talloc_free(parsed); + return 0; + +exit: + /* if we filter out the reset send an ack to the BSC */ + if (parsed->bssap == 0 && parsed->gsm_type == BSS_MAP_MSG_RESET) { + send_reset_ack(bsc); + send_reset_ack(bsc); + } else if (parsed->ipa_proto == IPAC_PROTO_IPACCESS) { + /* do we know who is handling this? */ + if (msg->l2h[0] == IPAC_MSGT_ID_RESP) { + struct tlv_parsed tvp; + ipaccess_idtag_parse(&tvp, + (unsigned char *) msg->l2h + 2, + msgb_l2len(msg) - 2); + if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME)) + ipaccess_auth_bsc(&tvp, bsc); + } + + goto exit2; + } + +exit2: + talloc_free(parsed); + msgb_free(msg); + return -1; +} + +static int ipaccess_bsc_read_cb(struct bsc_fd *bfd) +{ + int error; + struct bsc_connection *bsc = bfd->data; + struct msgb *msg = ipaccess_read_msg(bfd, &error); + + if (!msg) { + if (error == 0) { + LOGP(DNAT, LOGL_ERROR, "The connection to the BSC was lost. Cleaning it\n"); + remove_bsc_connection(bsc); + } else { + LOGP(DNAT, LOGL_ERROR, "Failed to parse ip access message: %d\n", error); + } + return -1; + } + + + LOGP(DNAT, LOGL_DEBUG, "MSG from BSC: %s proto: %d\n", hexdump(msg->data, msg->len), msg->l2h[0]); + + /* Handle messages from the BSC */ + /* FIXME: Currently no PONG is sent to the BSC */ + /* FIXME: Currently no ID ACK is sent to the BSC */ + forward_sccp_to_msc(bsc, msg); + + return 0; +} + +static int ipaccess_bsc_write_cb(struct bsc_fd *bfd, struct msgb *msg) +{ + int rc; + + rc = write(bfd->fd, msg->data, msg->len); + if (rc != msg->len) + LOGP(DNAT, LOGL_ERROR, "Failed to write message to the BSC.\n"); + + return rc; +} + +static int ipaccess_listen_bsc_cb(struct bsc_fd *bfd, unsigned int what) +{ + struct bsc_connection *bsc; + int ret; + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (!(what & BSC_FD_READ)) + return 0; + + ret = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len); + if (ret < 0) { + perror("accept"); + return ret; + } + + /* count the reconnect */ + counter_inc(nat->stats.bsc.reconn); + + /* + * if we are not connected to a msc... just close the socket + */ + if (!msc_con->is_connected) { + LOGP(DNAT, LOGL_NOTICE, "Disconnecting BSC due lack of MSC connection.\n"); + close(ret); + return 0; + } + + /* todo... do something with the connection */ + /* todo... use GNUtls to see if we want to trust this as a BTS */ + + /* + * + */ + bsc = bsc_connection_alloc(nat); + if (!bsc) { + LOGP(DNAT, LOGL_ERROR, "Failed to allocate BSC struct.\n"); + close(ret); + return -1; + } + + write_queue_init(&bsc->write_queue, 100); + bsc->write_queue.bfd.data = bsc; + bsc->write_queue.bfd.fd = ret; + bsc->write_queue.read_cb = ipaccess_bsc_read_cb; + bsc->write_queue.write_cb = ipaccess_bsc_write_cb; + bsc->write_queue.bfd.when = BSC_FD_READ; + if (bsc_register_fd(&bsc->write_queue.bfd) < 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to register BSC fd.\n"); + close(ret); + talloc_free(bsc); + return -2; + } + + LOGP(DNAT, LOGL_NOTICE, "Registered new BSC\n"); + llist_add(&bsc->list_entry, &nat->bsc_connections); + send_id_ack(bsc); + send_id_req(bsc); + send_mgcp_reset(bsc); + + /* + * start the hangup timer + */ + bsc->id_timeout.data = bsc; + bsc->id_timeout.cb = ipaccess_close_bsc; + bsc_schedule_timer(&bsc->id_timeout, 2, 0); + return 0; +} + +static int listen_for_bsc(struct bsc_fd *bfd, struct in_addr *in_addr, int port) +{ + struct sockaddr_in addr; + int ret, on = 1; + + bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bfd->cb = ipaccess_listen_bsc_cb; + bfd->when = BSC_FD_READ; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = in_addr->s_addr; + + setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (ret < 0) { + fprintf(stderr, "Could not bind the BSC socket %s\n", + strerror(errno)); + return -EIO; + } + + ret = listen(bfd->fd, 1); + if (ret < 0) { + perror("listen"); + return ret; + } + + ret = bsc_register_fd(bfd); + if (ret < 0) { + perror("register_listen_fd"); + return ret; + } + return 0; +} + +static void print_usage() +{ + printf("Usage: bsc_nat\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -s --disable-color\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -m --msc=IP. The address of the MSC.\n"); + printf(" -l --local=IP. The local address of this BSC.\n"); +} + +static void handle_options(int argc, char** argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"debug", 1, 0, 'd'}, + {"config-file", 1, 0, 'c'}, + {"disable-color", 0, 0, 's'}, + {"timestamp", 0, 0, 'T'}, + {"msc", 1, 0, 'm'}, + {"local", 1, 0, 'l'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:sTPc:m:l:", + 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 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(stderr_target, 1); + break; + case 'm': + msc_ip = optarg; + break; + case 'l': + inet_aton(optarg, &local_addr); + break; + default: + /* ignore */ + break; + } + } +} + +static void signal_handler(int signal) +{ + switch (signal) { + case SIGABRT: + /* in case of abort, we want to obtain a talloc report + * and then return to the caller, who will abort the process */ + case SIGUSR1: + talloc_report_full(tall_bsc_ctx, stderr); + break; + default: + break; + } +} + +int main(int argc, char** argv) +{ + log_init(&log_info); + stderr_target = log_target_create_stderr(); + log_add_target(stderr_target); + log_set_all_filter(stderr_target, 1); + + nat = bsc_nat_alloc(); + if (!nat) { + fprintf(stderr, "Failed to allocate the BSC nat.\n"); + return -4; + } + + nat->mgcp_cfg = talloc_zero(nat, struct mgcp_config); + if (!nat->mgcp_cfg) { + fprintf(stderr, "Failed to allocate MGCP cfg.\n"); + return -5; + } + + /* parse options */ + local_addr.s_addr = INADDR_ANY; + handle_options(argc, argv); + + /* init vty and parse */ + bsc_nat_vty_init(nat); + telnet_init(NULL, 4244); + if (mgcp_parse_config(config_file, nat->mgcp_cfg) < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); + return -3; + } + + /* over rule the VTY config */ + if (msc_ip) + bsc_nat_set_msc_ip(nat, msc_ip); + + /* seed the PRNG */ + srand(time(NULL)); + + /* + * Setup the MGCP code.. + */ + if (bsc_mgcp_init(nat) != 0) + return -4; + + /* connect to the MSC */ + msc_con = bsc_msc_create(nat->msc_ip, nat->msc_port); + if (!msc_con) { + fprintf(stderr, "Creating a bsc_msc_connection failed.\n"); + exit(1); + } + + msc_con->connection_loss = msc_connection_was_lost; + msc_con->write_queue.read_cb = ipaccess_msc_read_cb; + msc_con->write_queue.write_cb = ipaccess_msc_write_cb;; + bsc_msc_connect(msc_con); + + /* wait for the BSC */ + if (listen_for_bsc(&bsc_listen, &local_addr, 5000) < 0) { + fprintf(stderr, "Failed to listen for BSC.\n"); + exit(1); + } + + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGPIPE, SIG_IGN); + + while (1) { + bsc_select_main(0); + } + + return 0; +} diff --git a/openbsc/src/nat/bsc_nat_utils.c b/openbsc/src/nat/bsc_nat_utils.c new file mode 100644 index 000000000..290ee4eaa --- /dev/null +++ b/openbsc/src/nat/bsc_nat_utils.c @@ -0,0 +1,187 @@ + +/* BSC Multiplexer/NAT Utilities */ + +/* + * (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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/bsc_nat.h> +#include <openbsc/gsm_data.h> +#include <openbsc/bssap.h> +#include <openbsc/debug.h> +#include <openbsc/ipaccess.h> + +#include <osmocore/linuxlist.h> +#include <osmocore/talloc.h> +#include <osmocore/gsm0808.h> + +#include <sccp/sccp.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +struct bsc_nat *bsc_nat_alloc(void) +{ + struct bsc_nat *nat = talloc_zero(tall_bsc_ctx, struct bsc_nat); + if (!nat) + return NULL; + + INIT_LLIST_HEAD(&nat->sccp_connections); + INIT_LLIST_HEAD(&nat->bsc_connections); + INIT_LLIST_HEAD(&nat->bsc_configs); + nat->stats.sccp.conn = counter_alloc("nat.sccp.conn"); + nat->stats.sccp.calls = counter_alloc("nat.sccp.calls"); + nat->stats.bsc.reconn = counter_alloc("nat.bsc.conn"); + nat->stats.bsc.auth_fail = counter_alloc("nat.bsc.auth_fail"); + nat->stats.msc.reconn = counter_alloc("nat.msc.conn"); + nat->msc_ip = talloc_strdup(nat, "127.0.0.1"); + nat->msc_port = 5000; + return nat; +} + +void bsc_nat_set_msc_ip(struct bsc_nat *nat, const char *ip) +{ + if (nat->msc_ip) + talloc_free(nat->msc_ip); + nat->msc_ip = talloc_strdup(nat, ip); +} + +struct bsc_connection *bsc_connection_alloc(struct bsc_nat *nat) +{ + struct bsc_connection *con = talloc_zero(nat, struct bsc_connection); + if (!con) + return NULL; + + con->nat = nat; + return con; +} + +struct bsc_config *bsc_config_alloc(struct bsc_nat *nat, const char *token, unsigned int lac) +{ + struct bsc_config *conf = talloc_zero(nat, struct bsc_config); + if (!conf) + return NULL; + + conf->token = talloc_strdup(conf, token); + conf->lac = lac; + conf->nr = nat->num_bsc; + conf->nat = nat; + + llist_add_tail(&conf->entry, &nat->bsc_configs); + ++nat->num_bsc; + + conf->stats.sccp.conn = counter_alloc("nat.bsc.sccp.conn"); + conf->stats.sccp.calls = counter_alloc("nat.bsc.sccp.calls"); + conf->stats.net.reconn = counter_alloc("nat.bsc.net.reconnects"); + + return conf; +} + +void sccp_connection_destroy(struct sccp_connections *conn) +{ + LOGP(DNAT, LOGL_DEBUG, "Destroy 0x%x <-> 0x%x mapping for con %p\n", + sccp_src_ref_to_int(&conn->real_ref), + sccp_src_ref_to_int(&conn->patched_ref), conn->bsc); + bsc_mgcp_clear(conn); + llist_del(&conn->list_entry); + talloc_free(conn); +} + +struct bsc_connection *bsc_nat_find_bsc(struct bsc_nat *nat, struct msgb *msg, int *lac_out) +{ + struct bsc_connection *bsc; + int data_length; + const u_int8_t *data; + struct tlv_parsed tp; + int i = 0; + + *lac_out = -1; + + if (!msg->l3h || msgb_l3len(msg) < 3) { + LOGP(DNAT, LOGL_ERROR, "Paging message is too short.\n"); + return NULL; + } + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 3, msgb_l3len(msg) - 3, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) { + LOGP(DNAT, LOGL_ERROR, "No CellIdentifier List inside paging msg.\n"); + return NULL; + } + + data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + if (data[0] != CELL_IDENT_LAC) { + LOGP(DNAT, LOGL_ERROR, "Unhandled cell ident discrminator: %d\n", data[0]); + return NULL; + } + + /* Currently we only handle one BSC */ + for (i = 1; i < data_length - 1; i += 2) { + unsigned int _lac = ntohs(*(unsigned int *) &data[i]); + *lac_out = _lac; + llist_for_each_entry(bsc, &nat->bsc_connections, list_entry) { + if (!bsc->cfg) + continue; + if (!bsc->authenticated || _lac != bsc->cfg->lac) + continue; + + return bsc; + } + } + + return NULL; +} + +int bsc_write_mgcp(struct bsc_connection *bsc, const u_int8_t *data, unsigned int length) +{ + struct msgb *msg; + + if (length > 4096 - 128) { + LOGP(DINP, LOGL_ERROR, "Can not send message of that size.\n"); + return -1; + } + + msg = msgb_alloc_headroom(4096, 128, "to-bsc"); + if (!msg) { + LOGP(DINP, LOGL_ERROR, "Failed to allocate memory for BSC msg.\n"); + return -1; + } + + /* copy the data */ + msg->l3h = msgb_put(msg, length); + memcpy(msg->l3h, data, length); + + return bsc_write(bsc, msg, NAT_IPAC_PROTO_MGCP); +} + +int bsc_write(struct bsc_connection *bsc, struct msgb *msg, int proto) +{ + /* prepend the header */ + ipaccess_prepend_header(msg, proto); + + if (write_queue_enqueue(&bsc->write_queue, msg) != 0) { + LOGP(DINP, LOGL_ERROR, "Failed to enqueue the write.\n"); + msgb_free(msg); + return -1; + } + + return 0; +} + diff --git a/openbsc/src/nat/bsc_nat_vty.c b/openbsc/src/nat/bsc_nat_vty.c new file mode 100644 index 000000000..a03b5d178 --- /dev/null +++ b/openbsc/src/nat/bsc_nat_vty.c @@ -0,0 +1,359 @@ +/* OpenBSC NAT interface to quagga VTY */ +/* (C) 2010 by Holger Hans Peter Freyther + * (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 General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <vty/command.h> +#include <vty/buffer.h> +#include <vty/vty.h> + +#include <openbsc/bsc_nat.h> +#include <openbsc/gsm_04_08.h> +#include <openbsc/mgcp.h> +#include <openbsc/vty.h> + +#include <osmocore/talloc.h> + +#include <sccp/sccp.h> + +#include <stdlib.h> + +static struct bsc_nat *_nat; + +static struct cmd_node nat_node = { + NAT_NODE, + "%s(nat)#", + 1, +}; + +static struct cmd_node bsc_node = { + BSC_NODE, + "%s(bsc)#", + 1, +}; + +static int config_write_nat(struct vty *vty) +{ + vty_out(vty, "nat%s", VTY_NEWLINE); + if (_nat->imsi_allow) + vty_out(vty, " imsi allow %s%s", _nat->imsi_allow, VTY_NEWLINE); + if (_nat->imsi_deny) + vty_out(vty, " insi deny %s%s", _nat->imsi_deny, VTY_NEWLINE); + vty_out(vty, " msc ip %s%s", _nat->msc_ip, VTY_NEWLINE); + vty_out(vty, " msc port %d%s", _nat->msc_port, VTY_NEWLINE); + return CMD_SUCCESS; +} + +static void config_write_bsc_single(struct vty *vty, struct bsc_config *bsc) +{ + vty_out(vty, " bsc %u%s", bsc->nr, VTY_NEWLINE); + vty_out(vty, " token %s%s", bsc->token, VTY_NEWLINE); + vty_out(vty, " location_area_code %u%s", bsc->lac, VTY_NEWLINE); + if (bsc->imsi_allow) + vty_out(vty, " imsi allow %s%s", bsc->imsi_allow, VTY_NEWLINE); + if (bsc->imsi_deny) + vty_out(vty, " imsi deny %s%s", bsc->imsi_deny, VTY_NEWLINE); +} + +static int config_write_bsc(struct vty *vty) +{ + struct bsc_config *bsc; + + llist_for_each_entry(bsc, &_nat->bsc_configs, entry) + config_write_bsc_single(vty, bsc); + return CMD_SUCCESS; +} + + +DEFUN(show_sccp, show_sccp_cmd, "show sccp connections", + SHOW_STR "Display information about current SCCP connections") +{ + struct sccp_connections *con; + llist_for_each_entry(con, &_nat->sccp_connections, list_entry) { + vty_out(vty, "SCCP for BSC: Nr: %d lac: %d BSC ref: 0x%x Local ref: 0x%x MSC/BSC mux: 0x%x/0x%x%s", + con->bsc->cfg ? con->bsc->cfg->nr : -1, + con->bsc->cfg ? con->bsc->cfg->lac : -1, + sccp_src_ref_to_int(&con->real_ref), + sccp_src_ref_to_int(&con->patched_ref), + con->msc_timeslot, con->bsc_timeslot, + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_bsc, show_bsc_cmd, "show bsc connections", + SHOW_STR "Display information about current BSCs") +{ + struct bsc_connection *con; + struct sockaddr_in sock; + socklen_t len = sizeof(sock); + + llist_for_each_entry(con, &_nat->bsc_connections, list_entry) { + getpeername(con->write_queue.bfd.fd, (struct sockaddr *) &sock, &len); + vty_out(vty, "BSC lac: %d, %d auth: %d fd: %d peername: %s%s", + con->cfg ? con->cfg->nr : -1, + con->cfg ? con->cfg->lac : -1, + con->authenticated, con->write_queue.bfd.fd, + inet_ntoa(sock.sin_addr), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_bsc_cfg, show_bsc_cfg_cmd, "bsc config show", + "Display information about known BSC configs") +{ + struct bsc_config *conf; + llist_for_each_entry(conf, &_nat->bsc_configs, entry) { + vty_out(vty, "BSC token: '%s' lac: %u nr: %u%s", + conf->token, conf->lac, conf->nr, VTY_NEWLINE); + vty_out(vty, " imsi_allow: '%s' imsi_deny: '%s'%s", + conf->imsi_allow ? conf->imsi_allow: "any", + conf->imsi_deny ? conf->imsi_deny : "none", + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_stats, + show_stats_cmd, + "show statistics", + SHOW_STR "Display network statistics\n") +{ + struct bsc_config *conf; + + vty_out(vty, "NAT statistics%s", VTY_NEWLINE); + vty_out(vty, " SCCP Connections %lu total, %lu calls%s", + counter_get(_nat->stats.sccp.conn), + counter_get(_nat->stats.sccp.calls), VTY_NEWLINE); + vty_out(vty, " MSC Connections %lu%s", + counter_get(_nat->stats.msc.reconn), VTY_NEWLINE); + vty_out(vty, " BSC Connections %lu total, %lu auth failed.%s", + counter_get(_nat->stats.bsc.reconn), + counter_get(_nat->stats.bsc.auth_fail), VTY_NEWLINE); + + llist_for_each_entry(conf, &_nat->bsc_configs, entry) { + vty_out(vty, " BSC lac: %d nr: %d%s", + conf->lac, conf->nr, VTY_NEWLINE); + vty_out(vty, " SCCP Connnections %lu total, %lu calls%s", + counter_get(conf->stats.sccp.conn), + counter_get(conf->stats.sccp.calls), VTY_NEWLINE); + vty_out(vty, " BSC Connections %lu total%s", + counter_get(conf->stats.net.reconn), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_nat, cfg_nat_cmd, "nat", "Configute the NAT") +{ + vty->index = _nat; + vty->node = NAT_NODE; + + return CMD_SUCCESS; +} + +static void parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv) +{ + if (*imsi) { + talloc_free(*imsi); + *imsi = NULL; + } + regfree(reg); + + if (argc > 0) { + *imsi = talloc_strdup(ctx, argv[0]); + regcomp(reg, argv[0], 0); + } +} + +DEFUN(cfg_nat_imsi_allow, + cfg_nat_imsi_allow_cmd, + "imsi allow [REGEXP]", + "Allow matching IMSIs to talk to the MSC. " + "The defualt is to allow everyone.") +{ + parse_reg(_nat, &_nat->imsi_allow_re, &_nat->imsi_allow, argc, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_imsi_deny, + cfg_nat_imsi_deny_cmd, + "imsi deny [REGEXP]", + "Deny matching IMSIs to talk to the MSC. " + "The defualt is to not deny.") +{ + parse_reg(_nat, &_nat->imsi_deny_re, &_nat->imsi_deny, argc, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_msc_ip, + cfg_nat_msc_ip_cmd, + "msc ip IP", + "Set the IP address of the MSC.") +{ + bsc_nat_set_msc_ip(_nat, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_nat_msc_port, + cfg_nat_msc_port_cmd, + "msc port <1-65500>", + "Set the port of the MSC.") +{ + _nat->msc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* per BSC configuration */ +DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR", "Select a BSC to configure\n") +{ + int bsc_nr = atoi(argv[0]); + struct bsc_config *bsc; + + if (bsc_nr > _nat->num_bsc) { + vty_out(vty, "%% The next unused BSC number is %u%s", + _nat->num_bsc, VTY_NEWLINE); + return CMD_WARNING; + } else if (bsc_nr == _nat->num_bsc) { + /* allocate a new one */ + bsc = bsc_config_alloc(_nat, "unknown", 0); + } else + bsc = bsc_config_num(_nat, bsc_nr); + + if (!bsc) + return CMD_WARNING; + + vty->index = bsc; + vty->node = BSC_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_token, cfg_bsc_token_cmd, "token TOKEN", "Set the token") +{ + struct bsc_config *conf = vty->index; + + if (conf->token) + talloc_free(conf->token); + conf->token = talloc_strdup(conf, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_lac, cfg_bsc_lac_cmd, "location_area_code <0-65535>", + "Set the Location Area Code (LAC) of this BSC\n") +{ + struct bsc_config *tmp; + struct bsc_config *conf = vty->index; + + int lac = atoi(argv[0]); + + if (lac < 0 || lac > 0xffff) { + vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { + vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + /* verify that the LACs are unique */ + llist_for_each_entry(tmp, &_nat->bsc_configs, entry) { + if (tmp->lac == lac) { + vty_out(vty, "%% LAC %d is already used.%s", lac, VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + } + + conf->lac = lac; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_imsi_allow, + cfg_bsc_imsi_allow_cmd, + "imsi allow [REGEXP]", + "Allow IMSIs with the following network to talk to the MSC." + "The default is to allow everyone)") +{ + struct bsc_config *conf = vty->index; + + parse_reg(conf, &conf->imsi_allow_re, &conf->imsi_allow, argc, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_imsi_deny, + cfg_bsc_imsi_deny_cmd, + "imsi deny [REGEXP]", + "Deny IMSIs with the following network to talk to the MSC." + "The default is to not deny anyone.)") +{ + struct bsc_config *conf = vty->index; + + parse_reg(conf, &conf->imsi_deny_re, &conf->imsi_deny, argc, argv); + return CMD_SUCCESS; +} + +int bsc_nat_vty_init(struct bsc_nat *nat) +{ + _nat = nat; + + cmd_init(1); + vty_init(); + + /* show commands */ + install_element(VIEW_NODE, &show_sccp_cmd); + install_element(VIEW_NODE, &show_bsc_cmd); + install_element(VIEW_NODE, &show_bsc_cfg_cmd); + install_element(VIEW_NODE, &show_stats_cmd); + + openbsc_vty_add_cmds(); + + /* nat group */ + install_element(CONFIG_NODE, &cfg_nat_cmd); + install_node(&nat_node, config_write_nat); + install_default(NAT_NODE); + install_element(NAT_NODE, &cfg_nat_imsi_allow_cmd); + install_element(NAT_NODE, &cfg_nat_imsi_deny_cmd); + install_element(NAT_NODE, &cfg_nat_msc_ip_cmd); + install_element(NAT_NODE, &cfg_nat_msc_port_cmd); + + /* BSC subgroups */ + install_element(NAT_NODE, &cfg_bsc_cmd); + install_node(&bsc_node, config_write_bsc); + install_default(BSC_NODE); + install_element(BSC_NODE, &cfg_bsc_token_cmd); + install_element(BSC_NODE, &cfg_bsc_lac_cmd); + install_element(BSC_NODE, &cfg_bsc_imsi_allow_cmd); + install_element(BSC_NODE, &cfg_bsc_imsi_deny_cmd); + + mgcp_vty_init(); + + return 0; +} + + +/* called by the telnet interface... we have our own init above */ +void bsc_vty_init() +{} diff --git a/openbsc/src/nat/bsc_sccp.c b/openbsc/src/nat/bsc_sccp.c new file mode 100644 index 000000000..59d0cd574 --- /dev/null +++ b/openbsc/src/nat/bsc_sccp.c @@ -0,0 +1,206 @@ +/* SCCP patching and handling routines */ +/* + * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <openbsc/debug.h> +#include <openbsc/bsc_nat.h> + +#include <sccp/sccp.h> + +#include <osmocore/talloc.h> + +#include <string.h> + +static int equal(struct sccp_source_reference *ref1, struct sccp_source_reference *ref2) +{ + return memcmp(ref1, ref2, sizeof(*ref1)) == 0; +} + +/* + * SCCP patching below + */ + +/* check if we are using this ref for patched already */ +static int sccp_ref_is_free(struct sccp_source_reference *ref, struct bsc_nat *nat) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (memcmp(ref, &conn->patched_ref, sizeof(*ref)) == 0) + return -1; + } + + return 0; +} + +/* copied from sccp.c */ +static int assign_src_local_reference(struct sccp_source_reference *ref, struct bsc_nat *nat) +{ + static u_int32_t last_ref = 0x50000; + int wrapped = 0; + + do { + struct sccp_source_reference reference; + reference.octet1 = (last_ref >> 0) & 0xff; + reference.octet2 = (last_ref >> 8) & 0xff; + reference.octet3 = (last_ref >> 16) & 0xff; + + ++last_ref; + /* do not use the reversed word and wrap around */ + if ((last_ref & 0x00FFFFFF) == 0x00FFFFFF) { + LOGP(DNAT, LOGL_NOTICE, "Wrapped searching for a free code\n"); + last_ref = 0; + ++wrapped; + } + + if (sccp_ref_is_free(&reference, nat) == 0) { + *ref = reference; + return 0; + } + } while (wrapped != 2); + + LOGP(DNAT, LOGL_ERROR, "Finding a free reference failed\n"); + return -1; +} + +int create_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed) +{ + struct sccp_connections *conn; + + conn = talloc_zero(bsc->nat, struct sccp_connections); + if (!conn) { + LOGP(DNAT, LOGL_ERROR, "Memory allocation failure.\n"); + return -1; + } + + conn->bsc = bsc; + conn->real_ref = *parsed->src_local_ref; + if (assign_src_local_reference(&conn->patched_ref, bsc->nat) != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to assign a ref.\n"); + talloc_free(conn); + return -1; + } + + bsc_mgcp_clear(conn); + llist_add_tail(&conn->list_entry, &bsc->nat->sccp_connections); + counter_inc(bsc->cfg->stats.sccp.conn); + counter_inc(bsc->cfg->nat->stats.sccp.conn); + + LOGP(DNAT, LOGL_DEBUG, "Created 0x%x <-> 0x%x mapping for con %p\n", + sccp_src_ref_to_int(&conn->real_ref), + sccp_src_ref_to_int(&conn->patched_ref), bsc); + + return 0; +} + +int update_sccp_src_ref(struct sccp_connections *sccp, struct bsc_nat_parsed *parsed) +{ + if (!parsed->dest_local_ref || !parsed->src_local_ref) { + LOGP(DNAT, LOGL_ERROR, "CC MSG should contain both local and dest address.\n"); + return -1; + } + + sccp->remote_ref = *parsed->src_local_ref; + LOGP(DNAT, LOGL_DEBUG, "Updating 0x%x to remote 0x%x on %p\n", + sccp_src_ref_to_int(&sccp->patched_ref), + sccp_src_ref_to_int(&sccp->remote_ref), sccp->bsc); + + return 0; +} + +void remove_sccp_src_ref(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &bsc->nat->sccp_connections, list_entry) { + if (memcmp(parsed->src_local_ref, + &conn->patched_ref, sizeof(conn->patched_ref)) == 0) { + + sccp_connection_destroy(conn); + return; + } + } + + LOGP(DNAT, LOGL_ERROR, "Can not remove connection: 0x%x\n", + sccp_src_ref_to_int(parsed->src_local_ref)); +} + +/* + * We have a message from the MSC to the BSC. The MSC is using + * an address that was assigned by the MUX, we need to update the + * dest reference to the real network. + */ +struct sccp_connections *patch_sccp_src_ref_to_bsc(struct msgb *msg, + struct bsc_nat_parsed *parsed, + struct bsc_nat *nat) +{ + struct sccp_connections *conn; + + if (!parsed->dest_local_ref) { + LOGP(DNAT, LOGL_ERROR, "MSG should contain dest_local_ref.\n"); + return NULL; + } + + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (!equal(parsed->dest_local_ref, &conn->patched_ref)) + continue; + + /* Change the dest address to the real one */ + *parsed->dest_local_ref = conn->real_ref; + return conn; + } + + return NULL; +} + +/* + * These are message to the MSC. We will need to find the BSC + * Connection by either the SRC or the DST local reference. + * + * In case of a CR we need to work by the SRC local reference + * in all other cases we need to work by the destination local + * reference.. + */ +struct sccp_connections *patch_sccp_src_ref_to_msc(struct msgb *msg, + struct bsc_nat_parsed *parsed, + struct bsc_nat *nat) +{ + struct sccp_connections *conn; + + llist_for_each_entry(conn, &nat->sccp_connections, list_entry) { + if (parsed->src_local_ref) { + if (equal(parsed->src_local_ref, &conn->real_ref)) { + *parsed->src_local_ref = conn->patched_ref; + return conn; + } + } else if (parsed->dest_local_ref) { + if (equal(parsed->dest_local_ref, &conn->remote_ref)) + return conn; + } else { + LOGP(DNAT, LOGL_ERROR, "Header has neither loc/dst ref.\n"); + return NULL; + } + } + + return NULL; +} + diff --git a/openbsc/src/paging.c b/openbsc/src/paging.c index 314d3d135..73fdfbeef 100644 --- a/openbsc/src/paging.c +++ b/openbsc/src/paging.c @@ -327,3 +327,15 @@ void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t free_slots) { bts->paging.available_slots = free_slots; } + +unsigned int paging_pending_requests_nr(struct gsm_bts *bts) +{ + unsigned int requests = 0; + + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->paging.pending_requests, entry) + ++requests; + + return requests; +} diff --git a/openbsc/src/silent_call.c b/openbsc/src/silent_call.c index 8bd5341ec..85c7f8987 100644 --- a/openbsc/src/silent_call.c +++ b/openbsc/src/silent_call.c @@ -140,7 +140,7 @@ int gsm_silent_call_stop(struct gsm_subscriber *subscr) if (!conn->silent_call) return -EINVAL; - put_subscr_con(conn); + put_subscr_con(conn, 0); return 0; } diff --git a/openbsc/src/transaction.c b/openbsc/src/transaction.c index 5e0d50796..bd2761b5f 100644 --- a/openbsc/src/transaction.c +++ b/openbsc/src/transaction.c @@ -28,6 +28,7 @@ #include <openbsc/gsm_04_08.h> #include <openbsc/mncc.h> #include <openbsc/paging.h> +#include <openbsc/chan_alloc.h> void *tall_trans_ctx; @@ -95,14 +96,14 @@ void trans_free(struct gsm_trans *trans) break; } - if (trans->conn) - put_subscr_con(trans->conn); - if (!trans->conn && trans->subscr && trans->subscr->net) { /* Stop paging on all bts' */ paging_request_stop(NULL, trans->subscr, NULL); } + if (trans->conn) + put_subscr_con(trans->conn, 0); + if (trans->subscr) subscr_put(trans->subscr); @@ -159,7 +160,7 @@ int trans_lchan_change(struct gsm_subscriber_connection *conn_old, if (trans->conn == conn_old) { /* drop old channel use count */ - put_subscr_con(conn_old); + put_subscr_con(conn_old, 0); /* assign new channel */ trans->conn = conn_new; /* bump new channel use count */ diff --git a/openbsc/src/vty/command.c b/openbsc/src/vty/command.c index a38ed0424..d46a1bedb 100644 --- a/openbsc/src/vty/command.c +++ b/openbsc/src/vty/command.c @@ -47,6 +47,7 @@ Boston, MA 02111-1307, USA. */ #include <openbsc/gsm_data.h> #include <openbsc/gsm_subscriber.h> +#include <openbsc/bsc_nat.h> #include <osmocore/talloc.h> void *tall_vty_cmd_ctx; @@ -1949,6 +1950,13 @@ enum node_type vty_go_parent(struct vty *vty) subscr_put(vty->index); vty->index = NULL; break; + case BSC_NODE: + vty->node = NAT_NODE; + { + struct bsc_config *bsc = vty->index; + vty->index = bsc->nat; + } + break; default: vty->node = CONFIG_NODE; } @@ -2365,6 +2373,15 @@ DEFUN(config_exit, case MGCP_NODE: vty->node = CONFIG_NODE; vty->index = NULL; + case NAT_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case BSC_NODE: + vty->node = NAT_NODE; + vty->index = NULL; + break; + default: break; } diff --git a/openbsc/src/vty_interface.c b/openbsc/src/vty_interface.c index dd35372ca..aba7bccd9 100644 --- a/openbsc/src/vty_interface.c +++ b/openbsc/src/vty_interface.c @@ -39,6 +39,8 @@ #include <osmocore/talloc.h> #include <openbsc/telnet_interface.h> #include <openbsc/vty.h> +#include <openbsc/ipaccess.h> +#include <openbsc/paging.h> static struct gsm_network *gsmnet; @@ -100,6 +102,7 @@ static void dump_pchan_load_vty(struct vty *vty, char *prefix, static void net_dump_vty(struct vty *vty, struct gsm_network *net) { + int i; struct pchan_load pl; vty_out(vty, "BSC is on Country Code %u, Network Code %u " @@ -117,6 +120,8 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net) VTY_NEWLINE); vty_out(vty, " NECI (TCH/H): %u%s", net->neci, VTY_NEWLINE); + vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch, + VTY_NEWLINE); vty_out(vty, " RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode), VTY_NEWLINE); vty_out(vty, " MM Info: %s%s", net->send_mm_info ? "On" : "Off", @@ -126,6 +131,12 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net) network_chan_load(&pl, net); vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); dump_pchan_load_vty(vty, " ", &pl); + + vty_out(vty, " Allowed Audio Codecs: "); + for (i = 0; i < net->audio_length; ++i) + vty_out(vty, "hr: %d ver: %d, ", + net->audio_support[i]->hr, net->audio_support[i]->ver); + vty_out(vty, "%s", VTY_NEWLINE); } DEFUN(show_net, show_net_cmd, "show network", @@ -186,7 +197,8 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) net_dump_nmstate(vty, &bts->nm_state); vty_out(vty, " Site Mgr NM State: "); net_dump_nmstate(vty, &bts->site_mgr.nm_state); - vty_out(vty, " Paging: FIXME pending requests, %u free slots%s", + vty_out(vty, " Paging: %u pending requests, %u free slots%s", + paging_pending_requests_nr(bts), bts->paging.available_slots, VTY_NEWLINE); if (!is_ipaccess_bts(bts)) { vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); @@ -224,6 +236,36 @@ DEFUN(show_bts, show_bts_cmd, "show bts [number]", return CMD_SUCCESS; } +DEFUN(test_bts_lchan_alloc, test_bts_lchan_alloc_cmd, "test bts alloc (sdcch|tch_h|tch_f)", + "Test command to allocate all channels. You will need to restart. To free these channels.\n") +{ + struct gsm_network *net = gsmnet; + int bts_nr; + + enum gsm_chan_t type = GSM_LCHAN_NONE; + + if (strcmp("sdcch", argv[0]) == 0) + type = GSM_LCHAN_SDCCH; + else if (strcmp("tch_h", argv[0]) == 0) + type = GSM_LCHAN_TCH_H; + else if (strcmp("tch_f", argv[0]) == 0) + type = GSM_LCHAN_TCH_F; + else { + vty_out(vty, "Unknown mode for allocation.%s", VTY_NEWLINE); + } + + for (bts_nr = 0; bts_nr < net->num_bts; ++bts_nr) { + struct gsm_bts *bts = gsm_bts_num(net, bts_nr); + struct gsm_lchan *lchan; + + /* alloc the channel */ + while ((lchan = lchan_alloc(bts, type, 0)) != NULL) + rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); + } + + return CMD_SUCCESS; +} + /* utility functions */ static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line, const char *ts, const char *ss) @@ -362,7 +404,11 @@ static int config_write_net(struct vty *vty) { vty_out(vty, "network%s", VTY_NEWLINE); vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE); + if (gsmnet->core_country_code > 0) + vty_out(vty, " core network country code %u%s", gsmnet->core_country_code, VTY_NEWLINE); vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE); + if (gsmnet->core_network_code > 0) + vty_out(vty, " core mobile network code %u%s", gsmnet->core_network_code, VTY_NEWLINE); vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE); vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE); vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE); @@ -370,6 +416,7 @@ static int config_write_net(struct vty *vty) gsmnet->reject_cause, VTY_NEWLINE); vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE); vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE); + vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE); vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode), VTY_NEWLINE); vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE); @@ -397,6 +444,30 @@ static int config_write_net(struct vty *vty) vty_out(vty, " timer t3117 %u%s", gsmnet->T3117, VTY_NEWLINE); vty_out(vty, " timer t3119 %u%s", gsmnet->T3119, VTY_NEWLINE); vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE); + vty_out(vty, " ipacc rtp_payload %u%s", gsmnet->rtp_payload, VTY_NEWLINE); + vty_out(vty, " rtp base %u%s", gsmnet->rtp_base_port, VTY_NEWLINE); + + if (gsmnet->audio_length != 0) { + int i; + + vty_out(vty, " codec_list "); + for (i = 0; i < gsmnet->audio_length; ++i) { + printf("I... %d %d\n", i, gsmnet->audio_length); + if (i != 0) + vty_out(vty, ", "); + + if (gsmnet->audio_support[i]->hr) + vty_out(vty, "hr%.1u", gsmnet->audio_support[i]->ver); + else + vty_out(vty, "fr%.1u", gsmnet->audio_support[i]->ver); + } + vty_out(vty, "%s", VTY_NEWLINE); + } + + if (gsmnet->bsc_token) + vty_out(vty, " bsc_token %s%s", gsmnet->bsc_token, VTY_NEWLINE); + vty_out(vty, " msc ip %s%s", gsmnet->msc_ip, VTY_NEWLINE); + vty_out(vty, " msc port %d%s", gsmnet->msc_port, VTY_NEWLINE); return CMD_SUCCESS; } @@ -879,6 +950,50 @@ DEFUN(show_paging, return CMD_SUCCESS; } +DEFUN(drop_bts, + drop_bts_cmd, + "drop bts connection <0-65535> (oml|rsl)", + "Debug/Simulation command to drop ipaccess BTS\n") +{ + struct gsm_bts_trx *trx; + struct gsm_bts *bts; + unsigned int bts_nr; + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + + /* close all connections */ + if (strcmp(argv[1], "oml") == 0) + ipaccess_drop_oml(bts); + else if (strcmp(argv[1], "rsl") == 0) { + /* close all rsl connections */ + llist_for_each_entry(trx, &bts->trx_list, list) { + ipaccess_drop_rsl(trx); + } + } else { + vty_out(vty, "Argument must be 'oml' or 'rsl'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + DEFUN(cfg_net, cfg_net_cmd, "network", @@ -901,6 +1016,16 @@ DEFUN(cfg_net_ncc, return CMD_SUCCESS; } +DEFUN(cfg_core_net_ncc, + cfg_core_net_ncc_cmd, + "core network country code <1-999>", + "Set the GSM country code to be used in the MSC connection") +{ + gsmnet->core_country_code = atoi(argv[0]); + + return CMD_SUCCESS; +} + DEFUN(cfg_net_mnc, cfg_net_mnc_cmd, "mobile network code <1-999>", @@ -911,6 +1036,16 @@ DEFUN(cfg_net_mnc, return CMD_SUCCESS; } +DEFUN(cfg_core_net_mnc, + cfg_core_net_mnc_cmd, + "core mobile network code <1-999>", + "Set the GSM mobile network code to be used in the MSC connection") +{ + gsmnet->core_network_code = atoi(argv[0]); + + return CMD_SUCCESS; +} + DEFUN(cfg_net_name_short, cfg_net_name_short_cmd, "short name NAME", @@ -975,6 +1110,7 @@ DEFUN(cfg_net_neci, "Set if NECI of cell selection is to be set") { gsmnet->neci = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); return CMD_SUCCESS; } @@ -1061,6 +1197,133 @@ DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd, return CMD_SUCCESS; } +DEFUN(cfg_net_supported_codecs, + cfg_net_supported_codecs_cmd, + "codec_list .LIST", + "Set the three preferred audio codecs.\n" + "Codec List") +{ + int saw_fr, saw_hr; + int i; + + saw_fr = saw_hr = 0; + + /* free the old list... if it exists */ + if (gsmnet->audio_support) { + talloc_free(gsmnet->audio_support); + gsmnet->audio_support = NULL; + gsmnet->audio_length = 0; + } + + /* create a new array */ + gsmnet->audio_support = + talloc_zero_array(gsmnet, struct gsm_audio_support *, argc); + gsmnet->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; + + gsmnet->audio_support[i] = talloc_zero(gsmnet->audio_support, + struct gsm_audio_support); + gsmnet->audio_support[i]->ver = atoi(argv[i] + 2); + + if (strncmp("hr", argv[i], 2) == 0) { + gsmnet->audio_support[i]->hr = 1; + saw_hr = 1; + } else if (strncmp("fr", argv[i], 2) == 0) { + gsmnet->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_ipacc_rtp_payload, + cfg_net_ipacc_rtp_payload_cmd, + "ipacc rtp_payload <0-256>", + "Override the RTP payload to use") +{ + gsmnet->rtp_payload = atoi(argv[0]) & 0xff; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_rtp_base_port, + cfg_net_rtp_base_port_cmd, + "rtp base <0-65534>", + "Base port to use for MGCP RTP") +{ + unsigned int port = atoi(argv[0]); + if (port > 65534) { + vty_out(vty, "%% wrong base port '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->rtp_base_port = port; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_token, + cfg_net_bsc_token_cmd, + "bsc_token TOKEN", + "A token for the BSC to be sent to the MSC") +{ + if (gsmnet->bsc_token) + talloc_free(gsmnet->bsc_token); + gsmnet->bsc_token = talloc_strdup(gsmnet, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_pag_any_tch, + cfg_net_pag_any_tch_cmd, + "paging any use tch (0|1)", + "Assign a TCH when receiving a Paging Any request") +{ + gsmnet->pag_any_tch = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_ip, + cfg_net_msc_ip_cmd, + "msc ip IP", + "Set the MSC/MUX IP address.") +{ + if (gsmnet->msc_ip) + talloc_free(gsmnet->msc_ip); + gsmnet->msc_ip = talloc_strdup(gsmnet, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_port, + cfg_net_msc_port_cmd, + "msc port <1-65000>", + "Set the MSC/MUX port.") +{ + gsmnet->msc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + + #define DECLARE_TIMER(number, doc) \ DEFUN(cfg_net_T##number, \ cfg_net_T##number##_cmd, \ @@ -1071,7 +1334,7 @@ DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd, \ if (value < 0 || value > 65535) { \ vty_out(vty, "Timer value %s out of range.%s", \ - argv[0], VTY_NEWLINE); \ + argv[0], VTY_NEWLINE); \ return CMD_WARNING; \ } \ \ @@ -1084,14 +1347,13 @@ DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.") DECLARE_TIMER(3105, "Currently not used.") DECLARE_TIMER(3107, "Currently not used.") DECLARE_TIMER(3109, "Currently not used.") -DECLARE_TIMER(3111, "Currently not used.") +DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel.") DECLARE_TIMER(3113, "Set the time to try paging a subscriber.") DECLARE_TIMER(3115, "Currently not used.") DECLARE_TIMER(3117, "Currently not used.") DECLARE_TIMER(3119, "Currently not used.") DECLARE_TIMER(3141, "Currently not used.") - /* per-BTS configuration */ DEFUN(cfg_bts, cfg_bts_cmd, @@ -1721,13 +1983,18 @@ int bsc_vty_init(struct gsm_network *net) install_element(VIEW_NODE, &show_paging_cmd); - openbsc_vty_add_cmds(); + install_element(VIEW_NODE, &drop_bts_cmd); + install_element(VIEW_NODE, &test_bts_lchan_alloc_cmd); + openbsc_vty_add_cmds(); + install_element(CONFIG_NODE, &cfg_net_cmd); install_node(&net_node, config_write_net); install_default(GSMNET_NODE); install_element(GSMNET_NODE, &cfg_net_ncc_cmd); + install_element(GSMNET_NODE, &cfg_core_net_ncc_cmd); install_element(GSMNET_NODE, &cfg_net_mnc_cmd); + install_element(GSMNET_NODE, &cfg_core_net_mnc_cmd); install_element(GSMNET_NODE, &cfg_net_name_short_cmd); install_element(GSMNET_NODE, &cfg_net_name_long_cmd); install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd); @@ -1743,6 +2010,9 @@ int bsc_vty_init(struct gsm_network *net) install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd); install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd); install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd); + install_element(GSMNET_NODE, &cfg_net_supported_codecs_cmd); + install_element(GSMNET_NODE, &cfg_net_ipacc_rtp_payload_cmd); + install_element(GSMNET_NODE, &cfg_net_rtp_base_port_cmd); install_element(GSMNET_NODE, &cfg_net_T3101_cmd); install_element(GSMNET_NODE, &cfg_net_T3103_cmd); install_element(GSMNET_NODE, &cfg_net_T3105_cmd); @@ -1754,6 +2024,10 @@ int bsc_vty_init(struct gsm_network *net) install_element(GSMNET_NODE, &cfg_net_T3117_cmd); install_element(GSMNET_NODE, &cfg_net_T3119_cmd); install_element(GSMNET_NODE, &cfg_net_T3141_cmd); + install_element(GSMNET_NODE, &cfg_net_bsc_token_cmd); + install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd); + install_element(GSMNET_NODE, &cfg_net_msc_ip_cmd); + install_element(GSMNET_NODE, &cfg_net_msc_port_cmd); install_element(GSMNET_NODE, &cfg_bts_cmd); install_node(&bts_node, config_write_bts); diff --git a/openbsc/src/vty_interface_bsc.c b/openbsc/src/vty_interface_bsc.c new file mode 100644 index 000000000..51a95a517 --- /dev/null +++ b/openbsc/src/vty_interface_bsc.c @@ -0,0 +1,75 @@ +/* OpenBSC interface to quagga VTY - BSC options */ +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +#include <vty/command.h> +#include <vty/buffer.h> +#include <vty/vty.h> + +#include <openbsc/gsm_data.h> +#include <openbsc/vty.h> + +#include <sccp/sccp.h> + +static struct gsm_network *gsmnet = NULL; + +extern struct llist_head *bsc_sccp_connections(); + +DEFUN(show_bsc, show_bsc_cmd, "show bsc", + SHOW_STR "Display information about the BSC\n") +{ + struct bss_sccp_connection_data *con; + + vty_out(vty, "BSC Information%s", VTY_NEWLINE); + llist_for_each_entry(con, bsc_sccp_connections(), active_connections) { + vty_out(vty, " Connection: LCHAN: %p sec LCHAN: %p SCCP src: %d dest: %d%s", + con->lchan, con->secondary_lchan, + con->sccp ? (int) sccp_src_ref_to_int(&con->sccp->source_local_reference) : -1, + con->sccp ? (int) sccp_src_ref_to_int(&con->sccp->destination_local_reference) : -1, + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_stats, + show_stats_cmd, + "show statistics", + SHOW_STR "Display network statistics\n") +{ + struct gsm_network *net = gsmnet; + + openbsc_vty_print_statistics(vty, net); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(struct gsm_network *net) +{ + gsmnet = net; + + /* get runtime information */ + install_element(VIEW_NODE, &show_bsc_cmd); + install_element(VIEW_NODE, &show_stats_cmd); + + return 0; +} |