Cell Broadcast: CBSP and CBCH scheduling support

This adds code to handle CBSP (Cell Broadcast Service Protocol)
from the CBC (Cell Broadcast Centre), as well as BSC-internal data
structures for scheduling the various SMSCB on the CBCH of each BTS.

There are currently one known shortcoming in the code: We don't yet
verify if keepalives are received within repetition period.

Change-Id: Ia0a0de862a104d0f447a5d6e56c7c83981b825c7
This commit is contained in:
Harald Welte 2019-06-13 09:41:58 +02:00
parent 9508e2232f
commit d41b7c7f83
19 changed files with 1898 additions and 7 deletions

View File

@ -0,0 +1,82 @@
[[smscb]]
== SMSCB (Cell Broadcast)
OsmoBSC supports SMS Cell Broadcast (SMSCB) services (CBS). This
includes the CBSP protocol to interact with a CBC (Cell Broadcast
Centre) such as OsmoCBC, as well as the scheduling of SMSCB messages on
both the BASIC and EXTENDED CBCH and transmission of related RSL
messages to the attached BTS.
More high-level information can be found at
https://en.wikipedia.org/wiki/Cell_Broadcast and the related
specification is <<3gpp-ts-23-041>>.
In order to use SMSCB with OsmoBSC, you will need to
* Configure the CBSP server and/or client
* Use a channel combination including a CBCH on the BTSs
=== Enabling a CBCH channel combination
On the Um interface, SMSCB are transmitted via the CBCH (Cell Broadcast
Channel). The CBCH is a separate downlink-only logical channel which
must be activated on any of the BTSs requiring CBSP support.
The channel combination is configured in the `timeslot` node of each TRX.
The two `phys_chan_config` supporting CBCH are `CCCH+SDCCH4+CBCH` and
`SDCCH/8+CBCH`. Please note that the CBCH steals one of the SDCCH, so
a SDCCH/4 will only have three remaining SDCCH, and a SDCCH/8 will
have only seven remaining SDCCH.
=== Configuring the CBSP connection
CBSP is the protocol between BSC and CBC. It operates over TCP.
According to 3GPP TS 48.049, a BSC typically operates as a TCP server,
and the CBC connects as TCP client. This would require the CBC to have
out-of-band knowledge of all the BSCs in the network (and their IP
addresses).
In order to comply with the specifications, OsmoBSC supports this mode
of operation as CBSP TCP server. However, to make network operation and
configuration more simple, it also can operate in TCP client mode,
connecting to the CBC. This way the all the BSCs need to know is the CBC IP
address, but not vice-versa.
The BSC can operate both CBSP TCP server and CBSP TCP client mode in
parallel.
The CBC related configuration of OsmoBSC can be found in the `cbc` configuration
node of the VTY interface.
.Example: Configure CBSP TCP client to connect to CBC at 1.2.3.4:48049
----
OsmoBSC> enable
OsmoBSC# configure terminal
OsmoBSC(config)# cbc
OsmoBSC(config-cbc)# remote-ip 1.2.3.4
OsmoBSC(config-cbc)# remote-port 48049
OsmoBSC(config-cbc)# end
----
.Example: Disable CBSP TCP client
----
OsmoBSC> enable
OsmoBSC# configure terminal
OsmoBSC(config)# cbc
OsmoBSC(config-cbc)# no remote-ip
OsmoBSC(config-cbc)# end
----
.Example: Configure CBSP TCP server to listen for CBC at 127.0.0.2:9999
----
OsmoBSC> enable
OsmoBSC# configure terminal
OsmoBSC(config)# cbc
OsmoBSC(config-cbc)# listen-ip 127.0.0.2
OsmoBSC(config-cbc)# listen-port 9999
OsmoBSC(config-cbc)# end
----
For more details on the available configuration commands, please check the OsmoBSC VTY Reference.

View File

@ -24,6 +24,8 @@ include::./common/chapters/bsc.adoc[]
include::{srcdir}/chapters/handover.adoc[]
include::{srcdir}/chapters/smscb.adoc[]
include::./common/chapters/counters-overview.adoc[]
include::{srcdir}/chapters/counters.adoc[]
@ -34,8 +36,6 @@ include::./common/chapters/control_if.adoc[]
include::{srcdir}/chapters/control.adoc[]
include::./common/chapters/cell-broadcast.adoc[]
include::{srcdir}/chapters/osmux_bsc.adoc[]
include::./common/chapters/port_numbers.adoc[]

View File

@ -320,7 +320,7 @@
<param name='MASK' doc='List of logging categories to log, e.g. &apos;abc:mno:xyz&apos;. Available log categories depend on the specific application, refer to the &apos;logging level&apos; command. Optionally add individual log levels like &apos;abc,1:mno,3:xyz,5&apos;, where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
</params>
</command>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@ -343,6 +343,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
<param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@ -514,6 +515,22 @@
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
</params>
</command>
<command id='show cbc'>
<params>
<param name='show' doc='Show running system information' />
<param name='cbc' doc='Display state of CBC / CBSP' />
</params>
</command>
<command id='show bts &lt;0-255&gt; smscb [(basic|extended)]'>
<params>
<param name='show' doc='Show running system information' />
<param name='bts' doc='Display information about a BTS' />
<param name='&lt;0-255&gt;' doc='BTS number' />
<param name='smscb' doc='SMS Cell Broadcast State' />
<param name='[basic]' doc='Show only information related to CBCH BASIC' />
<param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
</params>
</command>
<command id='show statistics'>
<params>
<param name='show' doc='Show running system information' />
@ -971,7 +988,7 @@
<param name='MASK' doc='List of logging categories to log, e.g. &apos;abc:mno:xyz&apos;. Available log categories depend on the specific application, refer to the &apos;logging level&apos; command. Optionally add individual log levels like &apos;abc,1:mno,3:xyz,5&apos;, where the level numbers are LOGL_DEBUG=1 LOGL_INFO=3 LOGL_NOTICE=5 LOGL_ERROR=7 LOGL_FATAL=8' />
</params>
</command>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@ -994,6 +1011,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
<param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@ -1356,6 +1374,16 @@
<param name='all' doc='Display a list of all FSM instances of all finite state machine' />
</params>
</command>
<command id='show bts &lt;0-255&gt; smscb [(basic|extended)]'>
<params>
<param name='show' doc='Show running system information' />
<param name='bts' doc='Display information about a BTS' />
<param name='&lt;0-255&gt;' doc='BTS number' />
<param name='smscb' doc='SMS Cell Broadcast State' />
<param name='[basic]' doc='Show only information related to CBCH BASIC' />
<param name='[extended]' doc='Show only information related to CBCH EXTENDED' />
</params>
</command>
<command id='show statistics'>
<params>
<param name='show' doc='Show running system information' />
@ -1735,6 +1763,11 @@
<param name='e1_input' doc='Configure E1/T1/J1 TDM input' />
</params>
</command>
<command id='cbc'>
<params>
<param name='cbc' doc='Configure CBSP Link to Cell Broadcast Centre' />
</params>
</command>
<command id='msc [&lt;0-1000&gt;]'>
<params>
<param name='msc' doc='Configure MSC details' />
@ -1833,7 +1866,7 @@
<param name='[last]' doc='Log source file info at the end of a log line. If omitted, log source file info just before the log text.' />
</params>
</command>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<command id='logging level (rll|mm|rr|rsl|nm|pag|meas|msc|ho|hodec|ref|nat|ctrl|filter|pcu|lcls|chan|ts|as|cbs|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro) (debug|info|notice|error|fatal)'>
<params>
<param name='logging' doc='Configure logging' />
<param name='level' doc='Set the log level for a specified category' />
@ -1856,6 +1889,7 @@
<param name='chan' doc='lchan FSM' />
<param name='ts' doc='timeslot FSM' />
<param name='as' doc='assignment FSM' />
<param name='cbs' doc='Cell Broadcast System' />
<param name='lglobal' doc='Library-internal global log family' />
<param name='llapd' doc='LAPD in libosmogsm' />
<param name='linp' doc='A-bis Intput Subsystem' />
@ -4930,4 +4964,43 @@
</params>
</command>
</node>
<node id='config-cbc'>
<name>config-cbc</name>
<command id='remote-ip A.B.C.D'>
<params>
<param name='remote-ip' doc='IP Address of the Cell Broadcast Centre' />
<param name='A.B.C.D' doc='IP Address of the Cell Broadcast Centre' />
</params>
</command>
<command id='no remote-ip'>
<params>
<param name='no' doc='Negate a command or set its defaults' />
<param name='remote-ip' doc='Remove IP address of CBC; disables outbound CBSP connections' />
</params>
</command>
<command id='remote-port &lt;1-65535&gt;'>
<params>
<param name='remote-port' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
<param name='&lt;1-65535&gt;' doc='TCP Port number of the Cell Broadcast Centre (Default: 48049)' />
</params>
</command>
<command id='listen-port &lt;1-65535&gt;'>
<params>
<param name='listen-port' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
<param name='&lt;1-65535&gt;' doc='Local TCP port at which BSC listens for incoming CBSP connections from CBC' />
</params>
</command>
<command id='no listen-port'>
<params>
<param name='no' doc='Negate a command or set its defaults' />
<param name='listen-port' doc='Remove CBSP Listen Port; disables inbound CBSP connections' />
</params>
</command>
<command id='listen-ip A.B.C.D'>
<params>
<param name='listen-ip' doc='Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)' />
<param name='A.B.C.D' doc='Local IP Address where BSC listens for incoming CBC connections' />
</params>
</command>
</node>
</vtydoc>

