libosmocore/src/gb/gprs_bssgp.c

1440 lines
43 KiB
C
Raw Normal View History

/*! \file gprs_bssgp.c
* GPRS BSSGP protocol implementation as per 3GPP TS 08.18. */
/*
* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
*
* 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, see <http://www.gnu.org/licenses/>.
*
* TODO:
* o properly count incoming BVC-RESET packets in counter group
* o set log context as early as possible for outgoing packets
*/
#include <errno.h>
#include <stdint.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/byteswap.h>
#include <osmocom/core/bit16gen.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stats.h>
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
#include "osmocom/gsm/gsm48.h"
#include "gprs_bssgp_internal.h"
void *bssgp_tall_ctx = NULL;
static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg);
bssgp_bvc_send bssgp_ns_send = _gprs_ns_sendmsg;
void *bssgp_ns_send_data = NULL;
static const struct rate_ctr_desc bssgp_ctr_description[] = {
{ "packets:in", "Packets at BSSGP Level ( In)" },
{ "packets:out","Packets at BSSGP Level (Out)" },
{ "bytes:in", "Bytes at BSSGP Level ( In)" },
{ "bytes:out", "Bytes at BSSGP Level (Out)" },
{ "blocked", "BVC Blocking count" },
{ "discarded", "BVC LLC Discarded count" },
{ "status", "BVC Status count" },
};
static const struct rate_ctr_group_desc bssgp_ctrg_desc = {
.group_name_prefix = "bssgp:bss_ctx",
.group_description = "BSSGP Peer Statistics",
.num_ctr = ARRAY_SIZE(bssgp_ctr_description),
.ctr_desc = bssgp_ctr_description,
.class_id = OSMO_STATS_CLASS_PEER,
};
LLIST_HEAD(bssgp_bvc_ctxts);
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv);
/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg)
{
OSMO_ASSERT(bssgp_nsi);
return gprs_ns_sendmsg(bssgp_nsi, msg);
}
/* Find a BTS Context based on parsed RA ID and Cell ID */
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
{
struct bssgp_bvc_ctx *bctx;
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) &&
bctx->cell_id == cid)
return bctx;
}
return NULL;
}
/* Transmit a BVC-RESET or BVC-RESET-ACK with a given nsei and bvci (Chapter 10.4.12)
* \param[in] pdu Either BSSGP_PDUT_BVC_RESET or BSSGP_PDUT_BVC_RESET_ACK
* \param[in] nsei The NSEI to transmit over
* \param[in] bvci BVCI of the BVC to reset
* \param[in] cause The cause of the reset only valid for BSSGP_PDUT_BVC_RESET.
* \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
* \param[in] cell_id The cell_id to include (if ra_id is not NULL)
* returns >= 0 on success, on error < 0.
*/
static int tx_bvc_reset_nsei_bvci(enum bssgp_pdu_type pdu, uint16_t nsei, uint16_t bvci,
enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bvci);
OSMO_ASSERT(pdu == BSSGP_PDUT_BVC_RESET || pdu == BSSGP_PDUT_BVC_RESET_ACK);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = BVCI_SIGNALLING;
bgph->pdu_type = pdu;
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
if (pdu == BSSGP_PDUT_BVC_RESET) {
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
"CAUSE=%s\n", bvci, bssgp_cause_str(cause));
} else {
LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET-ACK\n", bvci);
}
if (ra_id) {
uint8_t bssgp_cid[8];
bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
}
/* Optional: Feature Bitmap */
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12)
* \param[in] nsei The NSEI to transmit over
* \param[in] bvci BVCI of the BVC to reset
* \param[in] cause The cause of the reset
* \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
* \param[in] cell_id The cell_id to include (if ra_id is not NULL)
* returns >= 0 on success, on error < 0.
*/
int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
{
return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET, nsei, bvci, cause, ra_id, cell_id);
}
/*! Transmit a BVC-RESET-ACK message with a given nsei and bvci (Chapter 10.4.12)
* \param[in] nsei The NSEI to transmit over
* \param[in] bvci BVCI of the BVC to reset
* \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
* \param[in] cell_id The cell_id to include (if ra_id is not NULL)
* returns >= 0 on success, on error < 0.
*/
int bssgp_tx_bvc_reset_ack_nsei_bvci(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
{
return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, bvci, 0, ra_id, cell_id);
}
/*! Initiate reset procedure for all PTP BVC on a given NSEI.
*
* This function initiates reset procedure for all PTP BVC with a given cause.
* \param[in] nsei NSEI to which PTP BVC should belong to
* \param[in] cause Cause of BVC RESET
* \returns 0 on success, negative error code otherwise
*/
int bssgp_tx_bvc_ptp_reset(uint16_t nsei, enum gprs_bssgp_cause cause)
{
int rc;
struct bssgp_bvc_ctx *bctx;
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->nsei == nsei && bctx->bvci != BVCI_SIGNALLING) {
LOGP(DLBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
nsei, bctx->bvci, bssgp_cause_str(cause));
rc = bssgp_tx_bvc_reset(bctx, bctx->bvci, cause);
if (rc < 0)
return rc;
}
}
return 0;
}
/* Find a BTS context based on BVCI+NSEI tuple */
struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
{
struct bssgp_bvc_ctx *bctx;
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->nsei == nsei && bctx->bvci == bvci)
return bctx;
}
return NULL;
}
void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data)
{
bssgp_ns_send = ns_send;
bssgp_ns_send_data = data;
}
struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
{
struct bssgp_bvc_ctx *ctx;
ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bvc_ctx);
if (!ctx)
return NULL;
ctx->bvci = bvci;
ctx->nsei = nsei;
ctx->is_sgsn = true;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
if (!ctx->ctrg)
goto err_ctrg;
ctx->fc = talloc_zero(ctx, struct bssgp_flow_control);
if (!ctx->fc)
goto err_fc;
/* cofigure for 2Mbit, 30 packets in queue */
bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud);
llist_add(&ctx->list, &bssgp_bvc_ctxts);
return ctx;
err_fc:
rate_ctr_group_free(ctx->ctrg);
err_ctrg:
talloc_free(ctx);
return NULL;
}
void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
{
if (!ctx)
return;
osmo_timer_del(&ctx->fc->timer);
rate_ctr_group_free(ctx->ctrg);
llist_del(&ctx->list);
talloc_free(ctx);
}
/* Chapter 10.4.5: Flow Control BVC ACK */
static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = ns_bvci;
bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.7 SUSPEND-ACK PDU */
int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli,
const struct gprs_ra_id *ra_id, uint8_t suspend_ref)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
bgph->pdu_type = BSSGP_PDUT_SUSPEND_ACK;
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.8 SUSPEND-NACK PDU */
int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli,
const struct gprs_ra_id *ra_id,
uint8_t *cause)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK;
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.10 RESUME-ACK PDU */
int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli,
const struct gprs_ra_id *ra_id)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
bgph->pdu_type = BSSGP_PDUT_RESUME_ACK;
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.11 RESUME-NACK PDU */
int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli,
const struct gprs_ra_id *ra_id, uint8_t *cause)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK;
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
{
/* 6 octets RAC */
gsm48_parse_ra(raid, buf);
/* 2 octets CID */
return osmo_load16be(buf+6);
}
int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid,
uint16_t cid)
{
/* 6 octets RAC */
gsm48_encode_ra((struct gsm48_ra_id *)buf, raid);
/* 2 octets CID */
osmo_store16be(cid, buf+6);
return 8;
}
/* Chapter 8.4 BVC-Reset Procedure */
static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t ns_bvci)
{
struct osmo_bssgp_prim nmp;
struct bssgp_bvc_ctx *bctx;
uint16_t nsei = msgb_nsei(msg);
uint16_t bvci;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
/* look-up or create the BTS context for this BVC */
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!bctx)
bctx = btsctx_alloc(bvci, nsei);
/* As opposed to NS-VCs, BVCs are NOT blocked after RESET */
bctx->state &= ~BVC_S_BLOCKED;
/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
* informs us about its RAC + Cell ID, so we can create a mapping */
if (bctx->is_sgsn && bvci != BVCI_SIGNALLING && bvci != BVCI_PTM) {
if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
"missing mandatory IE\n", bvci);
return -EINVAL;
}
/* actually extract RAC / CID */
bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
TLVP_VAL(tp, BSSGP_IE_CELL_ID));
LOGP(DLBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
implement support for 3-digit MNC with leading zeros Enable representing three-digit MNC with leading zeros. The MNCs 23 and 023 are actually different; so far we treated both as 23. Re-encode an incoming BCD or string of 023 as it were, i.e. not dropping the leading zero as 23. Break ABI compatibility by changing the size and ordering of structs gprs_ra_id, osmo_plmn_id, osmo_cell_global_id, ... by adding an mnc_3_digits flag. Change ordering in gprs_ra_id because the canonical oder is {Mobile Country Code, Mobile Network Code}, so have the mcc member first. ABI compatibility cannot be maintained for struct gprs_ra_id, since it is a direct member of structs bssgp_bvc_ctx and bssgp_paging_info, and even just adding a flag to the end would cause ABI changes of those structs. Similarly, osmo_plmn_id is a direct member of osmo_location_area_id, and so forth. Add new API to set and read this additional flag to preserve leading zeros: - osmo_plmn_to_bcd(), osmo_plmn_from_bcd() after gsm48_mcc_mnc_to_bcd() and gsm48_mcc_mnc_from_bcd(). - gsm48_decode_lai2(), gsm48_generate_lai2() after gsm48_decode_lai(), gsm48_generate_lai(). - gsm0808_create_layer3_2() after gsm0808_create_layer3() and gsm0808_create_layer3_aoip(). - various osmo_*_name() functions in gsm23003.h (osmo_rai_name() still in gsm48.h close to struct gprs_ra_id definition). The amount and duplication of these may seem a bit overboard, but IMO they do make sense in this way. Though most code will soon see patches unifying the data structures used, in some cases (vty, ctrl) they are required singled out. Without these functions, the formatting ("%0*u", mnc_3_digits ? 3 : 2, mnc) would be duplicated all over our diverse repositories. In various log output, include the leading MNC zeros. Mark one TODO in card_fs_sim.c, I am not sure how to communicate a leading zero to/from a SIM card FS. The focus here is on the core network / BSS. To indicate ABI incompatibility, bump libosmogsm and libosmogb LIBVERSIONs; adjust debian files accordingly. Implementation choices: - The default behavior upon zero-initialization will be the mnc_3_digits flag set to false, which yields exactly the previous behavior. - I decided against packing the mnc with the mnc_3_digits field into a sub-struct because it would immediately break all builds of dependent projects: it would require immediate merging of numerous patches in other repositories, and it would make compiling older code against a newer libosmocore unneccessarily hard. Change-Id: Id2240f7f518494c9df6c8bda52c0d5092f90f221
2018-02-20 12:47:08 +00:00
osmo_rai_name(&bctx->ra_id), bctx->cell_id, bvci);
}
/* Acknowledge the RESET to the BTS */
if (bvci == BVCI_SIGNALLING || bvci == BVCI_PTM || bctx->is_sgsn)
bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
nsei, bvci, ns_bvci);
else
bssgp_tx_bvc_reset_ack_nsei_bvci(nsei, bvci, &bctx->ra_id, bctx->cell_id);
/* Send NM_BVC_RESET.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
nmp.bvci = bvci;
nmp.tp = tp;
nmp.ra_id = &bctx->ra_id;
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_RESET,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
return 0;
}
static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp)
{
struct osmo_bssgp_prim nmp;
uint16_t bvci, nsei = msgb_nsei(msg);
struct bssgp_bvc_ctx *ptp_ctx;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received block for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
LOGP(DLBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
ptp_ctx->state |= BVC_S_BLOCKED;
rate_ctr_inc(rate_ctr_group_get_ctr(ptp_ctx->ctrg, BSSGP_CTR_BLOCKED));
/* Send NM_BVC_BLOCK.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
nmp.bvci = bvci;
nmp.tp = tp;
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_BLOCK,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
/* We always acknowledge the BLOCKing */
return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK, nsei,
bvci, msgb_bvci(msg));
};
static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp)
{
struct osmo_bssgp_prim nmp;
uint16_t bvci, nsei = msgb_nsei(msg);
struct bssgp_bvc_ctx *ptp_ctx;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received unblock for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
ptp_ctx->state &= ~BVC_S_BLOCKED;
/* Send NM_BVC_UNBLOCK.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
nmp.bvci = bvci;
nmp.tp = tp;
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_UNBLOCK,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
/* We always acknowledge the unBLOCKing */
return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, nsei,
bvci, msgb_bvci(msg));
};
/* Uplink unit-data */
static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp,
struct bssgp_bvc_ctx *ctx)
{
struct osmo_bssgp_prim gbp;
struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
/* extract TLLI and parse TLV IEs */
msgb_tlli(msg) = osmo_ntohl(budh->tlli);
DEBUGP(DLBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
/* Cell ID and LLC_PDU are the only mandatory IE */
if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8) ||
!TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
"missing mandatory IE\n", msgb_tlli(msg));
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
/* store pointer to LLC header and CELL ID in msgb->cb */
msgb_llch(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_LLC_PDU);
msgb_bcid(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_CELL_ID);
/* Send BSSGP_UL_UD.ind to NM */
memset(&gbp, 0, sizeof(gbp));
gbp.nsei = ctx->nsei;
gbp.bvci = ctx->bvci;
gbp.tlli = msgb_tlli(msg);
gbp.tp = tp;
osmo_prim_init(&gbp.oph, SAP_BSSGP_LL, PRIM_BSSGP_UL_UD,
PRIM_OP_INDICATION, msg);
return bssgp_prim_cb(&gbp.oph, NULL);
}
static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp)
{
struct osmo_bssgp_prim gbp;
struct gprs_ra_id raid;
uint32_t tlli;
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
/* Inform GMM about the SUSPEND request */
memset(&gbp, 0, sizeof(gbp));
gbp.nsei = nsei;
gbp.bvci = ns_bvci;
gbp.tlli = tlli;
gbp.ra_id = &raid;
osmo_prim_init(&gbp.oph, SAP_BSSGP_GMM, PRIM_BSSGP_GMM_SUSPEND,
PRIM_OP_REQUEST, msg);
rc = bssgp_prim_cb(&gbp.oph, NULL);
if (rc < 0)
return bssgp_tx_suspend_nack(nsei, tlli, &raid, NULL);
bssgp_tx_suspend_ack(nsei, tlli, &raid, 0);
return 0;
}
static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
{
struct osmo_bssgp_prim gbp;
struct gprs_ra_id raid;
uint32_t tlli;
uint8_t suspend_ref;
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4 ) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_SUSPEND_REF_NR, 1)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR);
DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
/* Inform GMM about the RESUME request */
memset(&gbp, 0, sizeof(gbp));
gbp.nsei = nsei;
gbp.bvci = ns_bvci;
gbp.tlli = tlli;
gbp.ra_id = &raid;
gbp.u.resume.suspend_ref = suspend_ref;
osmo_prim_init(&gbp.oph, SAP_BSSGP_GMM, PRIM_BSSGP_GMM_RESUME,
PRIM_OP_REQUEST, msg);
rc = bssgp_prim_cb(&gbp.oph, NULL);
if (rc < 0)
return bssgp_tx_resume_nack(nsei, tlli, &raid,
NULL);
bssgp_tx_resume_ack(nsei, tlli, &raid);
return 0;
}
static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp,
struct bssgp_bvc_ctx *ctx)
{
struct osmo_bssgp_prim nmp;
uint32_t tlli = 0;
uint16_t nsei = msgb_nsei(msg);
if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_LLC_FRAMES_DISCARDED, 1) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_NUM_OCT_AFF, 3)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
"missing mandatory IE\n", ctx->bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
ctx->bvci, tlli);
rate_ctr_inc(rate_ctr_group_get_ctr(ctx->ctrg, BSSGP_CTR_DISCARDED));
/* send NM_LLC_DISCARDED to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
nmp.bvci = ctx->bvci;
nmp.tlli = tlli;
nmp.tp = tp;
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_LLC_DISCARDED,
PRIM_OP_INDICATION, msg);
return bssgp_prim_cb(&nmp.oph, NULL);
}
int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
uint16_t bvci, struct bssgp_bvc_ctx *bctx)
{
uint16_t nsei = msgb_nsei(msg);
struct osmo_bssgp_prim nmp;
enum gprs_bssgp_cause cause;
if (!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
"missing mandatory IE\n", bvci);
cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC;
} else {
cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
}
LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
bvci, bssgp_cause_str(cause));
if (cause == BSSGP_CAUSE_BVCI_BLOCKED || cause == BSSGP_CAUSE_UNKNOWN_BVCI) {
if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2))
LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP BVCI=%u Rx STATUS cause=%s "
"missing conditional BVCI IE\n",
bvci, bssgp_cause_str(cause));
}
if (bctx)
rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_STATUS));
/* send NM_STATUS to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
nmp.bvci = bvci;
nmp.tp = tp;
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_STATUS,
PRIM_OP_INDICATION, msg);
return bssgp_prim_cb(&nmp.oph, NULL);
}
/* One element (msgb) in a BSSGP Flow Control queue */
struct bssgp_fc_queue_element {
/* linked list of queue elements */
struct llist_head list;
/* The message that we have enqueued */
struct msgb *msg;
/* Length of the LLC PDU part of the contained message */
uint32_t llc_pdu_len;
/* private pointer passed to the flow control out_cb function */
void *priv;
};
static int fc_queue_timer_cfg(struct bssgp_flow_control *fc);
static int bssgp_fc_needs_queueing(struct bssgp_flow_control *fc, uint32_t pdu_len);
static void fc_timer_cb(void *data)
{
struct bssgp_flow_control *fc = data;
struct bssgp_fc_queue_element *fcqe;
struct timeval time_now;
/* if the queue is empty, we return without sending something
* and without re-starting the timer */
if (llist_empty(&fc->queue))
return;
/* get the first entry from the queue */
fcqe = llist_entry(fc->queue.next, struct bssgp_fc_queue_element,
list);
if (bssgp_fc_needs_queueing(fc, fcqe->llc_pdu_len)) {
LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
"not able to send PDU of %u bytes\n", fcqe->llc_pdu_len);
/* make sure we re-start the timer */
fc_queue_timer_cfg(fc);
return;
}
/* remove from the queue */
llist_del(&fcqe->list);
fc->queue_depth--;
/* record the time we transmitted this PDU */
osmo_gettimeofday(&time_now, NULL);
fc->time_last_pdu = time_now;
/* call the output callback for this FC instance */
fc->out_cb(fcqe->priv, fcqe->msg, fcqe->llc_pdu_len, NULL);
/* we expect that out_cb will in the end free the msgb once
* it is no longer needed */
/* but we have to free the queue element ourselves */
talloc_free(fcqe);
/* re-configure the timer for the next PDU */
fc_queue_timer_cfg(fc);
}
/* configure/schedule the flow control timer to expire once the bucket
* will have leaked a sufficient number of bytes to transmit the next
* PDU in the queue */
static int fc_queue_timer_cfg(struct bssgp_flow_control *fc)
{
struct bssgp_fc_queue_element *fcqe;
uint32_t msecs;
if (llist_empty(&fc->queue))
return 0;
fcqe = llist_entry(fc->queue.next, struct bssgp_fc_queue_element,
list);
if (fc->bucket_leak_rate != 0) {
/* Calculate the point in time at which we will have leaked
* a sufficient number of bytes from the bucket to transmit
* the first PDU in the queue */
msecs = (fcqe->llc_pdu_len * 1000) / fc->bucket_leak_rate;
/* FIXME: add that time to fc->time_last_pdu and subtract it from
* current time */
osmo_timer_setup(&fc->timer, fc_timer_cb, fc);
osmo_timer_schedule(&fc->timer, msecs / 1000, (msecs % 1000) * 1000);
} else {
/* If the PCU is telling us to not send any more data at all,
* there's no point starting a timer. */
}
return 0;
}
/* Enqueue a PDU in the flow control queue for delayed transmission */
static int fc_enqueue(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv)
{
struct bssgp_fc_queue_element *fcqe;
if (fc->queue_depth >= fc->max_queue_depth)
return -ENOSPC;
fcqe = talloc_zero(fc, struct bssgp_fc_queue_element);
if (!fcqe)
return -ENOMEM;
fcqe->msg = msg;
fcqe->llc_pdu_len = llc_pdu_len;
fcqe->priv = priv;
llist_add_tail(&fcqe->list, &fc->queue);
fc->queue_depth++;
/* re-configure the timer for dequeueing the pdu */
fc_queue_timer_cfg(fc);
return 0;
}
/* According to Section 8.2 */
static int bssgp_fc_needs_queueing(struct bssgp_flow_control *fc, uint32_t pdu_len)
{
struct timeval time_now, time_diff;
int64_t bucket_predicted;
uint32_t csecs_elapsed, leaked;
/* B' = B + L(p) - (Tc - Tp)*R */
/* compute number of centi-seconds that have elapsed since transmitting
* the last PDU (Tc - Tp) */
osmo_gettimeofday(&time_now, NULL);
timersub(&time_now, &fc->time_last_pdu, &time_diff);
csecs_elapsed = time_diff.tv_sec*100 + time_diff.tv_usec/10000;
/* compute number of bytes that have leaked in the elapsed number
* of centi-seconds */
leaked = csecs_elapsed * (fc->bucket_leak_rate / 100);
/* add the current PDU length to the last bucket level */
bucket_predicted = fc->bucket_counter + pdu_len;
/* ... and subtract the number of leaked bytes */
bucket_predicted -= leaked;
if (bucket_predicted < pdu_len)
return 0;
if (bucket_predicted <= fc->bucket_size_max) {
/* the bucket is not full yet, we can pass the packet */
fc->bucket_counter = bucket_predicted;
return 0;
}
/* bucket is full, PDU needs to be delayed */
return 1;
}
/* output callback for BVC flow control */
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv)
{
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* input function of the flow control implementation, called first
* for the MM flow control, and then as the MM flow control output
* callback in order to perform BVC flow control */
int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv)
{
struct timeval time_now;
if (llc_pdu_len > fc->bucket_size_max) {
LOGP(DLBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
"than maximum bucket size (%u)!\n", llc_pdu_len,
fc->bucket_size_max);
msgb_free(msg);
return -EIO;
}
if (bssgp_fc_needs_queueing(fc, llc_pdu_len)) {
int rc;
rc = fc_enqueue(fc, msg, llc_pdu_len, priv);
if (rc)
msgb_free(msg);
return rc;
} else {
/* record the time we transmitted this PDU */
osmo_gettimeofday(&time_now, NULL);
fc->time_last_pdu = time_now;
return fc->out_cb(priv, msg, llc_pdu_len, NULL);
}
}
/* Initialize the Flow Control structure */
void bssgp_fc_init(struct bssgp_flow_control *fc,
uint32_t bucket_size_max, uint32_t bucket_leak_rate,
uint32_t max_queue_depth,
int (*out_cb)(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv))
{
fc->out_cb = out_cb;
fc->bucket_size_max = bucket_size_max;
fc->bucket_leak_rate = bucket_leak_rate;
fc->max_queue_depth = max_queue_depth;
INIT_LLIST_HEAD(&fc->queue);
osmo_gettimeofday(&fc->time_last_pdu, NULL);
}
/* Initialize the Flow Control parameters for a new MS according to
* default values for the BVC specified by BVCI and NSEI */
int bssgp_fc_ms_init(struct bssgp_flow_control *fc_ms, uint16_t bvci,
uint16_t nsei, uint32_t max_queue_depth)
{
struct bssgp_bvc_ctx *ctx;
ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ctx)
return -ENODEV;
/* output call-back of per-MS FC is per-CTX FC */
bssgp_fc_init(fc_ms, ctx->bmax_default_ms, ctx->r_default_ms,
max_queue_depth, bssgp_fc_in);
return 0;
}
static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
struct bssgp_bvc_ctx *bctx)
{
uint32_t old_leak_rate = bctx->fc->bucket_leak_rate;
uint32_t old_r_def_ms = bctx->r_default_ms;
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
bctx->bvci);
if (!TLVP_PRES_LEN(tp, BSSGP_IE_TAG, 1) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_BVC_BUCKET_SIZE, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_BUCKET_LEAK_RATE, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_BMAX_DEFAULT_MS, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_R_DEFAULT_MS,2)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
"missing mandatory IE\n", bctx->bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
/* 11.3.5 Bucket Size in 100 octets unit */
bctx->fc->bucket_size_max = 100 * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
/* 11.3.4 Bucket Leak Rate in 100 bits/sec unit */
bctx->fc->bucket_leak_rate = 100 * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE) / 8;
/* 11.3.2 in octets */
bctx->bmax_default_ms = tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
/* 11.3.32 Bucket Leak rate in 100bits/sec unit */
bctx->r_default_ms = 100 * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS) / 8;
if (old_leak_rate != 0 && bctx->fc->bucket_leak_rate == 0)
LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of 0, stopping all DL GPRS!\n");
else if (old_leak_rate == 0 && bctx->fc->bucket_leak_rate != 0)
LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of != 0, restarting all DL GPRS!\n");
if (old_r_def_ms != 0 && bctx->r_default_ms == 0)
LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate of 0, stopping DL GPRS!\n");
else if (old_r_def_ms == 0 && bctx->r_default_ms != 0)
LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate != 0, restarting DL GPRS!\n");
/* reconfigure the timer for flow control based on new values */
fc_queue_timer_cfg(bctx->fc);
/* Send FLOW_CONTROL_BVC_ACK */
return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG),
msgb_bvci(msg));
}
/* Receive a BSSGP PDU from a BSS on a PTP BVCI */
static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
struct bssgp_bvc_ctx *bctx)
{
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_bssgph(msg);
uint8_t pdu_type = bgph->pdu_type;
int rc = 0;
OSMO_ASSERT(pdu_type != BSSGP_PDUT_STATUS);
/* If traffic is received on a BVC that is marked as blocked, the
* received PDU shall not be accepted and a STATUS PDU (Cause value:
* BVC Blocked) shall be sent to the peer entity on the signalling BVC */
if (bctx->state & BVC_S_BLOCKED) {
uint16_t bvci = msgb_bvci(msg);
return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &bvci, msg);
}
switch (pdu_type) {
case BSSGP_PDUT_UL_UNITDATA:
/* some LLC data from the MS */
rc = bssgp_rx_ul_ud(msg, tp, bctx);
break;
case BSSGP_PDUT_RA_CAPABILITY:
/* BSS requests RA capability or IMSI */
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
bctx->bvci);
/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */
/* FIXME: send RA_CAPA_UPDATE_ACK */
break;
case BSSGP_PDUT_RADIO_STATUS:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
/* BSS informs us of some exception */
/* FIXME: send GMM_RADIO_STATUS.ind to GMM */
break;
case BSSGP_PDUT_FLOW_CONTROL_BVC:
/* BSS informs us of available bandwidth in Gb interface */
rc = bssgp_rx_fc_bvc(msg, tp, bctx);
break;
case BSSGP_PDUT_FLOW_CONTROL_MS:
/* BSS informs us of available bandwidth to one MS */
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
bctx->bvci);
/* FIXME: actually implement flow control */
/* FIXME: Send FLOW_CONTROL_MS_ACK */
break;
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
case BSSGP_PDUT_BVC_RESET:
rc = bssgp_rx_bvc_reset(msg, tp, bctx->bvci);
break;
case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
case BSSGP_PDUT_MODIFY_BSS_PFC:
case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
"implemented\n", bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
break;
/* those only exist in the SGSN -> BSS direction */
case BSSGP_PDUT_DL_UNITDATA:
case BSSGP_PDUT_PAGING_PS:
case BSSGP_PDUT_PAGING_CS:
case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
}
return rc;
}
/* Receive a BSSGP PDU from a BSS on a SIGNALLING BVCI */
static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
struct bssgp_bvc_ctx *bctx)
{
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_bssgph(msg);
uint8_t pdu_type = bgph->pdu_type;
int rc = 0;
uint16_t ns_bvci = msgb_bvci(msg);
uint16_t bvci = bctx ? bctx->bvci : ns_bvci;
switch (bgph->pdu_type) {
case BSSGP_PDUT_SUSPEND:
/* MS wants to suspend */
rc = bssgp_rx_suspend(msg, tp);
break;
case BSSGP_PDUT_RESUME:
/* MS wants to resume */
rc = bssgp_rx_resume(msg, tp);
break;
case BSSGP_PDUT_FLUSH_LL_ACK:
/* BSS informs us it has performed LL FLUSH */
DEBUGP(DLBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
/* FIXME: send NM_FLUSH_LL.res to NM */
break;
case BSSGP_PDUT_LLC_DISCARD:
/* BSS informs that some LLC PDU's have been discarded */
if (!bctx) {
LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP Rx LLC-DISCARD missing mandatory BVCI\n");
goto err_mand_ie;
}
rc = bssgp_rx_llc_disc(msg, tp, bctx);
break;
case BSSGP_PDUT_BVC_BLOCK:
/* BSS tells us that BVC shall be blocked */
if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_block(msg, tp);
break;
case BSSGP_PDUT_BVC_UNBLOCK:
/* BSS tells us that BVC shall be unblocked */
if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_unblock(msg, tp);
break;
case BSSGP_PDUT_BVC_RESET_ACK:
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
break;
case BSSGP_PDUT_BVC_RESET:
/* SGSN or BSS tells us that BVC init is required */
if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_reset(msg, tp, ns_bvci);
break;
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
case BSSGP_PDUT_RAN_INFO:
case BSSGP_PDUT_RAN_INFO_REQ:
case BSSGP_PDUT_RAN_INFO_ACK:
case BSSGP_PDUT_RAN_INFO_ERROR:
case BSSGP_PDUT_RAN_INFO_APP_ERROR:
rc = bssgp_rx_rim(msg, tp, bvci);
break;
/* those only exist in the SGSN -> BSS direction */
case BSSGP_PDUT_PAGING_PS:
case BSSGP_PDUT_PAGING_CS:
case BSSGP_PDUT_SUSPEND_ACK:
case BSSGP_PDUT_SUSPEND_NACK:
case BSSGP_PDUT_RESUME_ACK:
case BSSGP_PDUT_RESUME_NACK:
case BSSGP_PDUT_FLUSH_LL:
case BSSGP_PDUT_BVC_BLOCK_ACK:
case BSSGP_PDUT_BVC_UNBLOCK_ACK:
case BSSGP_PDUT_SGSN_INVOKE_TRACE:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
}
return rc;
err_mand_ie:
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
/* We expect msgb_bssgph() to point to the BSSGP header */
int bssgp_rcvmsg(struct msgb *msg)
{
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_bssgph(msg);
struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg);
struct tlv_parsed tp;
struct bssgp_bvc_ctx *bctx;
uint8_t pdu_type = bgph->pdu_type;
uint16_t ns_bvci = msgb_bvci(msg);
uint16_t nsei = msgb_nsei(msg);
uint16_t bvci = ns_bvci;
int data_len;
int rc = 0;
/* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */
/* UNITDATA BSSGP headers have TLLI in front */
if (pdu_type != BSSGP_PDUT_UL_UNITDATA &&
pdu_type != BSSGP_PDUT_DL_UNITDATA) {
data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
rc = bssgp_tlv_parse(&tp, bgph->data, data_len);
} else {
data_len = msgb_bssgp_len(msg) - sizeof(*budh);
rc = bssgp_tlv_parse(&tp, budh->data, data_len);
}
if (rc < 0) {
LOGP(DLBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
bssgp_pdu_str(pdu_type), msgb_hexdump(msg));
if (pdu_type != BSSGP_PDUT_STATUS)
return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg);
return rc;
}
if (bvci == BVCI_SIGNALLING && TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2))
bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
/* look-up or create the BTS context for this BVC */
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (bctx) {
log_set_context(LOG_CTX_GB_BVC, bctx);
rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_IN));
rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_IN),
msgb_bssgp_len(msg));
}
/* Always handle STATUS PDUs, even if they contain an invalid BVCI or
* are otherwise unexpected */
if (pdu_type == BSSGP_PDUT_STATUS)
/* Some exception has occurred */
return bssgp_rx_status(msg, &tp, bvci, bctx);
/* Only a RESET PDU can create a new BVC context, otherwise it must be
* registered if a BVCI is given. */
if (!bctx && bvci != BVCI_SIGNALLING &&
pdu_type != BSSGP_PDUT_BVC_RESET) {
LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
bssgp_pdu_str(pdu_type));
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
}
if (ns_bvci == BVCI_SIGNALLING)
rc = bssgp_rx_sign(msg, &tp, bctx);
else if (ns_bvci == BVCI_PTM)
rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
else if (bctx)
rc = bssgp_rx_ptp(msg, &tp, bctx);
else
LOGP(DLBSSGP, LOGL_NOTICE,
"NSEI=%u/BVCI=%u Cannot handle PDU type %s for unknown BVCI, NS BVCI %u\n", nsei, bvci,
bssgp_pdu_str(pdu_type), ns_bvci);
return rc;
}
int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
struct bssgp_dl_ud_par *dup)
{
struct bssgp_bvc_ctx *bctx;
struct bssgp_ud_hdr *budh;
uint8_t llc_pdu_tlv_hdr_len = 2;
uint8_t *llc_pdu_tlv;
uint16_t msg_len = msg->len;
uint16_t bvci = msgb_bvci(msg);
uint16_t nsei = msgb_nsei(msg);
uint16_t _pdu_lifetime = osmo_htons(pdu_lifetime); /* centi-seconds */
uint16_t drx_params;
OSMO_ASSERT(dup != NULL);
/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
if (bvci <= BVCI_PTM ) {
LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
bvci);
msgb_free(msg);
return -EINVAL;
}
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!bctx) {
LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
bvci);
msgb_free(msg);
return -ENODEV;
}
if (msg->len > TVLV_MAX_ONEBYTE)
llc_pdu_tlv_hdr_len += 1;
/* prepend the tag and length of the LLC-PDU TLV */
llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len);
llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU;
if (llc_pdu_tlv_hdr_len > 2) {
llc_pdu_tlv[1] = msg_len >> 8;
llc_pdu_tlv[2] = msg_len & 0xff;
} else {
llc_pdu_tlv[1] = msg_len & 0x7f;
llc_pdu_tlv[1] |= 0x80;
}
/* FIXME: optional elements: Alignment, UTRAN CCO, LSA, PFI */
/* Old TLLI to help BSS map from old->new */
if (dup->tlli) {
uint32_t tlli = osmo_htonl(*dup->tlli);
msgb_tvlv_push(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &tlli);
}
/* IMSI */
if (dup->imsi && strlen(dup->imsi)) {
uint8_t mi[GSM48_MID_MAX_SIZE];
/* gsm48_generate_mid_from_imsi() is guaranteed to never return more than 11,
* but somehow gcc (8.2) is not smart enough to figure this out and claims that
* the memcpy in msgb_tvlv_put() below will cause and out-of-bounds access up to
* mi[131], which is wrong */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
int imsi_len = gsm48_generate_mid_from_imsi(mi, dup->imsi);
OSMO_ASSERT(imsi_len <= GSM48_MID_MAX_SIZE);
if (imsi_len > 2)
msgb_tvlv_push(msg, BSSGP_IE_IMSI,
imsi_len-2, mi+2);
#pragma GCC diagnostic pop
}
/* DRX parameters */
drx_params = osmo_htons(dup->drx_parms);
msgb_tvlv_push(msg, BSSGP_IE_DRX_PARAMS, 2,
(uint8_t *) &drx_params);
/* FIXME: Priority */
/* MS Radio Access Capability */
if (dup->ms_ra_cap.len)
msgb_tvlv_push(msg, BSSGP_IE_MS_RADIO_ACCESS_CAP,
dup->ms_ra_cap.len, dup->ms_ra_cap.v);
/* prepend the pdu lifetime */
msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (uint8_t *)&_pdu_lifetime);
/* prepend the QoS profile, TLLI and pdu type */
budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh));
memcpy(budh->qos_profile, dup->qos_profile, sizeof(budh->qos_profile));
budh->tlli = osmo_htonl(msgb_tlli(msg));
budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
/* Identifiers down: BVCI, NSEI (in msgb->cb) */
/* check if we have to go through per-ms flow control or can go
* directly to the per-BSS flow control */
if (dup->fc)
return bssgp_fc_in(dup->fc, msg, msg_len, bctx->fc);
else
return bssgp_fc_in(bctx->fc, msg, msg_len, NULL);
}
/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */
int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
struct bssgp_paging_info *pinfo)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t drx_params = osmo_htons(pinfo->drx_params);
uint8_t mi[GSM48_MID_MAX_SIZE];
int imsi_len = gsm48_generate_mid_from_imsi(mi, pinfo->imsi);
struct gsm48_ra_id ra;
if (imsi_len < 2)
return -EINVAL;
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = ns_bvci;
if (pinfo->mode == BSSGP_PAGING_PS)
bgph->pdu_type = BSSGP_PDUT_PAGING_PS;
else
bgph->pdu_type = BSSGP_PDUT_PAGING_CS;
/* IMSI */
/* gsm48_generate_mid_from_imsi() is guaranteed to never return more than 11,
* but somehow gcc (8.2) is not smart enough to figure this out and claims that
* the memcpy in msgb_tvlv_put() below will cause and out-of-bounds access up to
* mi[131], which is wrong */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
OSMO_ASSERT(imsi_len <= GSM48_MID_MAX_SIZE);
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
#pragma GCC diagnostic pop
/* DRX Parameters */
msgb_tvlv_put(msg, BSSGP_IE_DRX_PARAMS, 2,
(uint8_t *) &drx_params);
/* Scope */
switch (pinfo->scope) {
case BSSGP_PAGING_BSS_AREA:
{
uint8_t null = 0;
msgb_tvlv_put(msg, BSSGP_IE_BSS_AREA_ID, 1, &null);
}
break;
case BSSGP_PAGING_LOCATION_AREA:
gsm48_encode_ra(&ra, &pinfo->raid);
msgb_tvlv_put(msg, BSSGP_IE_LOCATION_AREA, 4, (const uint8_t *)&ra);
break;
case BSSGP_PAGING_ROUTEING_AREA:
bssgp_msgb_ra_put(msg, &pinfo->raid);
break;
case BSSGP_PAGING_BVCI:
{
uint16_t bvci = osmo_htons(pinfo->bvci);
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *)&bvci);
}
break;
}
/* QoS profile mandatory for PS */
if (pinfo->mode == BSSGP_PAGING_PS)
msgb_tvlv_put(msg, BSSGP_IE_QOS_PROFILE, 3, pinfo->qos);
/* Optional (P-)TMSI */
if (pinfo->ptmsi) {
uint32_t ptmsi = osmo_htonl(*pinfo->ptmsi);
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi);
}
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
void bssgp_set_log_ss(int ss)
{
/* BSSGP has moved from DGPRS to DLGPRS, please update your code if it's
* still calling this function
*/
}
/*!
* \brief Flush the queue of the bssgp_flow_control
* \param[in] The flow control object which holds the queue.
*/
void bssgp_fc_flush_queue(struct bssgp_flow_control *fc)
{
struct bssgp_fc_queue_element *element, *tmp;
llist_for_each_entry_safe(element, tmp, &fc->queue, list) {
msgb_free(element->msg);
llist_del(&element->list);
talloc_free(element);
}
}
/*!
* \brief Flush the queues of all BSSGP contexts.
*/
void bssgp_flush_all_queues(void)
{
struct bssgp_bvc_ctx *bctx;
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->fc)
bssgp_fc_flush_queue(bctx->fc);
}
}