View File

@ -55,4 +55,5 @@ noinst_HEADERS = \
gsm_08_08.h \
penalty_timers.h \
osmo_bsc_lcls.h \
smscb.h \
$(NULL)

View File

@ -150,6 +150,7 @@ struct bsc_msc_data {
/*
* Per BSC data.
*/
struct bsc_cbc_link;
struct osmo_bsc_data {
struct gsm_network *network;
@ -167,6 +168,8 @@ struct osmo_bsc_data {
char *ussd_no_msc_txt;
char *acc_lst_name;
struct bsc_cbc_link *cbc;
};

View File

@ -27,6 +27,7 @@ enum {
DCHAN,
DTS,
DAS,
DCBS,
Debug_LastEntry,
};

View File

@ -14,6 +14,7 @@
#include <osmocom/core/stat_item.h>
#include <osmocom/gsm/bts_features.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/core/fsm.h>
@ -957,6 +958,53 @@ struct gsm_bts_ref {
struct gsm_bts *bts;
};
/* A single Page of a SMSCB message */
struct bts_smscb_page {
/* SMSCB message we're part of */
struct bts_smscb_message *msg;
/* Page Number within message (1 to 15) */
uint8_t nr;
/* number of valid blocks in data (up to 4) */
uint8_t num_blocks;
/* up to four blocks of 22 bytes each */
uint8_t data[88];
};
/* A SMSCB message (received from CBSP) */
struct bts_smscb_message {
/* entry in bts_smscb_chan_state.messages */
struct llist_head list;
struct {
/* input data from CBSP (CBC) side */
uint16_t msg_id;
uint16_t serial_nr;
enum cbsp_category category;
uint16_t rep_period;
uint16_t num_bcast_req;
uint8_t dcs;
} input;
/* how often have all pages of this message been broadcast? */
uint32_t bcast_count;
/* actual page data of this message */
uint8_t num_pages; /* up to 15 */
struct bts_smscb_page page[15];
};
/* per-channel (basic/extended) CBCH state for a single BTS */
struct bts_smscb_chan_state {
/* back-pointer to BTS */
struct gsm_bts *bts;
/* list of bts_smscb_message */
struct llist_head messages;
/* scheduling array; pointer of SMSCB pages */
struct bts_smscb_page **sched_arr;
size_t sched_arr_size;
/* index of the next to be transmitted page into the scheduler array */
size_t next_idx;
/* number of messages we have to pause due to overflow */
uint8_t overflow;
};
/* One BTS */
struct gsm_bts {
/* list header in net->bts_list */
@ -1213,6 +1261,11 @@ struct gsm_bts {
struct load_counter chan_load_samples[7];
int chan_load_samples_idx;
uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */
/* cell broadcast system */
struct osmo_timer_list cbch_timer;
struct bts_smscb_chan_state cbch_basic;
struct bts_smscb_chan_state cbch_extended;
};
/* One rejected BTS */

View File

@ -0,0 +1,59 @@
#pragma once
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/core/msgb.h>
#include <osmocom/netif/stream.h>
#include <osmocom/gsm/cbsp.h>
struct bsc_cbc_link;
/* smscb.c */
void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
const char *reason);
const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb);
struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended);
int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec);
int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg);
const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate);
unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate);
unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate);
void smscb_vty_init(void);
/* cbch_scheduler.c */
int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out);
struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate);
void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page);
int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
uint8_t slot_count);
void bts_cbch_timer_schedule(struct gsm_bts *bts);
/* cbsp_link.c */
struct bsc_cbc_link {
struct gsm_network *net;
struct {
/* hostname/IP of CBC */
char *cbc_hostname;
/* TCP port (Default: 48049) of CBC */
int cbc_port;
/* local listening port (0 for disabling local server) */
int listen_port;
/* local listening hostname/IP */
char *listen_hostname;
} config;
/* for handling inbound TCP connections */
struct {
struct osmo_stream_srv *srv;
struct osmo_stream_srv_link *link;
char *sock_name;
struct msgb *msg;
} server;
/* for handling outbound TCP connections */
struct {
struct osmo_stream_cli *cli;
char *sock_name;
struct msgb *msg;
} client;
};
void cbc_vty_init(void);
int bsc_cbc_link_restart(void);
int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *decoded);

View File

@ -24,6 +24,7 @@ enum bsc_vty_node {
OM2K_NODE,
OM2K_CON_GROUP_NODE,
BSC_NODE,
CBC_NODE,
};
struct log_info;

View File

@ -88,6 +88,9 @@ osmo_bsc_SOURCES = \
rest_octets.c \
system_information.c \
timeslot_fsm.c \
smscb.c \
cbch_scheduler.c \
cbsp_link.c \
$(NULL)
osmo_bsc_LDADD = \
@ -96,6 +99,7 @@ osmo_bsc_LDADD = \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMONETIF_LIBS) \
$(COVERAGE_LDFLAGS) \
$(LIBOSMOABIS_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \

View File

@ -1,7 +1,7 @@
/* GSM Radio Signalling Link messages on the A-bis interface
* 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
/* (C) 2008-2019 by Harald Welte <laforge@gnumonks.org>
* (C) 2012 by Holger Hans Peter Freyther
*
* All Rights Reserved
@ -52,6 +52,7 @@
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
#define RSL_ALLOC_SIZE 1024
#define RSL_ALLOC_HEADROOM 128
@ -1489,6 +1490,36 @@ static int rsl_rx_ccch_load(struct msgb *msg)
return 0;
}
/* 8.5.9 current load on the CBCH (Cell Broadcast) */
static int rsl_rx_cbch_load(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = msg->dst;
struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
struct gsm_bts *bts = sign_link->trx->bts;
bool cbch_extended = false;
bool is_overflow = false;
int8_t load_info;
struct tlv_parsed tp;
uint8_t slot_count;
rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh));
if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) {
LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n");
return -1;
}
/* 9.4.43 */
load_info = *TLVP_VAL(&tp, RSL_IE_CBCH_LOAD_INFO);
if (load_info & 0x80)
is_overflow = true;
slot_count = load_info & 0x0F;
if (TLVP_PRES_LEN(&tp, RSL_IE_SMSCB_CHAN_INDICATOR, 1) &&
(*TLVP_VAL(&tp, RSL_IE_SMSCB_CHAN_INDICATOR) & 0x0F) == 0x01)
cbch_extended = true;
return bts_smscb_rx_cbch_load_ind(bts, cbch_extended, is_overflow, slot_count);
}
/* Ericsson specific: Immediate Assign Sent */
static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg)
{
@ -1537,7 +1568,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
break;
case RSL_MT_CBCH_LOAD_IND:
/* current load on the CBCH */
/* FIXME: handle this. Ignore for now */
rc = rsl_rx_cbch_load(msg);
break;
case RSL_MT_ERICSSON_IMM_ASS_SENT:
rc = rsl_rx_ericsson_imm_assign_sent(msg);

View File

@ -37,6 +37,9 @@
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <time.h>
#include <limits.h>
#include <stdbool.h>
@ -244,6 +247,11 @@ static struct gsm_network *bsc_network_init(void *ctx)
talloc_free(net);
return NULL;
}
net->bsc_data->cbc = talloc_zero(net->bsc_data, struct bsc_cbc_link);
if (!net->bsc_data->cbc) {
talloc_free(net);
return NULL;
}
/* Init back pointer */
net->bsc_data->auto_off_timeout = -1;
@ -272,6 +280,13 @@ static struct gsm_network *bsc_network_init(void *ctx)
osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net);
osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
net->bsc_data->cbc->net = net;
/* no cbc_hostname: client not started by default */
net->bsc_data->cbc->config.cbc_port = CBSP_TCP_PORT;
/* listen_port == -1: server not started by default */
net->bsc_data->cbc->config.listen_port = -1;
net->bsc_data->cbc->config.listen_hostname = talloc_strdup(net->bsc_data->cbc, "127.0.0.1");
return net;
}

View File

@ -70,6 +70,7 @@
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <inttypes.h>
@ -318,6 +319,14 @@ static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
vty_out(vty, " (%d)", count);
}
static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate)
{
vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-entry sched_arr, %u%% load%s",
bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages),
bts_smscb_chan_page_count(cstate), cstate->sched_arr_size,
bts_smscb_chan_load_percent(cstate), VTY_NEWLINE);
}
static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
{
unsigned int i;
@ -504,6 +513,9 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
dump_pchan_load_vty(vty, " ", &pl);
bts_dump_vty_cbch(vty, &bts->cbch_basic);
bts_dump_vty_cbch(vty, &bts->cbch_extended);
vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s",
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current,
bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current,
@ -5427,6 +5439,8 @@ int bsc_vty_init(struct gsm_network *network)
osmo_fsm_vty_add_cmds();
ho_vty_init();
cbc_vty_init();
smscb_vty_init();
bsc_vty_init_extra();

View File

@ -0,0 +1,287 @@
/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */
/*
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/core/stats.h>
#include <osmocom/core/select.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/abis_rsl.h>
/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */
static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size,
int base_idx, struct bts_smscb_message *smscb)
{
int arr_idx = base_idx;
int i;
OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
for (i = 0; i < smscb->num_pages; i++) {
while (sched_arr[arr_idx]) {
arr_idx++;
if (arr_idx >= sched_arr_size)
return -ENOSPC;
}
sched_arr[arr_idx] = &smscb->page[i];
}
return arr_idx;
}
/* add all pages of given smscb so they appear *before* (included) last_idx. */
static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size,
int last_idx, struct bts_smscb_message *smscb)
{
int arr_idx = last_idx;
int last_used_idx = 0;
int i;
OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
OSMO_ASSERT(smscb->num_pages >= 1);
for (i = smscb->num_pages - 1; i >= 0; i--) {
while (sched_arr[arr_idx]) {
arr_idx--;
if (arr_idx < 0)
return -ENOSPC;
}
sched_arr[arr_idx] = &smscb->page[i];
if (i == smscb->num_pages)
last_used_idx = i;
}
return last_used_idx;
}
/* obtain the least frequently scheduled SMSCB for given SMSCB channel */
static struct bts_smscb_message *
bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate)
{
if (llist_empty(&cstate->messages))
return NULL;
/* messages are expected to be ordered with increasing period, so we're
* able to return the last message in the list */
return llist_entry(cstate->messages.prev, struct bts_smscb_message, list);
}
/*! Generate per-BTS SMSCB scheduling array
* \param[in] cstate BTS CBCH channel state
* \param[out] arr_out return argument for allocated + generated scheduling array
* \return size of returned scheduling array arr_out in number of entries; negative on error */
int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out)
{
struct bts_smscb_message *smscb, *least_freq;
struct bts_smscb_page **arr;
int arr_size;
int rc;
/* start with one instance of the least frequent message at position 0, as we
* need to transmit it exactly once during the duration of the scheduling array */
least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate);
if (!least_freq) {
LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n");
*arr_out = NULL;
return 0;
}
arr_size = least_freq->input.rep_period;
arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size);
OSMO_ASSERT(arr);
rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq);
if (rc < 0) {
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
"very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq));
talloc_free(arr);
return rc;
}
/* continue filling with repetitions of the more frequent messages, starting from
* the most frequent message to the least frequent one, repeating them as needed
* throughout the duration of the array */
llist_for_each_entry(smscb, &cstate->messages, list) {
int last_page;
if (smscb == least_freq)
continue;
/* messages are expected to be ordered with increasing period, so we're
* starting with the most frequent / shortest period first */
rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb);
if (rc < 0) {
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
"SMSCB %s\n", bts_smscb_msg2str(smscb));
talloc_free(arr);
return rc;
}
last_page = rc;
while (last_page < cstate->sched_arr_size) {
/* store further instances in a way that the last block of the N+1th instance
* happens no later than "interval" after the last block of the Nth instance */
rc = bts_smscb_sched_add_before(arr, arr_size,
last_page + smscb->input.rep_period, smscb);
if (rc < 0) {
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further "
"SMSCB %s\n", bts_smscb_msg2str(smscb));
talloc_free(arr);
return rc;
}
last_page = rc;
}
}
*arr_out = arr;
return arr_size;
}
/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */
struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate)
{
struct bts_smscb_page *page;
/* if there are no messages to schedule, there is no array */
if (!cstate->sched_arr)
return NULL;
/* obtain the page from the scheduler array */
page = cstate->sched_arr[cstate->next_idx];
/* increment the index for the next call to this function */
cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size;
/* the array can have gaps in between where there is nothing scheduled */
if (!page)
return NULL;
return page;
}
/*! To be called after bts_smscb_pull_page() in order to update transmission count and
* check if SMSCB is complete.
* \param[in] cstate BTS CBC channel state
* \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which
* is no longer needed now */
void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page)
{
struct bts_smscb_message *smscb = page->msg;
/* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */
if (page->nr == smscb->num_pages) {
smscb->bcast_count++;
/* Check if the SMSCB transmission duration is now over */
if (smscb->bcast_count >= smscb->input.num_bcast_req)
bts_smscb_del(smscb, cstate, "COMPLETE");
}
}
/***********************************************************************
* BTS / RSL side
***********************************************************************/
static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
{
struct bts_smscb_page *page;
struct gsm_bts *bts = cstate->bts;
struct rsl_ie_cb_cmd_type cb_cmd;
bool is_extended = false;
if (cstate == &bts->cbch_extended)
is_extended = true;
if (cstate->overflow) {
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n",
cstate->overflow);
cstate->overflow--;
return;
}
page = bts_smscb_pull_page(cstate);
if (!page) {
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n");
return;
}
cb_cmd.spare = 0;
cb_cmd.def_bcast = 0;
cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
switch (page->num_blocks) {
case 1:
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
break;
case 2:
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
break;
case 3:
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
break;
case 4:
cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
break;
default:
osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks);
}
rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended,
page->data, sizeof(page->data));
bts_smscb_page_done(cstate, page);
}
static void bts_cbch_timer(void *data)
{
struct gsm_bts *bts = (struct gsm_bts *)data;
bts_cbch_send_one(&bts->cbch_basic);
bts_cbch_send_one(&bts->cbch_extended);
bts_cbch_timer_schedule(bts);
}
/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */
void bts_cbch_timer_schedule(struct gsm_bts *bts)
{
osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts);
osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
}
/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9
* \param[in] bts The BTS for which the load indication was received
* \param[in] cbch_extended Is this report for extended (true) or basic CBCH
* \param[in] is_overflow Is this report and overflow (true) or underflow report
* \param[in] slot_count amount of SMSCB messages needed / delay needed */
int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
uint8_t slot_count)
{
struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended);
int i;
if (!gsm_bts_get_cbch(bts))
return -ENODEV;
if (is_overflow) {
/* halt/delay transmission of further CBCH messages */
cstate->overflow = slot_count;
} else {
for (i = 0; i < slot_count; i++)
bts_cbch_send_one(cstate);
/* re-schedule the timer to count from now on */
bts_cbch_timer_schedule(bts);
}
return 0;
}

417
src/osmo-bsc/cbsp_link.c Normal file
View File

@ -0,0 +1,417 @@
/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */
/*
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/socket.h>
#include <osmocom/gsm/cbsp.h>
/* if a CBC IP/port has been configured, we continuously try to re-establish the TCP
* connection (as a client) to the CBC. If none has been configured, and we have a listen
* TCP port, we expect the CBC to connect to us. If neither of the two is configured,
* CBSP is effectively disabled */
/*********************************************************************************
* CBSP Server (inbound TCP connection from CBC)
*********************************************************************************/
static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
{
struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
//struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name);
talloc_free(cbc->server.sock_name);
cbc->server.sock_name = NULL;
cbc->server.srv = NULL;
return 0;
}
static int cbsp_srv_cb(struct osmo_stream_srv *conn)
{
struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct osmo_cbsp_decoded *decoded;
struct msgb *msg;
int rc;
/* READ */
rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg);
if (rc <= 0) {
if (rc == -EAGAIN || rc == -EINTR) {
/* more data needs to be read */
return 0;
} else if (rc == -EPIPE || rc == -ECONNRESET) {
/* lost connection */
} else if (rc == 0) {
/* connection closed */
}
osmo_stream_srv_destroy(conn);
cbc->server.srv = NULL;
return -EBADF;
}
OSMO_ASSERT(msg);
decoded = osmo_cbsp_decode(conn, msg);
if (decoded) {
LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
get_value_string(cbsp_msg_type_names, decoded->msg_type));
cbsp_rx_decoded(cbc, decoded);
talloc_free(decoded);
} else {
LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
msgb_hexdump(msg), osmo_cbsp_errstr);
}
msgb_free(msg);
return 0;
}
static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
{
struct bsc_cbc_link *cbc = osmo_stream_srv_link_get_data(link);
struct osmo_stream_srv *srv;
LOGP(DCBS, LOGL_INFO, "CBSP Server received inbound connection from CBC: %s\n",
osmo_sock_get_name2(fd));
if (cbc->server.srv) {
LOGP(DCBS, LOGL_NOTICE, "CBSP Server refusing further connection (%s) "
"while we already have another connection (%s)\n",
osmo_sock_get_name2(fd), cbc->server.sock_name);
return -1;
}
srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc);
if (!srv) {
LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n",
osmo_sock_get_name2(fd));
return -1;
}
cbc->server.srv = srv;
if (cbc->server.sock_name)
talloc_free(cbc->server.sock_name);
cbc->server.sock_name = osmo_sock_get_name(cbc, fd);
LOGP(DCBS, LOGL_NOTICE, "CBSP Server link established from CBC %s\n", cbc->server.sock_name);
/* TODO: introduce ourselves to the peer using some osmcoom extensions */
cbsp_tx_restart(cbc, false);
return 0;
}
/*********************************************************************************
* CBSP Client (outbound TCP connection to CBC)
*********************************************************************************/
static int cbsp_client_connect_cb(struct osmo_stream_cli *cli)
{
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
if (cbc->client.sock_name)
talloc_free(cbc->client.sock_name);
cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd);
LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name);
/* TODO: introduce ourselves to the peer using some osmcoom extensions */
cbsp_tx_restart(cbc, false);
return 0;
}
static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli)
{
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
LOGP(DCBS, LOGL_NOTICE, "CBSP Client lost connection to %s\n", cbc->client.sock_name);
talloc_free(cbc->client.sock_name);
cbc->client.sock_name = NULL;
return 0;
}
static int cbsp_client_read_cb(struct osmo_stream_cli *cli)
{
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
struct osmo_cbsp_decoded *decoded;
struct msgb *msg = NULL;
int rc;
/* READ */
rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg);
if (rc <= 0) {
if (rc == -EAGAIN || rc == -EINTR) {
/* more data needs to be read */
return 0;
} else if (rc == -EPIPE || rc == -ECONNRESET) {
/* lost connection */
} else if (rc == 0) {
/* connection closed */
}
osmo_stream_cli_reconnect(cli);
return -EBADF;
}
OSMO_ASSERT(msg);
decoded = osmo_cbsp_decode(cli, msg);
if (decoded) {
LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
get_value_string(cbsp_msg_type_names, decoded->msg_type));
cbsp_rx_decoded(cbc, decoded);
talloc_free(decoded);
} else {
LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
msgb_hexdump(msg), osmo_cbsp_errstr);
}
msgb_free(msg);
return 0;
}
int bsc_cbc_link_restart(void)
{
struct bsc_cbc_link *cbc = bsc_gsmnet->bsc_data->cbc;
/* shut down client, if no longer configured */
if (cbc->client.cli && !cbc->config.cbc_hostname) {
LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP client\n");
osmo_stream_cli_close(cbc->client.cli);
osmo_stream_cli_destroy(cbc->client.cli);
cbc->client.cli = NULL;
}
/* shut down server, if no longer configured */
if (cbc->config.listen_port == -1) {
if (cbc->server.srv || cbc->server.link)
LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP server\n");
if (cbc->server.srv) {
osmo_stream_srv_destroy(cbc->server.srv);
cbc->server.srv = NULL;
}
if (cbc->server.link) {
osmo_stream_srv_link_close(cbc->server.link);
osmo_stream_srv_link_destroy(cbc->server.link);
cbc->server.link = NULL;
}
}
/* start client, if configured */
if (cbc->config.cbc_hostname) {
LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Client (to CBC at %s:%u)\n",
cbc->config.cbc_hostname, cbc->config.cbc_port);
if (!cbc->client.cli) {
cbc->client.cli = osmo_stream_cli_create(cbc);
osmo_stream_cli_set_data(cbc->client.cli, cbc);
osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb);
osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb);
osmo_stream_cli_set_read_cb(cbc->client.cli, cbsp_client_read_cb);
}
/* CBC side */
osmo_stream_cli_set_addr(cbc->client.cli, cbc->config.cbc_hostname);
osmo_stream_cli_set_port(cbc->client.cli, cbc->config.cbc_port);
/* Close/Reconnect? */
osmo_stream_cli_open(cbc->client.cli);
}
/* start server, if configured */
if (cbc->config.listen_port != -1) {
LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Server (bound to %s:%u)\n",
cbc->config.listen_hostname, cbc->config.listen_port);
if (!cbc->server.srv) {
cbc->server.link = osmo_stream_srv_link_create(cbc);
osmo_stream_srv_link_set_data(cbc->server.link, cbc);
osmo_stream_srv_link_set_accept_cb(cbc->server.link, cbsp_srv_link_accept_cb);
}
osmo_stream_srv_link_set_addr(cbc->server.link, cbc->config.listen_hostname);
osmo_stream_srv_link_set_port(cbc->server.link, cbc->config.listen_port);
}
return 0;
}
/*! Encode + Transmit a 'decoded' CBSP message over given CBC link
* \param[in] cbc Data structure representing the BSCs link to the CBC
* \param[in] cbsp Decoded CBSP message to be transmitted. Ownership is transferred.
* \return 0 on success, negative otherwise */
int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp)
{
struct msgb *msg;
msg = osmo_cbsp_encode(cbc, cbsp);
if (!msg) {
LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n",
get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr);
talloc_free(cbsp);
return -1;
}
if (cbc->client.cli)
osmo_stream_cli_send(cbc->client.cli, msg);
else if (cbc->server.srv)
osmo_stream_srv_send(cbc->server.srv, msg);
else {
LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg));
msgb_free(msg);
}
talloc_free(cbsp);
return 0;
}
static struct bsc_cbc_link *vty_cbc_data(struct vty *vty)
{
return bsc_gsmnet->bsc_data->cbc;
}
/*********************************************************************************
* VTY Interface (Configuration + Introspection)
*********************************************************************************/
DEFUN(cfg_cbc, cfg_cbc_cmd,
"cbc", "Configure CBSP Link to Cell Broadcast Centre\n")
{
vty->node = CBC_NODE;
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd,
"remote-ip A.B.C.D",
"IP Address of the Cell Broadcast Centre\n"
"IP Address of the Cell Broadcast Centre\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
osmo_talloc_replace_string(cbc, &cbc->config.cbc_hostname, argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd,
"no remote-ip",
NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
talloc_free(cbc->config.cbc_hostname);
cbc->config.cbc_hostname = NULL;
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd,
"remote-port <1-65535>",
"TCP Port number of the Cell Broadcast Centre (Default: 48049)\n"
"TCP Port number of the Cell Broadcast Centre (Default: 48049)\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
cbc->config.cbc_port = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd,
"listen-port <1-65535>",
"Local TCP port at which BSC listens for incoming CBSP connections from CBC\n"
"Local TCP port at which BSC listens for incoming CBSP connections from CBC\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
cbc->config.listen_port = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd,
"no listen-port",
NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
cbc->config.listen_port = -1;
return CMD_SUCCESS;
}
DEFUN(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd,
"listen-ip A.B.C.D",
"Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)\n"
"Local IP Address where BSC listens for incoming CBC connections\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
osmo_talloc_replace_string(cbc, &cbc->config.listen_hostname, argv[0]);
return CMD_SUCCESS;
}
static struct cmd_node cbc_node = {
CBC_NODE,
"%s(config-cbc)# ",
1,
};
static int config_write_cbc(struct vty *vty)
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
vty_out(vty, "cbc%s", VTY_NEWLINE);
if (cbc->config.cbc_hostname) {
vty_out(vty, " remote-ip %s%s", cbc->config.cbc_hostname, VTY_NEWLINE);
vty_out(vty, " remote-port %u%s", cbc->config.cbc_port, VTY_NEWLINE);
} else
vty_out(vty, " no remote-ip%s", VTY_NEWLINE);
if (cbc->config.listen_port >= 0) {
vty_out(vty, " listen-port %u%s", cbc->config.listen_port, VTY_NEWLINE);
vty_out(vty, " listen-ip %s%s", cbc->config.listen_hostname, VTY_NEWLINE);
} else
vty_out(vty, " no listen-port%s", VTY_NEWLINE);
return 0;
}
DEFUN(show_cbc, show_cbc_cmd,
"show cbc",
SHOW_STR "Display state of CBC / CBSP\n")
{
struct bsc_cbc_link *cbc = vty_cbc_data(vty);
if (!cbc->config.cbc_hostname)
vty_out(vty, "CBSP Client Config: Disabled%s", VTY_NEWLINE);
else {
vty_out(vty, "CBSP Client Config: CBC IP=%s, CBC Port=%u%s",
cbc->config.cbc_hostname, cbc->config.cbc_port, VTY_NEWLINE);
vty_out(vty, "CBSP Client Connection: %s%s",
cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE);
}
if (cbc->config.listen_port < 0)
vty_out(vty, "CBSP Server Config: Disabled%s\n", VTY_NEWLINE);
else {
vty_out(vty, "CBSP Server Config: Listen IP=%s, Port=%u%s\n",
cbc->config.listen_hostname, cbc->config.listen_port, VTY_NEWLINE);
vty_out(vty, "CBSP Server Connection: %s%s",
cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE);
}
return CMD_SUCCESS;
}
void cbc_vty_init(void)
{
install_element(VIEW_NODE, &show_cbc_cmd);
install_element(CONFIG_NODE, &cfg_cbc_cmd);
install_node(&cbc_node, config_write_cbc);
install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd);
install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd);
install_element(CBC_NODE, &cfg_cbc_remote_port_cmd);
install_element(CBC_NODE, &cfg_cbc_listen_port_cmd);
install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd);
install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd);
}

View File

@ -767,6 +767,12 @@ static const struct gprs_rlc_cfg rlc_cfg_default = {
.initial_mcs = 6,
};
static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts)
{
cstate->bts = bts;
INIT_LLIST_HEAD(&cstate->messages);
}
/* Initialize those parts that don't require osmo-bsc specific dependencies.
* This part is shared among the thin programs in osmo-bsc/src/utils/.
* osmo-bsc requires further initialization that pulls in more dependencies (see
@ -945,6 +951,9 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
}
bts->mr_half.num_modes = 3;
bts_init_cbch_state(&bts->cbch_basic, bts);
bts_init_cbch_state(&bts->cbch_extended, bts);
return bts;
}

View File

@ -37,6 +37,7 @@
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
@ -310,6 +311,10 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx)
OSMO_ASSERT(ts->fi);
osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
}
/* Start CBCH transmit timer if CBCH is present */
if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts))
bts_cbch_timer_schedule(trx->bts);
}
static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
@ -379,6 +384,8 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
acc_ramp_abort(&trx->bts->acc_ramp);
all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
if (trx->nr == 0)
osmo_timer_del(&trx->bts->cbch_timer);
}
gsm_bts_mo_reset(trx->bts);
@ -764,6 +771,11 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.description = "Local Call, Local Switch",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DCBS] = {
.name = "DCBS",
.description = "Cell Broadcast System",
.enabled = 1, .loglevel = LOGL_NOTICE,
}
};
@ -912,6 +924,7 @@ int main(int argc, char **argv)
handover_decision_1_init();
hodec2_init(bsc_gsmnet);
bsc_cbc_link_restart();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);

823
src/osmo-bsc/smscb.c Normal file
View File

@ -0,0 +1,823 @@
/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */
/*
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <limits.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/select.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gsm/cbsp.h>
#include <osmocom/gsm/protocol/gsm_23_041.h>
#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <osmocom/netif/stream.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/vty.h>
/*********************************************************************************
* Helper Functions
*********************************************************************************/
/* replace the old head of an entire list with a new head; effectively moves the entire
* list from old to new head */
static void llist_replace_head(struct llist_head *new, struct llist_head *old)
{
if (llist_empty(old))
INIT_LLIST_HEAD(new);
else
__llist_add(new, old->prev, old->next);
INIT_LLIST_HEAD(old);
}
/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */
struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended)
{
struct bts_smscb_chan_state *chan_state;
if (extended)
chan_state = &bts->cbch_extended;
else
chan_state = &bts->cbch_basic;
return chan_state;
}
/* do an ordered list insertion. we keep the list with increasing period, i.e. the most
* frequent message first */
static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new)
{
struct bts_smscb_message *tmp, *tmp2;
if (llist_empty(&cstate->messages)) {
llist_add(&new->list, &cstate->messages);
return;
}
llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) {
if (tmp->input.rep_period > new->input.rep_period) {
/* we found the first message with longer period than the new message,
* we must insert ourselves before that one */
__llist_add(&new->list, tmp->list.prev, &tmp->list);
return;
}
}
}
/* stringify a SMSCB for logging */
const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb)
{
static char buf[128];
snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u",
smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages,
smscb->input.rep_period, smscb->input.num_bcast_req);
return buf;
}
const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate)
{
if (cstate == &cstate->bts->cbch_basic)
return "BASIC";
else if (cstate == &cstate->bts->cbch_extended)
return "EXTENDED";
else
return "UNKNOWN";
}
unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate)
{
unsigned int sched_arr_used = 0;
unsigned int i;
if (cstate->sched_arr_size == 0)
return 0;
/* count the number of used slots */
for (i = 0; i < cstate->sched_arr_size; i++) {
if (cstate->sched_arr[i])
sched_arr_used++;
}
OSMO_ASSERT(sched_arr_used <= UINT_MAX/100);
return (sched_arr_used * 100) / cstate->sched_arr_size;
}
unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate)
{
struct bts_smscb_message *smscb;
unsigned int page_count = 0;
llist_for_each_entry(smscb, &cstate->messages, list)
page_count += smscb->num_pages;
return page_count;
}
/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */
static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts)
{
static struct osmo_cell_global_id cgi;
cgi.lai.plmn = bts->network->plmn;
cgi.lai.lac = bts->location_area_code;
cgi.cell_identity = bts->cell_identity;
return &cgi;
}
/* represents the various lists that the BSC can create as part of a response */
struct response_state {
struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */
struct llist_head fail; /* osmo_cbsp_fail_ent */
struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */
struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */
};
/*! per-BTS callback function used by cbsp_per_bts().
* \param[in] bts BTS currently being processed
* \param[in] dec decoded CBSP message currently being processed
* \param r_state response state accumulating cell lists (success/failure/...)
* \param priv opaque private data provided by caller of cbsp_per_bts()
* \returns 0 on success; negative TS 48.049 cause value on error */
typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
struct response_state *r_state, void *priv);
/* append a success for given cell to response state */
static void append_success(struct response_state *r_state, struct gsm_bts *bts)
{
struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent);
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n");
OSMO_ASSERT(cent);
cent->cell_id.global = *cgi;
llist_add_tail(&cent->list, &r_state->success.list);
}
/* append a failure for given cell to response state */
static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause)
{
struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause);
OSMO_ASSERT(fent);
fent->id_discr = CELL_IDENT_WHOLE_GLOBAL;
fent->cell_id.global = *cgi;
fent->cause = cause;
llist_add_tail(&fent->list, &r_state->fail);
}
/* append a 'number of broadcasts completed' for given cell to response state */
static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts,
struct bts_smscb_message *smscb)
{
struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent);
struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count);
OSMO_ASSERT(cent);
r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL;
cent->cell_id.global = *cgi;
if (smscb->bcast_count > INT16_MAX) {
cent->num_compl = INT16_MAX;
cent->num_bcast_info = 0x01; /* Overflow */
} else {
cent->num_compl = smscb->bcast_count;
cent->num_bcast_info = 0x00;
}
llist_add_tail(&cent->list, &r_state->num_completed.list);
}
/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result
* to succeeded/failed lists.
* \param[in] net GSM network in which we operate
* \param[in] caller-allocated Response state structure collecting results
* \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on
* \param[in] cb_fn Call-back function to call for each matching BTS
* \param[in] priv Opqaue private data; passed to cb_fn
* */
static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state,
const struct osmo_cbsp_cell_list *cell_list,
bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv)
{
struct osmo_cbsp_cell_ent *ent;
struct gsm_bts *bts;
uint8_t bts_status[net->num_bts];
int rc, ret = 0;
memset(bts_status, 0, sizeof(bts_status));
INIT_LLIST_HEAD(&r_state->success.list);
INIT_LLIST_HEAD(&r_state->fail);
INIT_LLIST_HEAD(&r_state->num_completed.list);
INIT_LLIST_HEAD(&r_state->loading.list);
/* special case as cell_list->list is empty in this case */
if (cell_list->id_discr == CELL_IDENT_BSS) {
llist_for_each_entry(bts, &net->bts_list, list) {
bts_status[bts->nr] = 1;
/* call function on this BTS */
rc = cb_fn(bts, dec, r_state, priv);
if (rc < 0) {
append_fail(r_state, bts, -rc);
ret = -1;
} else
append_success(r_state, bts);
}
} else {
/* normal case: iterate over cell list */
llist_for_each_entry(ent, &cell_list->list, list) {
bool found_at_least_one = false;
/* find all matching BTSs for this entry */
llist_for_each_entry(bts, &net->bts_list, list) {
struct gsm0808_cell_id cell_id = {
.id_discr = cell_list->id_discr,
.id = ent->cell_id
};
if (!gsm_bts_matches_cell_id(bts, &cell_id))
continue;
found_at_least_one = true;
/* skip any BTSs which we've already processed */
if (bts_status[bts->nr])
continue;
bts_status[bts->nr] = 1;
/* call function on this BTS */
rc = cb_fn(bts, dec, r_state, priv);
if (rc < 0) {
append_fail(r_state, bts, -rc);
ret = -1;
} else
append_success(r_state, bts);
}
if (!found_at_least_one) {
struct osmo_cbsp_fail_ent *fent;
LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n");
fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
OSMO_ASSERT(fent);
fent->id_discr = cell_list->id_discr;
fent->cell_id = ent->cell_id;
llist_add_tail(&fent->list, &r_state->fail);
ret = -1;
}
}
}
return ret;
}
/*! Find an existing SMSCB message within given BTS.
* \param[in] chan_state BTS CBCH channel state
* \param[in] msg_id Message Id of to-be-found message
* \param[in] serial_nr Serial Number of to-be-found message
* \returns SMSCB message if found; NULL otherwise */
struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state,
uint16_t msg_id, uint16_t serial_nr)
{
struct bts_smscb_message *smscb;
llist_for_each_entry(smscb, &chan_state->messages, list) {
if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr)
return smscb;
}
return NULL;
}
/*! create a new SMSCB message for specified BTS; don't link it yet.
* \param[in] bts BTS for which the SMSCB is to be allocated
* \param[in] wrepl CBSP write-replace message
* \returns callee-allocated SMSCB message filled with data from wrepl */
static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
const struct osmo_cbsp_write_replace *wrepl)
{
struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message);
struct osmo_cbsp_content *cont;
int i;
if (!smscb)
return NULL;
OSMO_ASSERT(wrepl->is_cbs);
/* initialize all pages inside the message */
for (i = 0; i < ARRAY_SIZE(smscb->page); i++) {
struct bts_smscb_page *page = &smscb->page[i];
page->nr = i+1; /* page numbers are 1-based */
page->msg = smscb;
}
/* initialize "header" part */
smscb->input.msg_id = wrepl->msg_id;
smscb->input.serial_nr = wrepl->new_serial_nr;
smscb->input.category = wrepl->u.cbs.category;
smscb->input.rep_period = wrepl->u.cbs.rep_period;
smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req;
smscb->input.dcs = wrepl->u.cbs.dcs;
smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content);
if (smscb->num_pages > ARRAY_SIZE(smscb->page)) {
LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n",
smscb->num_pages, ARRAY_SIZE(smscb->page));
talloc_free(smscb);
return NULL;
}
i = 0;
llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) {
struct gsm23041_msg_param_gsm *msg_param;
struct bts_smscb_page *page;
size_t bytes_used;
/* we have just ensured a few lines above that this cannot overflow */
page = &smscb->page[i++];
msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0];
/* build 6 byte header according to TS 23.041 9.4.1.2 */
osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr);
osmo_store16be(wrepl->msg_id, &msg_param->message_id);
msg_param->dcs = wrepl->u.cbs.dcs;
msg_param->page_param.num_pages = smscb->num_pages;
msg_param->page_param.page_nr = page->nr;
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
memcpy(&msg_param->content, cont->data, cont->user_len);
bytes_used = sizeof(*msg_param) + cont->user_len;
/* compute number of valid blocks in page */
page->num_blocks = bytes_used / 22;
if (bytes_used % 22)
page->num_blocks += 1;
}
return smscb;
}
/*! remove a SMSCB message */
void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
const char *reason)
{
struct bts_smscb_page **arr;
int rc;
LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n",
bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason);
llist_del(&smscb->list);
/* we must recompute the scheduler array here, as the old one will have pointers
* to the pages of the just-to-be-deleted message */
rc = bts_smscb_gen_sched_arr(cstate, &arr);
if (rc < 0) {
LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after "
"removing message %s. WTF?\n", bts_smscb_msg2str(smscb));
/* we cannot free the message now, to ensure the page pointers in the old
* array are still valid. let's re-add it to keep things sane */
__bts_smscb_add(cstate, smscb);
} else {
/* success */
talloc_free(smscb);
/* replace array with new one */
talloc_free(cstate->sched_arr);
cstate->sched_arr = arr;
cstate->sched_arr_size = rc;
cstate->next_idx = 0;
}
}
/*********************************************************************************
* Transmit of CBSP to CBC
*********************************************************************************/
/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */
int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
{
struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
if (is_emerg)
cbsp->u.restart.bcast_msg_type = 0x01;
cbsp->u.restart.recovery_ind = 0x01; /* message data lost */
cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS;
return cbsp_tx_decoded(cbc, cbsp);
}
/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */
static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
{
struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL);
return cbsp_tx_decoded(cbc, cbsp);
}
/*********************************************************************************
* Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts()
*********************************************************************************/
/*! Try to execute a write-replace operation; roll-back if it fails.
* \param[in] chan_state BTS CBCH channel state
* \param[in] extended_cbch Basic (false) or Extended (true) CBCH
* \param[in] new_msg New SMSCB message which should be added
* \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL
* \return 0 on success; negative on error */
static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state,
struct bts_smscb_message *new_msg,
struct bts_smscb_message *exclude_msg,
struct response_state *r_state)
{
struct bts_smscb_page **arr;
int rc;
if (exclude_msg) {
/* temporarily remove from list of SMSCB */
llist_del(&exclude_msg->list);
}
/* temporarily add new_msg to list of SMSCB */
__bts_smscb_add(chan_state, new_msg);
/* attempt to create scheduling array */
rc = bts_smscb_gen_sched_arr(chan_state, &arr);
if (rc < 0) {
/* it didn't work out; we couldn't schedule it */
/* remove the new message again */
llist_del(&new_msg->list);
/* up to the caller to free() it */
if (exclude_msg) {
/* re-add the temporarily removed message */
__bts_smscb_add(chan_state, new_msg);
}
return -1;
}
/* success! */
if (exclude_msg) {
LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, "
"pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n",
bts_smscb_chan_state_name(chan_state),
new_msg->input.msg_id, new_msg->input.serial_nr,
exclude_msg->num_pages, new_msg->num_pages,
exclude_msg->input.rep_period, new_msg->input.rep_period,
exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req);
append_bcast_compl(r_state, chan_state->bts, exclude_msg);
talloc_free(exclude_msg);
} else
LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n",
bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg));
/* replace array with new one */
talloc_free(chan_state->sched_arr);
chan_state->sched_arr = arr;
chan_state->sched_arr_size = rc;
chan_state->next_idx = 0;
return 0;
}
static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
struct response_state *r_state, void *priv)
{
const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
bool extended_cbch = wrepl->u.cbs.channel_ind;
struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch);
struct bts_smscb_message *smscb;
int rc;
if (!wrepl->is_cbs) {
LOG_BTS(bts, DCBS, LOGL_ERROR, "(Primary) Emergency Message not supported\n");
return -CBSP_CAUSE_CB_NOT_SUPPORTED;
}
/* check if cell has a CBCH at all */
if (!gsm_bts_get_cbch(bts))
return -CBSP_CAUSE_CB_NOT_SUPPORTED;
/* check for duplicate */
if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr))
return -CBSP_CAUSE_MSG_REF_ALREADY_USED;
if (!wrepl->old_serial_nr) { /* new message */
/* create new message */
smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
if (!smscb)
return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
/* check if scheduling permits this additional message */
rc = bts_try_write_replace(chan_state, smscb, NULL, r_state);
if (rc < 0) {
talloc_free(smscb);
return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
}
} else { /* modify / replace existing message */
struct bts_smscb_message *smscb_old;
/* find existing message */
smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr);
if (!smscb_old)
return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
/* create new message */
smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
if (!smscb)
return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
/* check if scheduling permits this modified message */
rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state);
if (rc < 0) {
talloc_free(smscb);
return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
}
}
return 0;
}
static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
struct response_state *r_state, void *priv)
{
const struct osmo_cbsp_kill *kill = &dec->u.kill;
struct bts_smscb_chan_state *chan_state;
struct bts_smscb_message *smscb;
bool extended = false;
if (kill->channel_ind && *kill->channel_ind == 0x01)
extended = true;
chan_state = bts_get_smscb_chan(bts, extended);
/* Find message by msg_id + old_serial_nr */
smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr);
if (!smscb)
return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
/* Remove it */
bts_smscb_del(smscb, chan_state, "KILL");
return 0;
}
static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
struct response_state *r_state, void *priv)
{
struct bts_smscb_chan_state *chan_state;
struct bts_smscb_message *smscb, *smscb2;
/* remove all SMSCB from CBCH BASIC this BTS */
chan_state = bts_get_smscb_chan(bts, false);
llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
bts_smscb_del(smscb, chan_state, "RESET");
/* remove all SMSCB from CBCH EXTENDED this BTS */
chan_state = bts_get_smscb_chan(bts, true);
llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
bts_smscb_del(smscb, chan_state, "RESET");
return 0;
}
/*********************************************************************************
* Receive of CBSP from CBC
*********************************************************************************/
static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
{
const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
struct gsm_network *net = cbc->net;
struct response_state *r_state = talloc_zero(cbc, struct response_state);
struct osmo_cbsp_decoded *resp;
enum cbsp_channel_ind channel_ind;
int rc;
LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY");
rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list,
bts_rx_write_replace, dec, NULL);
/* generate response */
if (rc < 0) {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL);
struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail;
fail->msg_id = wrepl->msg_id;
fail->new_serial_nr = wrepl->new_serial_nr;
fail->old_serial_nr = wrepl->old_serial_nr;
llist_replace_head(&fail->fail_list, &r_state->fail);
fail->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
if (wrepl->is_cbs) {
channel_ind = wrepl->u.cbs.channel_ind;
fail->channel_ind = &channel_ind;
}
if (wrepl->old_serial_nr) {
fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
}
} else {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL);
struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl;
compl->msg_id = wrepl->msg_id;
compl->new_serial_nr = wrepl->new_serial_nr;
compl->old_serial_nr = wrepl->old_serial_nr;
compl->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
if (wrepl->is_cbs) {
channel_ind = wrepl->u.cbs.channel_ind;
compl->channel_ind = &channel_ind;
}
if (wrepl->old_serial_nr) {
compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
}
}
cbsp_tx_decoded(cbc, resp);
talloc_free(r_state);
return rc;
}
static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
{
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n");
/* FIXME: repetition period */
return tx_cbsp_keepalive_compl(cbc);
}
static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
{
const struct osmo_cbsp_kill *kill = &dec->u.kill;
struct gsm_network *net = cbc->net;
struct response_state *r_state = talloc_zero(cbc, struct response_state);
struct osmo_cbsp_decoded *resp;
int rc;
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n");
rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL);
if (rc < 0) {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL);
struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail;
fail->msg_id = kill->msg_id;
fail->old_serial_nr = kill->old_serial_nr;
fail->channel_ind = kill->channel_ind;
llist_replace_head(&fail->fail_list, &r_state->fail);
fail->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
} else {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL);
struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl;
compl->msg_id = kill->msg_id;
compl->old_serial_nr = kill->old_serial_nr;
compl->channel_ind = kill->channel_ind;
compl->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
}
cbsp_tx_decoded(cbc, resp);
talloc_free(r_state);
return rc;
}
static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
{
struct gsm_network *net = cbc->net;
struct response_state *r_state = talloc_zero(cbc, struct response_state);
struct osmo_cbsp_decoded *resp;
int rc;
LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n");
rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL);
if (rc < 0) {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL);
struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail;
llist_replace_head(&fail->fail_list, &r_state->fail);
fail->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&fail->cell_list.list, &r_state->success.list);
} else {
resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL);
struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl;
if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) {
/* replace the list of individual cell identities with CELL_IDENT_BSS */
compl->cell_list.id_discr = CELL_IDENT_BSS;
/* no need to free success_list entries, hierarchical talloc works */
} else {
compl->cell_list.id_discr = r_state->success.id_discr;
llist_replace_head(&compl->cell_list.list, &r_state->success.list);
}
}
cbsp_tx_decoded(cbc, resp);
talloc_free(r_state);
return rc;
}
/*! process an incoming, already decoded CBSP message from the CBC.
* \param[in] cbc link to the CBC
* \param[in] dec decoded CBSP message structure. Ownership not transferred.
* \returns 0 on success; negative on error. */
int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
{
int rc = -1;
switch (dec->msg_type) {
case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */
rc = cbsp_rx_write_replace(cbc, dec);
break;
case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */
rc = cbsp_rx_keep_alive(cbc, dec);
break;
case CBSP_MSGT_KILL: /* remove message */
rc = cbsp_rx_kill(cbc, dec);
break;
case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
rc = cbsp_rx_reset(cbc, dec);
break;
case CBSP_MSGT_LOAD_QUERY:
case CBSP_MSGT_MSG_STATUS_QUERY:
case CBSP_MSGT_SET_DRX:
LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s",
get_value_string(cbsp_msg_type_names, dec->msg_type));
/* we should implement those eventually */
break;
default:
LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s",
get_value_string(cbsp_msg_type_names, dec->msg_type));
break;
}
return rc;
}
/*********************************************************************************
* VTY Interface (Introspection)
*********************************************************************************/
static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
{
const struct bts_smscb_message *sm;
vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE);
vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE);
llist_for_each_entry(sm, &cs->messages, list) {
vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s",
sm->input.msg_id, sm->input.serial_nr, sm->num_pages,
get_value_string(cbsp_category_names, sm->input.category),
sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req,
sm->input.dcs, VTY_NEWLINE);
}
vty_out(vty, "%s", VTY_NEWLINE);
}
DEFUN(bts_show_cbs, bts_show_cbs_cmd,
"show bts <0-255> smscb [(basic|extended)]",
SHOW_STR "Display information about a BTS\n" "BTS number\n"
"SMS Cell Broadcast State\n"
"Show only information related to CBCH BASIC\n"
"Show only information related to CBCH EXTENDED\n")
{
struct gsm_network *net = gsmnet_from_vty(vty);
int bts_nr = atoi(argv[0]);
struct gsm_bts *bts;
if (bts_nr >= net->num_bts) {
vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
bts = gsm_bts_num(net, bts_nr);
if (argc < 2 || !strcmp(argv[1], "basic"))
vty_dump_smscb_chan_state(vty, &bts->cbch_basic);
if (argc < 2 || !strcmp(argv[1], "extended"))
vty_dump_smscb_chan_state(vty, &bts->cbch_extended);
return CMD_SUCCESS;
}
void smscb_vty_init(void)
{
install_element_ve(&bts_show_cbs_cmd);
}

View File

@ -11,6 +11,7 @@ AM_CFLAGS = \
$(LIBOSMOCTRL_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(LIBOSMONETIF_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
$(LIBOSMOMGCPCLIENT_CFLAGS) \
$(NULL)
@ -91,11 +92,15 @@ handover_test_LDADD = \
$(top_builddir)/src/osmo-bsc/rest_octets.o \
$(top_builddir)/src/osmo-bsc/system_information.o \
$(top_builddir)/src/osmo-bsc/timeslot_fsm.o \
$(top_builddir)/src/osmo-bsc/smscb.o \
$(top_builddir)/src/osmo-bsc/cbch_scheduler.o \
$(top_builddir)/src/osmo-bsc/cbsp_link.o \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOCTRL_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOABIS_LIBS) \
$(LIBOSMONETIF_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(NULL)