From 901ed14c898ef229c654009ac0dc078d09d7b054 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Mon, 1 Feb 2021 12:48:48 +0100 Subject: gbproxy: remove (moved to own repository) New repository: https://git.osmocom.org/osmo-gbproxy/ Related: OS#4992 Change-Id: I37f7cebaf2a06bd93627a452f5df44edcfc0f87a --- src/Makefile.am | 1 - src/gbproxy/Makefile.am | 40 -- src/gbproxy/gb_proxy.c | 1549 ------------------------------------------- src/gbproxy/gb_proxy_ctrl.c | 137 ---- src/gbproxy/gb_proxy_main.c | 336 ---------- src/gbproxy/gb_proxy_peer.c | 759 --------------------- src/gbproxy/gb_proxy_vty.c | 778 ---------------------- 7 files changed, 3600 deletions(-) delete mode 100644 src/gbproxy/Makefile.am delete mode 100644 src/gbproxy/gb_proxy.c delete mode 100644 src/gbproxy/gb_proxy_ctrl.c delete mode 100644 src/gbproxy/gb_proxy_main.c delete mode 100644 src/gbproxy/gb_proxy_peer.c delete mode 100644 src/gbproxy/gb_proxy_vty.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index c45d3ab46..e389b7f65 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,5 @@ SUBDIRS = \ gprs \ sgsn \ - gbproxy \ gtphub \ $(NULL) diff --git a/src/gbproxy/Makefile.am b/src/gbproxy/Makefile.am deleted file mode 100644 index 1500e1173..000000000 --- a/src/gbproxy/Makefile.am +++ /dev/null @@ -1,40 +0,0 @@ -AM_CPPFLAGS = \ - $(all_includes) \ - -I$(top_srcdir)/include \ - -I$(top_builddir) \ - $(NULL) - -AM_CFLAGS = \ - -Wall \ - -fno-strict-aliasing \ - $(LIBOSMOCORE_CFLAGS) \ - $(LIBOSMOGSM_CFLAGS) \ - $(LIBOSMOVTY_CFLAGS) \ - $(LIBOSMOCTRL_CFLAGS) \ - $(LIBOSMOABIS_CFLAGS) \ - $(LIBOSMOGB_CFLAGS) \ - $(LIBOSMOGSUPCLIENT_CFLAGS) \ - $(COVERAGE_CFLAGS) \ - $(LIBGTP_CFLAGS) \ - $(NULL) - -bin_PROGRAMS = \ - osmo-gbproxy \ - $(NULL) - -osmo_gbproxy_SOURCES = \ - gb_proxy.c \ - gb_proxy_main.c \ - gb_proxy_vty.c \ - gb_proxy_ctrl.c \ - gb_proxy_peer.c \ - $(NULL) -osmo_gbproxy_LDADD = \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOGSM_LIBS) \ - $(LIBOSMOVTY_LIBS) \ - $(LIBOSMOCTRL_LIBS) \ - $(LIBOSMOGB_LIBS) \ - $(LIBGTP_LIBS) \ - -lrt \ - $(NULL) diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c deleted file mode 100644 index c882eb068..000000000 --- a/src/gbproxy/gb_proxy.c +++ /dev/null @@ -1,1549 +0,0 @@ -/* NS-over-IP proxy */ - -/* (C) 2010-2020 by Harald Welte - * (C) 2010-2013 by On-Waves - * (C) 2013 by Holger Hans Peter Freyther - * 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 . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -extern void *tall_sgsn_ctx; - -static const struct rate_ctr_desc global_ctr_description[] = { - { "inv-bvci", "Invalid BVC Identifier " }, - { "inv-lai", "Invalid Location Area Identifier" }, - { "inv-rai", "Invalid Routing Area Identifier " }, - { "inv-nsei", "No BVC established for NSEI " }, - { "proto-err:bss", "BSSGP protocol error (BSS )" }, - { "proto-err:sgsn", "BSSGP protocol error (SGSN)" }, - { "not-supp:bss", "Feature not supported (BSS )" }, - { "not-supp:sgsn", "Feature not supported (SGSN)" }, - { "restart:sgsn", "Restarted RESET procedure (SGSN)" }, - { "tx-err:sgsn", "NS Transmission error (SGSN)" }, - { "error", "Other error " }, - { "mod-peer-err", "Patch error: no peer " }, -}; - -static const struct rate_ctr_group_desc global_ctrg_desc = { - .group_name_prefix = "gbproxy:global", - .group_description = "GBProxy Global Statistics", - .num_ctr = ARRAY_SIZE(global_ctr_description), - .ctr_desc = global_ctr_description, - .class_id = OSMO_STATS_CLASS_GLOBAL, -}; - -static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_bvc *bvc, - uint16_t ns_bvci); - - -/* generate BVC-STATUS message with cause value derived from TLV-parser error */ -static int tx_status_from_tlvp(enum osmo_tlv_parser_error tlv_p_err, struct msgb *orig_msg) -{ - uint8_t bssgp_cause; - switch (tlv_p_err) { - case OSMO_TLVP_ERR_MAND_IE_MISSING: - bssgp_cause = BSSGP_CAUSE_MISSING_MAND_IE; - break; - default: - bssgp_cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC; - } - return bssgp_tx_status(bssgp_cause, NULL, orig_msg); -} - -/* strip off the NS header */ -static void strip_ns_hdr(struct msgb *msg) -{ - int strip_len = msgb_bssgph(msg) - msg->data; - msgb_pull(msg, strip_len); -} - -#if 0 -/* feed a message down the NS-VC associated with the specified bvc */ -static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg, - uint16_t ns_bvci, uint16_t sgsn_nsei) -{ - /* create a copy of the message so the old one can - * be free()d safely when we return from gbprox_rcvmsg() */ - struct gprs_ns2_inst *nsi = cfg->nsi; - struct osmo_gprs_ns2_prim nsp = {}; - struct msgb *msg = bssgp_msgb_copy(old_msg, "msgb_relay2sgsn"); - int rc; - - DEBUGP(DGPRS, "NSE(%05u/BSS)-BVC(%05u) proxying BTS->SGSN NSE(%05u/SGSN)\n", - msgb_nsei(msg), ns_bvci, sgsn_nsei); - - nsp.bvci = ns_bvci; - nsp.nsei = sgsn_nsei; - - strip_ns_hdr(msg); - osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_UNIT_DATA, - PRIM_OP_REQUEST, msg); - rc = gprs_ns2_recv_prim(nsi, &nsp.oph); - if (rc < 0) - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_TX_ERR_SGSN]); - return rc; -} -#endif - -/*! Determine the TLLI from the given BSSGP message. - * \param[in] bssgp pointer to start of BSSGP header - * \param[in] bssgp_len length of BSSGP message in octets - * \param[out] tlli TLLI (if any) in host byte order - * \returns 1 if TLLI found; 0 if none found; negative on parse error */ -int gprs_gb_parse_tlli(const uint8_t *bssgp, size_t bssgp_len, uint32_t *tlli) -{ - const struct bssgp_normal_hdr *bgph; - uint8_t pdu_type; - - if (bssgp_len < sizeof(struct bssgp_normal_hdr)) - return -EINVAL; - - bgph = (struct bssgp_normal_hdr *)bssgp; - pdu_type = bgph->pdu_type; - - if (pdu_type == BSSGP_PDUT_UL_UNITDATA || - pdu_type == BSSGP_PDUT_DL_UNITDATA) { - const struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *)bssgp; - if (bssgp_len < sizeof(struct bssgp_ud_hdr)) - return -EINVAL; - *tlli = osmo_load32be((const uint8_t *)&budh->tlli); - return 1; - } else { - const uint8_t *data = bgph->data; - size_t data_len = bssgp_len - sizeof(*bgph); - struct tlv_parsed tp; - - if (bssgp_tlv_parse(&tp, data, data_len) < 0) - return -EINVAL; - - if (TLVP_PRESENT(&tp, BSSGP_IE_TLLI)) { - *tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - return 1; - } - } - - /* No TLLI present in message */ - return 0; -} - -/* feed a message down the NSE */ -static int gbprox_relay2nse(struct msgb *old_msg, struct gbproxy_nse *nse, - uint16_t ns_bvci) -{ - OSMO_ASSERT(nse); - OSMO_ASSERT(nse->cfg); - - /* create a copy of the message so the old one can - * be free()d safely when we return from gbprox_rcvmsg() */ - struct gprs_ns2_inst *nsi = nse->cfg->nsi; - struct osmo_gprs_ns2_prim nsp = {}; - struct msgb *msg = bssgp_msgb_copy(old_msg, "msgb_relay2nse"); - uint32_t tlli; - int rc; - - DEBUGP(DGPRS, "NSE(%05u/%s)-BVC(%05u/??) proxying to NSE(%05u/%s)\n", msgb_nsei(msg), - !nse->sgsn_facing ? "SGSN" : "BSS", ns_bvci, nse->nsei, nse->sgsn_facing ? "SGSN" : "BSS"); - - nsp.bvci = ns_bvci; - nsp.nsei = nse->nsei; - - /* Strip the old NS header, it will be replaced with a new one */ - strip_ns_hdr(msg); - - /* TS 48.018 Section 5.4.2: The link selector parameter is - * defined in 3GPP TS 48.016. At one side of the Gb interface, - * all BSSGP UNITDATA PDUs related to an MS shall be passed with - * the same LSP, e.g. the LSP contains the MS's TLLI, to the - * underlying network service. */ - if (gprs_gb_parse_tlli(msgb_data(msg), msgb_length(msg), &tlli) == 1) - nsp.u.unitdata.link_selector = tlli; - - osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, - PRIM_OP_REQUEST, msg); - rc = gprs_ns2_recv_prim(nsi, &nsp.oph); - /* FIXME: We need a counter group for gbproxy_nse */ - //if (rc < 0) - // rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_TX_ERR]); - - return rc; -} - -/* feed a message down the NS-VC associated with the specified bvc */ -static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_bvc *bvc, - uint16_t ns_bvci) -{ - int rc; - struct gbproxy_nse *nse = bvc->nse; - OSMO_ASSERT(nse); - - rc = gbprox_relay2nse(old_msg, nse, ns_bvci); - if (rc < 0) - rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_TX_ERR]); - - return rc; -} - -int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) -{ - return 0; -} - - -/*********************************************************************** - * PTP BVC handling - ***********************************************************************/ - -/* FIXME: Handle the tlli NULL case correctly, - * This function should take a generic selector - * and choose an sgsn based on that - */ -static struct gbproxy_sgsn *gbproxy_select_sgsn(struct gbproxy_config *cfg, const uint32_t *tlli) -{ - struct gbproxy_sgsn *sgsn = NULL; - struct gbproxy_sgsn *sgsn_avoid = NULL; - - int tlli_type; - int16_t nri; - bool null_nri = false; - - if (!tlli) { - sgsn = llist_first_entry(&cfg->sgsns, struct gbproxy_sgsn, list); - if (!sgsn) { - return NULL; - } - LOGPSGSN(sgsn, LOGL_INFO, "Could not get TLLI, using first SGSN\n"); - return sgsn; - } - - if (cfg->pool.nri_bitlen == 0) { - /* Pooling is disabled */ - sgsn = llist_first_entry(&cfg->sgsns, struct gbproxy_sgsn, list); - if (!sgsn) { - return NULL; - } - - LOGPSGSN(sgsn, LOGL_INFO, "Pooling disabled, using first configured SGSN\n"); - } else { - /* Pooling is enabled, try to use the NRI for routing to an SGSN - * See 3GPP TS 23.236 Ch. 5.3.2 */ - tlli_type = gprs_tlli_type(*tlli); - if (tlli_type == TLLI_LOCAL || tlli_type == TLLI_FOREIGN) { - /* Only get/use the NRI if tlli type is local */ - osmo_tmsi_nri_v_get(&nri, *tlli, cfg->pool.nri_bitlen); - if (nri >= 0) { - /* Get the SGSN for the NRI */ - sgsn = gbproxy_sgsn_by_nri(cfg, nri, &null_nri); - if (sgsn && !null_nri) - return sgsn; - /* If the NRI is the null NRI, we need to avoid the chosen SGSN */ - if (null_nri && sgsn) { - sgsn_avoid = sgsn; - } - } else { - /* We couldn't get the NRI from the TLLI */ - LOGP(DGPRS, LOGL_ERROR, "Could not extract NRI from local TLLI %08x\n", *tlli); - } - } else { - LOGP(DGPRS, LOGL_INFO, "TLLI %08x is neither local nor foreign, not routing by NRI\n", *tlli); - } - } - - /* If we haven't found an SGSN yet we need to choose one, but avoid the one in sgsn_avoid - * NOTE: This function is not stable if the number of SGSNs or allow_attach changes - * We could implement TLLI tracking here, but 3GPP TS 23.236 Ch. 5.3.2 (see NOTE) argues that - * we can just wait for the MS to reattempt the procedure. - */ - if (!sgsn) - sgsn = gbproxy_sgsn_by_tlli(cfg, sgsn_avoid, *tlli); - - if (!sgsn) { - LOGP(DGPRS, LOGL_ERROR, "No suitable SGSN found for TLLI %u\n", *tlli); - return NULL; - } - - return sgsn; -} - -/*! Find the correct gbproxy_bvc given a cell and an SGSN - * \param[in] cfg The gbproxy configuration - * \param[in] cell The cell the message belongs to - * \param[in] tlli An optional TLLI used for tracking - * \return Returns 0 on success, otherwise a negative value - */ -static struct gbproxy_bvc *gbproxy_select_sgsn_bvc(struct gbproxy_config *cfg, struct gbproxy_cell *cell, const uint32_t *tlli) -{ - struct gbproxy_sgsn *sgsn; - struct gbproxy_bvc *sgsn_bvc = NULL; - int i; - - sgsn = gbproxy_select_sgsn(cfg, tlli); - if (!sgsn) { - LOGPCELL(cell, LOGL_ERROR, "Could not find any SGSN, dropping message!\n"); - return NULL; - } - - /* Get the BVC for this SGSN/NSE */ - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - sgsn_bvc = cell->sgsn_bvc[i]; - if (!sgsn_bvc) - continue; - if (sgsn->nse != sgsn_bvc->nse) - continue; - - return sgsn_bvc; - } - - /* This shouldn't happen */ - LOGPCELL(cell, LOGL_ERROR, "Could not find matching BVC for SGSN %s, dropping message!\n", sgsn->name); - return NULL; -} - -/*! Send a message to the next SGSN, possibly ignoring the null SGSN - * route an uplink message on a PTP-BVC to a SGSN using the TLLI - * \param[in] cell The cell the message belongs to - * \param[in] msg The BSSGP message - * \param[in] null_sgsn If not NULL then avoid this SGSN (because this message contains its null NRI) - * \param[in] tlli An optional TLLI used for tracking - * \return Returns 0 on success, otherwise a negative value - */ -static int gbprox_bss2sgsn_tlli(struct gbproxy_cell *cell, struct msgb *msg, const uint32_t *tlli, - bool sig_bvci) -{ - struct gbproxy_config *cfg = cell->cfg; - struct gbproxy_bvc *sgsn_bvc; - - sgsn_bvc = gbproxy_select_sgsn_bvc(cfg, cell, tlli); - if (!sgsn_bvc) { - LOGPCELL(cell, LOGL_NOTICE, "Could not find any SGSN for TLLI %u, dropping message!\n", *tlli); - return -EINVAL; - } - - return gbprox_relay2peer(msg, sgsn_bvc, sig_bvci ? 0 : sgsn_bvc->bvci); -} - -/* Receive an incoming PTP message from a BSS-side NS-VC */ -static int gbprox_rx_ptp_from_bss(struct gbproxy_nse *nse, struct msgb *msg, uint16_t ns_bvci) -{ - struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); - const char *pdut_name = osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type); - struct gbproxy_bvc *bss_bvc; - struct tlv_parsed tp; - char log_pfx[32]; - uint32_t tlli; - int rc; - - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/BSS)-BVC(%05u/??)", nse->nsei, ns_bvci); - - LOGP(DGPRS, LOGL_DEBUG, "%s Rx %s\n", log_pfx, pdut_name); - - if (ns_bvci == 0 || ns_bvci == 1) { - LOGP(DGPRS, LOGL_NOTICE, "%s BVCI=%05u is not PTP\n", log_pfx, ns_bvci); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(bgph->pdu_type) & BSSGP_PDUF_PTP)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in PTP BVC\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(bgph->pdu_type) & BSSGP_PDUF_UL)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in uplink direction\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - bss_bvc = gbproxy_bvc_by_bvci(nse, ns_bvci); - if (!bss_bvc) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s - Didn't find BVC for PTP message, discarding\n", - log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &ns_bvci, msg); - } - - /* UL_UNITDATA has a different header than all other uplink PDUs */ - if (bgph->pdu_type == BSSGP_PDUT_UL_UNITDATA) { - const struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); - if (msgb_bssgp_len(msg) < sizeof(*budh)) - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, bgph->pdu_type, budh->data, - msgb_bssgp_len(msg) - sizeof(*budh), 0, 0, DGPRS, log_pfx); - /* populate TLLI from the fixed headser into the TLV-parsed array so later code - * doesn't have to worry where the TLLI came from */ - tp.lv[BSSGP_IE_TLLI].len = 4; - tp.lv[BSSGP_IE_TLLI].val = (const uint8_t *) &budh->tlli; - } else { - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, bgph->pdu_type, bgph->data, - msgb_bssgp_len(msg) - sizeof(*bgph), 0, 0, DGPRS, log_pfx); - } - if (rc < 0) { - rate_ctr_inc(&nse->cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); - return tx_status_from_tlvp(rc, msg); - } - /* hack to get both msg + tlv_parsed passed via osmo_fsm_inst_dispatch */ - msgb_bcid(msg) = (void *)&tp; - - switch (bgph->pdu_type) { - case BSSGP_PDUT_UL_UNITDATA: - case BSSGP_PDUT_RA_CAPA_UPDATE: - case BSSGP_PDUT_FLOW_CONTROL_MS: - 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_ACK: - case BSSGP_PDUT_DELETE_BSS_PFC_ACK: - case BSSGP_PDUT_FLOW_CONTROL_PFC: - case BSSGP_PDUT_DELETE_BSS_PFC_REQ: - case BSSGP_PDUT_PS_HO_REQUIRED: - case BSSGP_PDUT_PS_HO_REQUEST_ACK: - case BSSGP_PDUT_PS_HO_REQUEST_NACK: - case BSSGP_PDUT_PS_HO_COMPLETE: - case BSSGP_PDUT_PS_HO_CANCEL: - /* We can route based on TLLI-NRI */ - tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, &tlli, false); - break; - case BSSGP_PDUT_RADIO_STATUS: - if (TLVP_PRESENT(&tp, BSSGP_IE_TLLI)) { - tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, &tlli, false); - } else if (TLVP_PRESENT(&tp, BSSGP_IE_TMSI)) { - /* we treat the TMSI like a TLLI and extract the NRI from it */ - tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TMSI)); - /* Convert the TMSI into a FOREIGN TLLI so it is routed appropriately */ - tlli = gprs_tmsi2tlli(tlli, TLLI_FOREIGN); - rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, &tlli, false); - } else if (TLVP_PRESENT(&tp, BSSGP_IE_IMSI)) { - /* FIXME: Use the IMSI as selector? */ - rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, NULL, false); - /* rc = gbprox_bss2sgsn_hashed(bss_bvc->cell, msg, NULL); */ - } else - LOGPBVC(bss_bvc, LOGL_ERROR, "Rx RADIO-STATUS without any of the conditional IEs\n"); - break; - case BSSGP_PDUT_DUMMY_PAGING_PS_RESP: - case BSSGP_PDUT_PAGING_PS_REJECT: - { - /* Route according to IMSI<->NSE cache entry */ - struct osmo_mobile_identity mi; - const uint8_t *mi_data = TLVP_VAL(&tp, BSSGP_IE_IMSI); - uint8_t mi_len = TLVP_LEN(&tp, BSSGP_IE_IMSI); - osmo_mobile_identity_decode(&mi, mi_data, mi_len, false); - nse = gbproxy_nse_by_imsi(nse->cfg, mi.imsi); - if (nse) { - OSMO_ASSERT(nse->sgsn_facing); - rc = gbprox_relay2nse(msg, nse, ns_bvci); - } else { - LOGPBVC(bss_bvc, LOGL_ERROR, "Rx unmatched %s with IMSI %s\n", pdut_name, mi.imsi); - } - break; - } - case BSSGP_PDUT_FLOW_CONTROL_BVC: - osmo_fsm_inst_dispatch(bss_bvc->fi, BSSGP_BVCFSM_E_RX_FC_BVC, msg); - break; - case BSSGP_PDUT_STATUS: - /* TODO: Implement by inspecting the contained PDU */ - if (!TLVP_PRESENT(&tp, BSSGP_IE_PDU_IN_ERROR)) - break; - LOGPBVC(bss_bvc, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); - break; - } - - return 0; -} - -/* Receive an incoming PTP message from a SGSN-side NS-VC */ -static int gbprox_rx_ptp_from_sgsn(struct gbproxy_nse *nse, struct msgb *msg, uint16_t ns_bvci) -{ - struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); - const char *pdut_name = osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type); - struct gbproxy_bvc *sgsn_bvc, *bss_bvc; - struct tlv_parsed tp; - char log_pfx[32]; - int rc; - - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/SGSN)-BVC(%05u/??)", nse->nsei, ns_bvci); - - LOGP(DGPRS, LOGL_DEBUG, "%s Rx %s\n", log_pfx, pdut_name); - - if (ns_bvci == 0 || ns_bvci == 1) { - LOGP(DGPRS, LOGL_NOTICE, "%s BVCI is not PTP\n", log_pfx); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(bgph->pdu_type) & BSSGP_PDUF_PTP)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in PTP BVC\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(bgph->pdu_type) & BSSGP_PDUF_DL)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in downlink direction\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - sgsn_bvc = gbproxy_bvc_by_bvci(nse, ns_bvci); - if (!sgsn_bvc) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s - Didn't find BVC for for PTP message, discarding\n", - log_pfx, pdut_name); - rate_ctr_inc(&nse->cfg->ctrg-> ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &ns_bvci, msg); - } - - if (!bssgp_bvc_fsm_is_unblocked(sgsn_bvc->fi)) { - LOGPBVC(sgsn_bvc, LOGL_NOTICE, "Rx %s: Dropping on blocked BVC\n", pdut_name); - rate_ctr_inc(&sgsn_bvc->ctrg->ctr[GBPROX_PEER_CTR_DROPPED]); - return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &ns_bvci, msg); - } - - /* DL_UNITDATA has a different header than all other uplink PDUs */ - if (bgph->pdu_type == BSSGP_PDUT_DL_UNITDATA) { - const struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); - if (msgb_bssgp_len(msg) < sizeof(*budh)) - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, bgph->pdu_type, budh->data, - msgb_bssgp_len(msg) - sizeof(*budh), 0, 0, DGPRS, log_pfx); - /* populate TLLI from the fixed headser into the TLV-parsed array so later code - * doesn't have to worry where the TLLI came from */ - tp.lv[BSSGP_IE_TLLI].len = 4; - tp.lv[BSSGP_IE_TLLI].val = (const uint8_t *) &budh->tlli; - } else { - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, bgph->pdu_type, bgph->data, - msgb_bssgp_len(msg) - sizeof(*bgph), 0, 0, DGPRS, log_pfx); - } - if (rc < 0) { - rate_ctr_inc(&nse->cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); - return tx_status_from_tlvp(rc, msg); - } - /* hack to get both msg + tlv_parsed passed via osmo_fsm_inst_dispatch */ - msgb_bcid(msg) = (void *)&tp; - - OSMO_ASSERT(sgsn_bvc->cell); - bss_bvc = sgsn_bvc->cell->bss_bvc; - - switch (bgph->pdu_type) { - case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: - return osmo_fsm_inst_dispatch(sgsn_bvc->fi, BSSGP_BVCFSM_E_RX_FC_BVC_ACK, msg); - case BSSGP_PDUT_DUMMY_PAGING_PS: - case BSSGP_PDUT_PAGING_PS: - { - /* Cache the IMSI<->NSE to route PAGING REJECT */ - struct osmo_mobile_identity mi; - const uint8_t *mi_data = TLVP_VAL(&tp, BSSGP_IE_IMSI); - uint8_t mi_len = TLVP_LEN(&tp, BSSGP_IE_IMSI); - osmo_mobile_identity_decode(&mi, mi_data, mi_len, false); - gbproxy_imsi_cache_update(nse, mi.imsi); - break; - } - default: - break; - } - return gbprox_relay2peer(msg, bss_bvc, bss_bvc->bvci); - -} - -/*********************************************************************** - * BVC FSM call-backs - ***********************************************************************/ - -/* helper function to dispatch a FSM event to all SGSN-side BVC FSMs of a cell */ -static void dispatch_to_all_sgsn_bvc(struct gbproxy_cell *cell, uint32_t event, void *priv) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - struct gbproxy_bvc *sgsn_bvc = cell->sgsn_bvc[i]; - if (!sgsn_bvc) - continue; - osmo_fsm_inst_dispatch(sgsn_bvc->fi, event, priv); - } -} - -/* BVC FSM informs us about a BSS-side reset of the signaling BVC */ -static void bss_sig_bvc_reset_notif(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, - uint16_t cell_id, uint8_t cause, void *priv) -{ - struct gbproxy_bvc *sig_bvc = priv; - struct gbproxy_nse *nse = sig_bvc->nse; - struct gbproxy_bvc *ptp_bvc; - unsigned int i; - - /* BLOCK all SGSN-side PTP BVC within this NSE */ - hash_for_each(nse->bvcs, i, ptp_bvc, list) { - if (ptp_bvc == sig_bvc) - continue; - OSMO_ASSERT(ptp_bvc->cell); - - dispatch_to_all_sgsn_bvc(ptp_bvc->cell, BSSGP_BVCFSM_E_REQ_BLOCK, &cause); - } - - /* Delete all BSS-side PTP BVC within this NSE */ - gbproxy_cleanup_bvcs(nse, 0); - - /* TODO: we keep the "CELL" around for now, re-connecting it to - * any (later) new PTP-BVC for that BVCI. Not sure if that's the - * best idea ? */ -} - -/* forward declaration */ -static const struct bssgp_bvc_fsm_ops sgsn_ptp_bvc_fsm_ops; - -static const struct bssgp_bvc_fsm_ops bss_sig_bvc_fsm_ops = { - .reset_notification = bss_sig_bvc_reset_notif, -}; - -/* BVC FSM informs us about a BSS-side reset of a PTP BVC */ -static void bss_ptp_bvc_reset_notif(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, - uint16_t cell_id, uint8_t cause, void *priv) -{ - struct gbproxy_bvc *bvc = priv; - struct gbproxy_config *cfg = bvc->nse->cfg; - struct gbproxy_nse *sgsn_nse; - unsigned int i; - - OSMO_ASSERT(bvci != 0); - - if (!bvc->cell) { - /* see if we have a CELL dangling around */ - bvc->cell = gbproxy_cell_by_bvci(cfg, bvci); - if (bvc->cell) { - /* the CELL already exists. This means either it * was created before at an - * earlier PTP BVC-RESET, or that there are non-unique BVCIs and hence a - * malconfiguration */ - if (bvc->cell->bss_bvc) { - LOGPBVC(bvc, LOGL_NOTICE, "Rx BVC-RESET via this NSE, but CELL already " - "has BVC on NSEI=%05u\n", bvc->cell->bss_bvc->nse->nsei); - LOGPBVC(bvc->cell->bss_bvc, LOGL_NOTICE, "Destroying due to conflicting " - "BVCI configuration (new NSEI=%05u)!\n", bvc->nse->nsei); - gbproxy_bvc_free(bvc->cell->bss_bvc); - } - bvc->cell->bss_bvc = bvc; - } - } - - if (!bvc->cell) { - /* if we end up here, it means this is the first time we received a BVC-RESET - * for this BVC. We need to create the 'cell' data structure and the SGSN-side - * BVC counterparts */ - - bvc->cell = gbproxy_cell_alloc(cfg, bvci); - OSMO_ASSERT(bvc->cell); - memcpy(bvc->cell->ra, bvc->ra, sizeof(bvc->cell->ra)); - - /* link us to the cell and vice-versa */ - bvc->cell->bss_bvc = bvc; - } - - /* allocate (any missing) SGSN-side BVCs within the cell, and reset them */ - hash_for_each(cfg->sgsn_nses, i, sgsn_nse, list) { - struct gbproxy_bvc *sgsn_bvc = gbproxy_bvc_by_bvci(sgsn_nse, bvci); - if (sgsn_bvc) - OSMO_ASSERT(sgsn_bvc->cell == bvc->cell || !sgsn_bvc->cell); - - if (!sgsn_bvc) { - sgsn_bvc = gbproxy_bvc_alloc(sgsn_nse, bvci); - OSMO_ASSERT(sgsn_bvc); - - sgsn_bvc->cell = bvc->cell; - memcpy(sgsn_bvc->ra, bvc->cell->ra, sizeof(sgsn_bvc->ra)); - sgsn_bvc->fi = bssgp_bvc_fsm_alloc_ptp_bss(sgsn_bvc, cfg->nsi, sgsn_nse->nsei, - bvci, ra_id, cell_id); - OSMO_ASSERT(sgsn_bvc->fi); - bssgp_bvc_fsm_set_ops(sgsn_bvc->fi, &sgsn_ptp_bvc_fsm_ops, sgsn_bvc); - - gbproxy_cell_add_sgsn_bvc(bvc->cell, sgsn_bvc); - } - } - - /* Trigger outbound BVC-RESET procedure toward each SGSN */ - dispatch_to_all_sgsn_bvc(bvc->cell, BSSGP_BVCFSM_E_REQ_RESET, &cause); -} - -/* BVC FSM informs us about a BSS-side FSM state change */ -static void bss_ptp_bvc_state_chg_notif(uint16_t nsei, uint16_t bvci, int old_state, int state, void *priv) -{ - struct gbproxy_bvc *bvc = priv; - struct gbproxy_cell *cell = bvc->cell; - uint8_t cause = bssgp_bvc_fsm_get_block_cause(bvc->fi); - - /* we have just been created but due to callback ordering the cell is not associated */ - if (!cell) - return; - - switch (state) { - case BSSGP_BVCFSM_S_BLOCKED: - /* block the corresponding SGSN-side PTP BVCs */ - dispatch_to_all_sgsn_bvc(cell, BSSGP_BVCFSM_E_REQ_BLOCK, &cause); - break; - case BSSGP_BVCFSM_S_UNBLOCKED: - /* unblock the corresponding SGSN-side PTP BVCs */ - dispatch_to_all_sgsn_bvc(cell, BSSGP_BVCFSM_E_REQ_UNBLOCK, NULL); - break; - } -} - -/* BVC FSM informs us about BVC-FC PDU receive */ -static void bss_ptp_bvc_fc_bvc(uint16_t nsei, uint16_t bvci, const struct bssgp2_flow_ctrl *fc, void *priv) -{ - struct bssgp2_flow_ctrl fc_reduced; - struct gbproxy_bvc *bss_bvc = priv; - struct gbproxy_cell *cell; - struct gbproxy_config *cfg; - - OSMO_ASSERT(bss_bvc); - OSMO_ASSERT(fc); - - cell = bss_bvc->cell; - if (!cell) - return; - - cfg = cell->cfg; - - /* reduce / scale according to configuration to make sure we only advertise a fraction - * of the capacity to each of the SGSNs in the pool */ - fc_reduced = *fc; - fc_reduced.bucket_size_max = (fc->bucket_size_max * cfg->pool.bvc_fc_ratio) / 100; - fc_reduced.bucket_leak_rate = (fc->bucket_leak_rate * cfg->pool.bvc_fc_ratio) / 100; - /* we don't modify the per-MS related values as any single MS is only served by one SGSN */ - - dispatch_to_all_sgsn_bvc(cell, BSSGP_BVCFSM_E_REQ_FC_BVC, (void *) &fc_reduced); -} - -static const struct bssgp_bvc_fsm_ops bss_ptp_bvc_fsm_ops = { - .reset_notification = bss_ptp_bvc_reset_notif, - .state_chg_notification = bss_ptp_bvc_state_chg_notif, - .rx_fc_bvc = bss_ptp_bvc_fc_bvc, -}; - -/* BVC FSM informs us about a SGSN-side reset of a PTP BVC */ -static void sgsn_ptp_bvc_reset_notif(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, - uint16_t cell_id, uint8_t cause, void *priv) -{ - struct gbproxy_bvc *bvc = priv; - - if (!bvc->cell) { - LOGPBVC(bvc, LOGL_ERROR, "RESET of PTP BVC on SGSN side for which we have no BSS?\n"); - return; - } - - OSMO_ASSERT(bvc->cell->bss_bvc); - - /* request reset of BSS-facing PTP-BVC */ - osmo_fsm_inst_dispatch(bvc->cell->bss_bvc->fi, BSSGP_BVCFSM_E_REQ_RESET, &cause); -} - -static const struct bssgp_bvc_fsm_ops sgsn_ptp_bvc_fsm_ops = { - .reset_notification = sgsn_ptp_bvc_reset_notif, -}; - -/* BVC FSM informs us about a SGSN-side reset of the signaling BVC */ -static void sgsn_sig_bvc_reset_notif(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, - uint16_t cell_id, uint8_t cause, void *priv) -{ - struct gbproxy_bvc *bvc = priv; - struct gbproxy_config *cfg = bvc->nse->cfg; - struct gbproxy_nse *bss_nse; - unsigned int i; - - /* delete all SGSN-side PTP BVC for this SGSN */ - gbproxy_cleanup_bvcs(bvc->nse, 0); - /* FIXME: what to do about the cells? */ - /* FIXME: do we really want to RESET all signaling BVC on the BSS and affect all other SGSN? */ - - /* we need to trigger generating a reset procedure towards each BSS side signaling BVC */ - hash_for_each(cfg->bss_nses, i, bss_nse, list) { - struct gbproxy_bvc *bss_bvc = gbproxy_bvc_by_bvci(bss_nse, 0); - if (!bss_bvc) { - LOGPNSE(bss_nse, LOGL_ERROR, "Doesn't have BVC with BVCI=0 ?!?\n"); - continue; - } - osmo_fsm_inst_dispatch(bss_bvc->fi, BSSGP_BVCFSM_E_REQ_RESET, &cause); - } -} - -const struct bssgp_bvc_fsm_ops sgsn_sig_bvc_fsm_ops = { - .reset_notification = sgsn_sig_bvc_reset_notif, -}; - -/*********************************************************************** - * Signaling BVC handling - ***********************************************************************/ - -/* process a BVC-RESET message from the BSS side */ -static int rx_bvc_reset_from_bss(struct gbproxy_nse *nse, struct msgb *msg, struct tlv_parsed *tp) -{ - struct gbproxy_bvc *from_bvc = NULL; - uint16_t bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); - uint32_t features = 0; // FIXME: make configurable - - LOGPNSE(nse, LOGL_INFO, "Rx BVC-RESET (BVCI=%05u)\n", bvci); - - if (bvci == 0) { - /* If we receive a BVC reset on the signalling endpoint, we - * don't want the SGSN to reset, as the signalling endpoint - * is common for all point-to-point BVCs (and thus all BTS) */ - - from_bvc = gbproxy_bvc_by_bvci(nse, 0); - if (!from_bvc) { - from_bvc = gbproxy_bvc_alloc(nse, 0); - OSMO_ASSERT(from_bvc); - from_bvc->fi = bssgp_bvc_fsm_alloc_sig_sgsn(from_bvc, nse->cfg->nsi, nse->nsei, features); - if (!from_bvc->fi) { - LOGPNSE(nse, LOGL_ERROR, "Cannot allocate SIG-BVC FSM\n"); - gbproxy_bvc_free(from_bvc); - return -ENOMEM; - } - bssgp_bvc_fsm_set_ops(from_bvc->fi, &bss_sig_bvc_fsm_ops, from_bvc); - } - } else { - from_bvc = gbproxy_bvc_by_bvci(nse, bvci); - if (!from_bvc) { - /* if a PTP-BVC is reset, and we don't know that - * PTP-BVCI yet, we should allocate a new bvc */ - from_bvc = gbproxy_bvc_alloc(nse, bvci); - OSMO_ASSERT(from_bvc); - from_bvc->fi = bssgp_bvc_fsm_alloc_ptp_sgsn(from_bvc, nse->cfg->nsi, - nse->nsei, bvci); - if (!from_bvc->fi) { - LOGPNSE(nse, LOGL_ERROR, "Cannot allocate SIG-BVC FSM\n"); - gbproxy_bvc_free(from_bvc); - return -ENOMEM; - } - bssgp_bvc_fsm_set_ops(from_bvc->fi, &bss_ptp_bvc_fsm_ops, from_bvc); - } -#if 0 - /* Could have moved to a different NSE */ - if (!check_bvc_nsei(from_bvc, nsei)) { - LOGPBVC(from_bvc, LOGL_NOTICE, "moving bvc to NSE(%05u)\n", nsei); - - struct gbproxy_nse *nse_new = gbproxy_nse_by_nsei(cfg, nsei, false); - if (!nse_new) { - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u) Got PtP BVC reset before signalling reset for " - "BVCI=%05u\n", bvci, nsei); - bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_STATE, NULL, msg); - return 0; - } - - /* Move bvc to different NSE */ - gbproxy_bvc_move(from_bvc, nse_new); - } -#endif - /* FIXME: do we need this, if it happens within FSM? */ - if (TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) { - struct gprs_ra_id raid; - /* We have a Cell Identifier present in this - * PDU, this means we can extend our local - * state information about this particular cell - * */ - memcpy(from_bvc->ra, TLVP_VAL(tp, BSSGP_IE_CELL_ID), sizeof(from_bvc->ra)); - gsm48_parse_ra(&raid, from_bvc->ra); - LOGPBVC(from_bvc, LOGL_INFO, "Cell ID %s\n", osmo_rai_name(&raid)); - } - } - /* hand into FSM for further processing */ - osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_RESET, msg); - return 0; -} - -/* Receive an incoming signalling message from a BSS-side NS-VC */ -static int gbprox_rx_sig_from_bss(struct gbproxy_nse *nse, struct msgb *msg, uint16_t ns_bvci) -{ - struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); - uint8_t pdu_type = bgph->pdu_type; - const char *pdut_name = osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type); - struct tlv_parsed tp; - int data_len = msgb_bssgp_len(msg) - sizeof(*bgph); - struct gbproxy_bvc *from_bvc = NULL; - char log_pfx[32]; - uint16_t ptp_bvci; - uint32_t tlli; - int rc; - - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/BSS)-BVC(%05u/??)", nse->nsei, ns_bvci); - - LOGP(DGPRS, LOGL_DEBUG, "%s Rx %s\n", log_pfx, pdut_name); - - if (ns_bvci != 0 && ns_bvci != 1) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s BVCI=%05u is not signalling\n", log_pfx, pdut_name, ns_bvci); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(pdu_type) & BSSGP_PDUF_SIG)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in signalling BVC\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(pdu_type) & BSSGP_PDUF_UL)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in uplink direction\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, pdu_type, bgph->data, data_len, 0, 0, - DGPRS, log_pfx); - if (rc < 0) { - rate_ctr_inc(&nse->cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); - return tx_status_from_tlvp(rc, msg); - } - /* hack to get both msg + tlv_parsed passed via osmo_fsm_inst_dispatch */ - msgb_bcid(msg) = (void *)&tp; - - /* special case handling for some PDU types */ - switch (pdu_type) { - case BSSGP_PDUT_BVC_RESET: - /* resolve or create gbproxy_bvc + handlei n BVC-FSM */ - return rx_bvc_reset_from_bss(nse, msg, &tp); - case BSSGP_PDUT_BVC_RESET_ACK: - ptp_bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - from_bvc = gbproxy_bvc_by_bvci(nse, ptp_bvci); - if (!from_bvc) - goto err_no_bvc; - return osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_RESET_ACK, msg); - case BSSGP_PDUT_BVC_BLOCK: - ptp_bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - from_bvc = gbproxy_bvc_by_bvci(nse, ptp_bvci); - if (!from_bvc) - goto err_no_bvc; - return osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_BLOCK, msg); - case BSSGP_PDUT_BVC_UNBLOCK: - ptp_bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - from_bvc = gbproxy_bvc_by_bvci(nse, ptp_bvci); - if (!from_bvc) - goto err_no_bvc; - return osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_UNBLOCK, msg); - case BSSGP_PDUT_SUSPEND: - case BSSGP_PDUT_RESUME: - { - struct gbproxy_sgsn *sgsn; - - tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - sgsn = gbproxy_select_sgsn(nse->cfg, &tlli); - if (!sgsn) { - LOGP(DGPRS, LOGL_ERROR, "Could not find any SGSN for TLLI, dropping message!\n"); - rc = -EINVAL; - break; - } - - gbproxy_tlli_cache_update(nse, tlli); - - rc = gbprox_relay2nse(msg, sgsn->nse, 0); -#if 0 - /* TODO: Validate the RAI for consistency with the RAI - * we expect for any of the BVC within this BSS side NSE */ - memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA), sizeof(from_bvc->ra)); - gsm48_parse_ra(&raid, from_bvc->ra); -#endif - break; - } - case BSSGP_PDUT_STATUS: - /* FIXME: inspect the erroneous PDU IE (if any) and check - * if we can extract a TLLI/RNI to route it to the correct SGSN */ - 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: - /* FIXME: route based in RIM Routing IE */ - rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); - break; - case BSSGP_PDUT_LLC_DISCARD: - case BSSGP_PDUT_FLUSH_LL_ACK: - /* route based on BVCI + TLLI */ - ptp_bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - from_bvc = gbproxy_bvc_by_bvci(nse, ptp_bvci); - if (!from_bvc) - goto err_no_bvc; - gbprox_bss2sgsn_tlli(from_bvc->cell, msg, &tlli, true); - break; - case BSSGP_PDUT_PAGING_PS_REJECT: - case BSSGP_PDUT_DUMMY_PAGING_PS_RESP: - { - /* Route according to IMSI<->NSE cache entry */ - struct osmo_mobile_identity mi; - const uint8_t *mi_data = TLVP_VAL(&tp, BSSGP_IE_IMSI); - uint8_t mi_len = TLVP_LEN(&tp, BSSGP_IE_IMSI); - osmo_mobile_identity_decode(&mi, mi_data, mi_len, false); - nse = gbproxy_nse_by_imsi(nse->cfg, mi.imsi); - if (!nse) { - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); - } - OSMO_ASSERT(nse->sgsn_facing); - rc = gbprox_relay2nse(msg, nse, 0); - break; - } - default: - LOGPNSE(nse, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); - break; - } - - return rc; -err_no_bvc: - LOGPNSE(nse, LOGL_ERROR, "Rx %s: cannot find BVC for BVCI=%05u\n", pdut_name, ptp_bvci); - rate_ctr_inc(&nse->cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_NSEI]); - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); -} - -/* Receive paging request from SGSN, we need to relay to proper BSS */ -static int gbprox_rx_paging(struct gbproxy_nse *sgsn_nse, struct msgb *msg, const char *pdut_name, - struct tlv_parsed *tp, uint16_t ns_bvci, bool broadcast) -{ - struct gbproxy_config *cfg = sgsn_nse->cfg; - struct gbproxy_bvc *sgsn_bvc, *bss_bvc; - struct gbproxy_nse *nse; - unsigned int n_nses = 0; - int errctr = GBPROX_GLOB_CTR_PROTO_ERR_SGSN; - int i, j; - - /* FIXME: Handle paging logic to only page each matching NSE */ - - if (TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) { - uint16_t bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); - errctr = GBPROX_GLOB_CTR_OTHER_ERR; - sgsn_bvc = gbproxy_bvc_by_bvci(sgsn_nse, bvci); - if (!sgsn_bvc) { - LOGPNSE(sgsn_nse, LOGL_NOTICE, "Rx %s: unable to route: BVCI=%05u unknown\n", - pdut_name, bvci); - rate_ctr_inc(&cfg->ctrg->ctr[errctr]); - return -EINVAL; - } - LOGPBVC(sgsn_bvc, LOGL_INFO, "Rx %s: routing by BVCI\n", pdut_name); - return gbprox_relay2peer(msg, sgsn_bvc->cell->bss_bvc, ns_bvci); - } else if (TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) { - errctr = GBPROX_GLOB_CTR_INV_RAI; - /* iterate over all bvcs and dispatch the paging to each matching one */ - hash_for_each(cfg->bss_nses, i, nse, list) { - hash_for_each(nse->bvcs, j, bss_bvc, list) { - if (!memcmp(bss_bvc->ra, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA), 6)) { - LOGPNSE(nse, LOGL_INFO, "Rx %s: routing to NSE (RAI match)\n", - pdut_name); - gbprox_relay2peer(msg, bss_bvc, ns_bvci); - n_nses++; - /* Only send it once to each NSE */ - break; - } - } - } - } else if (TLVP_PRES_LEN(tp, BSSGP_IE_LOCATION_AREA, 5)) { - errctr = GBPROX_GLOB_CTR_INV_LAI; - /* iterate over all bvcs and dispatch the paging to each matching one */ - hash_for_each(cfg->bss_nses, i, nse, list) { - hash_for_each(nse->bvcs, j, bss_bvc, list) { - if (!memcmp(bss_bvc->ra, TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA), 5)) { - LOGPNSE(nse, LOGL_INFO, "Rx %s: routing to NSE (LAI match)\n", - pdut_name); - gbprox_relay2peer(msg, bss_bvc, ns_bvci); - n_nses++; - /* Only send it once to each NSE */ - break; - } - } - } - } else if (TLVP_PRES_LEN(tp, BSSGP_IE_BSS_AREA_ID, 1) || broadcast) { - /* iterate over all bvcs and dispatch the paging to each matching one */ - hash_for_each(cfg->bss_nses, i, nse, list) { - hash_for_each(nse->bvcs, j, bss_bvc, list) { - LOGPNSE(nse, LOGL_INFO, "Rx %s:routing to NSE (broadcast)\n", pdut_name); - gbprox_relay2peer(msg, bss_bvc, ns_bvci); - n_nses++; - /* Only send it once to each NSE */ - break; - } - } - } else { - LOGPNSE(sgsn_nse, LOGL_ERROR, "BSSGP PAGING: unable to route, missing IE\n"); - rate_ctr_inc(&cfg->ctrg->ctr[errctr]); - } - - if (n_nses == 0) { - LOGPNSE(sgsn_nse, LOGL_ERROR, "BSSGP PAGING: unable to route, no destination found\n"); - rate_ctr_inc(&cfg->ctrg->ctr[errctr]); - return -EINVAL; - } - return 0; -} - -/* Receive an incoming BVC-RESET message from the SGSN */ -static int rx_bvc_reset_from_sgsn(struct gbproxy_nse *nse, struct msgb *msg, struct tlv_parsed *tp, - uint16_t ns_bvci) -{ - uint16_t ptp_bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); - struct gbproxy_bvc *from_bvc; - - LOGPNSE(nse, LOGL_INFO, "Rx BVC-RESET (BVCI=%05u)\n", ptp_bvci); - - if (ptp_bvci == 0) { - from_bvc = gbproxy_bvc_by_bvci(nse, 0); - OSMO_ASSERT(from_bvc); - osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_RESET, msg); - } else { - from_bvc = gbproxy_bvc_by_bvci(nse, ptp_bvci); - if (!from_bvc) { - LOGPNSE(nse, LOGL_ERROR, "Rx BVC-RESET BVCI=%05u: Cannot find BVC\n", ptp_bvci); - rate_ctr_inc(&nse->cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &ptp_bvci, msg); - } - osmo_fsm_inst_dispatch(from_bvc->fi, BSSGP_BVCFSM_E_RX_RESET, msg); - } - - return 0; -} - -/* Receive an incoming signalling message from the SGSN-side NS-VC */ -static int gbprox_rx_sig_from_sgsn(struct gbproxy_nse *nse, struct msgb *msg, uint16_t ns_bvci) -{ - struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); - uint8_t pdu_type = bgph->pdu_type; - const char *pdut_name = osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type); - struct gbproxy_config *cfg = nse->cfg; - struct gbproxy_bvc *sgsn_bvc; - struct tlv_parsed tp; - int data_len; - uint16_t bvci; - char log_pfx[32]; - int rc = 0; - int cause; - int i; - bool paging_bc = false; - - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/SGSN)-BVC(%05u/??)", nse->nsei, ns_bvci); - - LOGP(DGPRS, LOGL_DEBUG, "%s Rx %s\n", log_pfx, pdut_name); - - if (ns_bvci != 0 && ns_bvci != 1) { - LOGP(DGPRS, LOGL_NOTICE, "%s BVCI=%05u is not signalling\n", log_pfx, ns_bvci); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(pdu_type) & BSSGP_PDUF_SIG)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in signalling BVC\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - if (!(bssgp_pdu_type_flags(pdu_type) & BSSGP_PDUF_DL)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in downlink direction\n", log_pfx, pdut_name); - return bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - } - - data_len = msgb_bssgp_len(msg) - sizeof(*bgph); - - rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, &tp, 1, pdu_type, bgph->data, data_len, 0, 0, - DGPRS, log_pfx); - if (rc < 0) { - rc = tx_status_from_tlvp(rc, msg); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]); - return rc; - } - /* hack to get both msg + tlv_parsed passed via osmo_fsm_inst_dispatch */ - msgb_bcid(msg) = (void *)&tp; - - switch (pdu_type) { - case BSSGP_PDUT_BVC_RESET: - /* resolve or create ggbproxy_bvc + handle in BVC-FSM */ - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - rc = rx_bvc_reset_from_sgsn(nse, msg, &tp, ns_bvci); - break; - case BSSGP_PDUT_BVC_RESET_ACK: - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); - if (!sgsn_bvc) - goto err_no_bvc; - rc = osmo_fsm_inst_dispatch(sgsn_bvc->fi, BSSGP_BVCFSM_E_RX_RESET_ACK, msg); - break; - case BSSGP_PDUT_BVC_BLOCK_ACK: - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); - if (!sgsn_bvc) - goto err_no_bvc; - rc = osmo_fsm_inst_dispatch(sgsn_bvc->fi, BSSGP_BVCFSM_E_RX_BLOCK_ACK, msg); - break; - case BSSGP_PDUT_BVC_UNBLOCK_ACK: - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); - if (!sgsn_bvc) - goto err_no_bvc; - rc = osmo_fsm_inst_dispatch(sgsn_bvc->fi, BSSGP_BVCFSM_E_RX_UNBLOCK_ACK, msg); - break; - case BSSGP_PDUT_FLUSH_LL: - /* simple case: BVCI IE is mandatory */ - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); - if (!sgsn_bvc) - goto err_no_bvc; - if (sgsn_bvc->cell && sgsn_bvc->cell->bss_bvc) - rc = gbprox_relay2peer(msg, sgsn_bvc->cell->bss_bvc, ns_bvci); - break; - case BSSGP_PDUT_DUMMY_PAGING_PS: - /* Routing area is optional in dummy paging and we have nothing else to go by - * so in case it is missing we need to broadcast the paging */ - paging_bc = true; - /* fall through */ - case BSSGP_PDUT_PAGING_PS: - { - /* Cache the IMSI<->NSE to route PAGING REJECT */ - struct osmo_mobile_identity mi; - const uint8_t *mi_data = TLVP_VAL(&tp, BSSGP_IE_IMSI); - uint8_t mi_len = TLVP_LEN(&tp, BSSGP_IE_IMSI); - osmo_mobile_identity_decode(&mi, mi_data, mi_len, false); - gbproxy_imsi_cache_update(nse, mi.imsi); - /* fall through */ - } - case BSSGP_PDUT_PAGING_CS: - /* process the paging request (LAI/RAI lookup) */ - rc = gbprox_rx_paging(nse, msg, pdut_name, &tp, ns_bvci, paging_bc); - break; - case BSSGP_PDUT_STATUS: - /* Some exception has occurred */ - cause = *TLVP_VAL(&tp, BSSGP_IE_CAUSE); - LOGPNSE(nse, LOGL_NOTICE, "Rx STATUS cause=0x%02x(%s) ", cause, - bssgp_cause_str(cause)); - if (TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) { - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - LOGPC(DGPRS, LOGL_NOTICE, "BVCI=%05u\n", bvci); - sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); - /* don't send STATUS in response to STATUS if !bvc */ - if (sgsn_bvc && sgsn_bvc->cell && sgsn_bvc->cell->bss_bvc) - rc = gbprox_relay2peer(msg, sgsn_bvc->cell->bss_bvc, ns_bvci); - } else - LOGPC(DGPRS, LOGL_NOTICE, "\n"); - break; - /* those only exist in the SGSN -> BSS direction */ - case BSSGP_PDUT_SUSPEND_ACK: - case BSSGP_PDUT_SUSPEND_NACK: - case BSSGP_PDUT_RESUME_ACK: - case BSSGP_PDUT_RESUME_NACK: - { - struct gbproxy_nse *nse_peer; - uint32_t tlli = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TLLI)); - - nse_peer = gbproxy_nse_by_tlli(cfg, tlli); - if (!nse_peer) { - LOGPNSE(nse, LOGL_ERROR, "Rx %s: Cannot find NSE\n", pdut_name); - /* TODO: Counter */ - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); - } - /* Delete the entry after we're done */ - gbproxy_tlli_cache_remove(cfg, tlli); - LOGPNSE(nse_peer, LOGL_DEBUG, "Rx %s: forwarding\n", pdut_name); - gbprox_relay2nse(msg, nse_peer, ns_bvci); - break; - } - case BSSGP_PDUT_SGSN_INVOKE_TRACE: - case BSSGP_PDUT_OVERLOAD: - LOGPNSE(nse, LOGL_DEBUG, "Rx %s: broadcasting\n", pdut_name); - /* broadcast to all BSS-side bvcs */ - hash_for_each(cfg->bss_nses, i, nse, list) { - gbprox_relay2nse(msg, nse, 0); - } - 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: - /* FIXME: route based in RIM Routing IE */ - rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); - break; - default: - LOGPNSE(nse, LOGL_NOTICE, "Rx %s: Not supported\n", pdut_name); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]); - rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); - break; - } - - return rc; - -err_no_bvc: - LOGPNSE(nse, LOGL_ERROR, "Rx %s: Cannot find BVC\n", pdut_name); - rate_ctr_inc(&cfg->ctrg-> ctr[GBPROX_GLOB_CTR_INV_RAI]); - return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg); -} - - -/*********************************************************************** - * libosmogb NS/BSSGP integration - ***********************************************************************/ - -int gbprox_bssgp_send_cb(void *ctx, struct msgb *msg) -{ - int rc; - struct gbproxy_config *cfg = (struct gbproxy_config *) ctx; - struct gprs_ns2_inst *nsi = cfg->nsi; - struct osmo_gprs_ns2_prim nsp = {}; - - nsp.bvci = msgb_bvci(msg); - nsp.nsei = msgb_nsei(msg); - - osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg); - rc = gprs_ns2_recv_prim(nsi, &nsp.oph); - - return rc; -} - -/* Main input function for Gb proxy */ -int gbprox_rcvmsg(void *ctx, struct msgb *msg) -{ - struct gbproxy_config *cfg = (struct gbproxy_config *) ctx; - uint16_t ns_bvci = msgb_bvci(msg); - uint16_t nsei = msgb_nsei(msg); - struct gbproxy_nse *nse; - - /* ensure minimum length to decode PCU type */ - if (msgb_bssgp_len(msg) < sizeof(struct bssgp_normal_hdr)) - return bssgp_tx_status(BSSGP_CAUSE_SEM_INCORR_PDU, NULL, msg); - - nse = gbproxy_nse_by_nsei(cfg, nsei, NSE_F_SGSN); - if (nse) { - if (ns_bvci == 0 || ns_bvci == 1) - return gbprox_rx_sig_from_sgsn(nse, msg, ns_bvci); - else - return gbprox_rx_ptp_from_sgsn(nse, msg, ns_bvci); - } - - nse = gbproxy_nse_by_nsei(cfg, nsei, NSE_F_BSS); - if (!nse) { - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u/BSS) not known -> allocating\n", nsei); - nse = gbproxy_nse_alloc(cfg, nsei, false); - } - if (nse) { - if (ns_bvci == 0 || ns_bvci == 1) - return gbprox_rx_sig_from_bss(nse, msg, ns_bvci); - else - return gbprox_rx_ptp_from_bss(nse, msg, ns_bvci); - } - - return 0; -} - -/* TODO: What about handling: - * GPRS_NS2_AFF_CAUSE_VC_FAILURE, - GPRS_NS2_AFF_CAUSE_VC_RECOVERY, - GPRS_NS2_AFF_CAUSE_FAILURE, - GPRS_NS2_AFF_CAUSE_RECOVERY, - osmocom own causes - GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED, - GPRS_NS2_AFF_CAUSE_SNS_FAILURE, - */ - -void gprs_ns_prim_status_cb(struct gbproxy_config *cfg, struct osmo_gprs_ns2_prim *nsp) -{ - /* TODO: bss nsei available/unavailable bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, nsvc->nsei, bvc->bvci, 0); - * TODO: sgsn nsei available/unavailable - */ - - struct gbproxy_bvc *bvc; - struct gbproxy_nse *sgsn_nse; - - switch (nsp->u.status.cause) { - case GPRS_NS2_AFF_CAUSE_SNS_FAILURE: - case GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED: - break; - - case GPRS_NS2_AFF_CAUSE_RECOVERY: - LOGP(DGPRS, LOGL_NOTICE, "NS-NSE %d became available\n", nsp->nsei); - sgsn_nse = gbproxy_nse_by_nsei(cfg, nsp->nsei, NSE_F_SGSN); - if (sgsn_nse) { - uint8_t cause = BSSGP_CAUSE_OML_INTERV; - bvc = gbproxy_bvc_by_bvci(sgsn_nse, 0); - if (bvc) - osmo_fsm_inst_dispatch(bvc->fi, BSSGP_BVCFSM_E_REQ_RESET, &cause); - } - break; - case GPRS_NS2_AFF_CAUSE_FAILURE: -#if 0 - if (gbproxy_is_sgsn_nsei(cfg, nsp->nsei)) { - /* sgsn */ - /* TODO: BSVC: block all PtP towards bss */ - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_RESTART_RESET_SGSN]); - } else { - /* bss became unavailable - * TODO: Block all BVC belonging to that NSE */ - bvc = gbproxy_bvc_by_nsei(cfg, nsp->nsei); - if (!bvc) { - /* TODO: use primitive name + status cause name */ - LOGP(DGPRS, LOGL_NOTICE, "Received ns2 primitive %d for unknown bvc NSEI=%u\n", - nsp->u.status.cause, nsp->nsei); - break; - } - - if (!bvc->blocked) - break; - hash_for_each(cfg->sgsn_nses, _sgsn, sgsn_nse, list) { - bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, sgsn_nse->nsei, bvc->bvci, 0); - } - } -#endif - LOGP(DGPRS, LOGL_NOTICE, "NS-NSE %d became unavailable\n", nsp->nsei); - break; - default: - LOGP(DGPRS, LOGL_NOTICE, "NS: Unknown NS-STATUS.ind cause=%s from NS\n", - gprs_ns2_aff_cause_prim_str(nsp->u.status.cause)); - break; - } -} - -/* called by the ns layer */ -int gprs_ns2_prim_cb(struct osmo_prim_hdr *oph, void *ctx) -{ - struct osmo_gprs_ns2_prim *nsp; - struct gbproxy_config *cfg = (struct gbproxy_config *) ctx; - uintptr_t bvci; - int rc = 0; - - if (oph->sap != SAP_NS) - return 0; - - nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph); - - if (oph->operation != PRIM_OP_INDICATION) { - LOGP(DGPRS, LOGL_NOTICE, "NS: Unexpected primitive operation %s from NS\n", - get_value_string(osmo_prim_op_names, oph->operation)); - return 0; - } - - switch (oph->primitive) { - case GPRS_NS2_PRIM_UNIT_DATA: - - /* hand the message into the BSSGP implementation */ - msgb_bssgph(oph->msg) = oph->msg->l3h; - msgb_bvci(oph->msg) = nsp->bvci; - msgb_nsei(oph->msg) = nsp->nsei; - bvci = nsp->bvci | BVC_LOG_CTX_FLAG; - - log_set_context(LOG_CTX_GB_BVC, (void *)bvci); - rc = gbprox_rcvmsg(cfg, oph->msg); - msgb_free(oph->msg); - break; - case GPRS_NS2_PRIM_STATUS: - gprs_ns_prim_status_cb(cfg, nsp); - break; - default: - LOGP(DGPRS, LOGL_NOTICE, "NS: Unknown prim %s %s from NS\n", - gprs_ns2_prim_str(oph->primitive), - get_value_string(osmo_prim_op_names, oph->operation)); - break; - } - - return rc; -} - -void gbprox_reset(struct gbproxy_config *cfg) -{ - struct gbproxy_nse *nse; - struct hlist_node *ntmp; - int i, j; - - hash_for_each_safe(cfg->bss_nses, i, ntmp, nse, list) { - struct gbproxy_bvc *bvc; - struct hlist_node *tmp; - hash_for_each_safe(nse->bvcs, j, tmp, bvc, list) - gbproxy_bvc_free(bvc); - - gbproxy_nse_free(nse); - } - /* FIXME: cells */ - /* FIXME: SGSN side BVCs (except signaling) */ - - rate_ctr_group_free(cfg->ctrg); - gbproxy_init_config(cfg); -} - -static void tlli_cache_cleanup(void *data) -{ - struct gbproxy_config *cfg = data; - gbproxy_tlli_cache_cleanup(cfg); - - /* TODO: Disable timer when cache is empty */ - osmo_timer_schedule(&cfg->tlli_cache.timer, 2, 0); -} - -static void imsi_cache_cleanup(void *data) -{ - struct gbproxy_config *cfg = data; - gbproxy_imsi_cache_cleanup(cfg); - - /* TODO: Disable timer when cache is empty */ - osmo_timer_schedule(&cfg->imsi_cache.timer, 2, 0); -} - -int gbproxy_init_config(struct gbproxy_config *cfg) -{ - struct timespec tp; - - /* by default we advertise 100% of the BSS-side capacity to _each_ SGSN */ - cfg->pool.bvc_fc_ratio = 100; - cfg->pool.null_nri_ranges = osmo_nri_ranges_alloc(cfg); - /* TODO: Make configurable */ - cfg->tlli_cache.timeout = 10; - cfg->imsi_cache.timeout = 10; - - hash_init(cfg->bss_nses); - hash_init(cfg->sgsn_nses); - hash_init(cfg->cells); - hash_init(cfg->tlli_cache.entries); - INIT_LLIST_HEAD(&cfg->sgsns); - - osmo_timer_setup(&cfg->tlli_cache.timer, tlli_cache_cleanup, cfg); - osmo_timer_schedule(&cfg->tlli_cache.timer, 2, 0); - - /* We could also combine both timers */ - osmo_timer_setup(&cfg->imsi_cache.timer, imsi_cache_cleanup, cfg); - osmo_timer_schedule(&cfg->imsi_cache.timer, 2, 0); - - cfg->ctrg = rate_ctr_group_alloc(tall_sgsn_ctx, &global_ctrg_desc, 0); - if (!cfg->ctrg) { - LOGP(DGPRS, LOGL_ERROR, "Cannot allocate global counter group!\n"); - return -1; - } - osmo_clock_gettime(CLOCK_REALTIME, &tp); - osmo_fsm_log_timeouts(true); - - return 0; -} \ No newline at end of file diff --git a/src/gbproxy/gb_proxy_ctrl.c b/src/gbproxy/gb_proxy_ctrl.c deleted file mode 100644 index 456163466..000000000 --- a/src/gbproxy/gb_proxy_ctrl.c +++ /dev/null @@ -1,137 +0,0 @@ -/* Control Interface Implementation for the Gb-proxy */ -/* - * (C) 2018 by sysmocom s.f.m.c. GmbH - * All Rights Reserved - * - * Author: Daniel Willmann - * - * 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 . - * - */ - -#include - - -#include -#include - -#include -#include -#include -#include - -extern vector ctrl_node_vec; - -struct nsvc_cb_data { - struct ctrl_cmd *cmd; - uint16_t nsei; - bool is_sgsn; -}; - -static int ctrl_nsvc_state_cb(struct gprs_ns2_vc *nsvc, void *ctx) { - struct nsvc_cb_data *data = (struct nsvc_cb_data *)ctx; - struct ctrl_cmd *cmd = (struct ctrl_cmd *)data->cmd; - - cmd->reply = talloc_asprintf_append(cmd->reply, "%u,%s,%s,%s\n", - data->nsei, gprs_ns2_ll_str(nsvc), gprs_ns2_nsvc_state_name(nsvc), - data->is_sgsn ? "SGSN" : "BSS" ); - - return 0; -} - -static int get_nsvc_state(struct ctrl_cmd *cmd, void *data) -{ - struct gbproxy_config *cfg = data; - struct gprs_ns2_inst *nsi = cfg->nsi; - struct gprs_ns2_nse *nse; - struct gbproxy_nse *nse_peer; - int i; - - cmd->reply = talloc_strdup(cmd, ""); - - /* NS-VCs for SGSN */ - hash_for_each(cfg->sgsn_nses, i, nse_peer, list) { - nse = gprs_ns2_nse_by_nsei(nsi, nse_peer->nsei); - if (nse) - gprs_ns2_nse_foreach_nsvc(nse, &ctrl_nsvc_state_cb, cmd); - } - - /* NS-VCs for BSS peers */ - hash_for_each(cfg->bss_nses, i, nse_peer, list) { - nse = gprs_ns2_nse_by_nsei(nsi, nse_peer->nsei); - if (nse) - gprs_ns2_nse_foreach_nsvc(nse, &ctrl_nsvc_state_cb, cmd); - } - - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_RO(nsvc_state, "nsvc-state"); - -static int get_gbproxy_state(struct ctrl_cmd *cmd, void *data) -{ - struct gbproxy_config *cfg = data; - struct gbproxy_nse *nse_peer; - int i, j; - - cmd->reply = talloc_strdup(cmd, ""); - - hash_for_each(cfg->bss_nses, i, nse_peer, list) { - struct gbproxy_bvc *bvc; - hash_for_each(nse_peer->bvcs, j, bvc, list) { - struct gprs_ra_id raid; - gsm48_parse_ra(&raid, bvc->ra); - - cmd->reply = talloc_asprintf_append(cmd->reply, "%u,%u,%u,%u,%u,%u,%s\n", - nse_peer->nsei, bvc->bvci, - raid.mcc, raid.mnc, - raid.lac, raid.rac, - osmo_fsm_inst_state_name(bvc->fi)); - } - } - - return CTRL_CMD_REPLY; -} - -CTRL_CMD_DEFINE_RO(gbproxy_state, "gbproxy-state"); - -static int get_num_peers(struct ctrl_cmd *cmd, void *data) -{ - struct gbproxy_config *cfg = data; - struct gbproxy_nse *nse_peer; - struct gbproxy_bvc *bvc; - uint32_t count = 0; - int i, j; - - hash_for_each(cfg->bss_nses, i, nse_peer, list) { - hash_for_each(nse_peer->bvcs, j, bvc, list) - count++; - } - - cmd->reply = talloc_strdup(cmd, ""); - cmd->reply = talloc_asprintf_append(cmd->reply, "%u", count); - - return CTRL_CMD_REPLY; -} -CTRL_CMD_DEFINE_RO(num_peers, "number-of-peers"); - -int gb_ctrl_cmds_install(void) -{ - int rc = 0; - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_nsvc_state); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_gbproxy_state); - rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_num_peers); - - return rc; -} diff --git a/src/gbproxy/gb_proxy_main.c b/src/gbproxy/gb_proxy_main.c deleted file mode 100644 index b76e0fbb6..000000000 --- a/src/gbproxy/gb_proxy_main.c +++ /dev/null @@ -1,336 +0,0 @@ -/* NS-over-IP proxy */ - -/* (C) 2010 by Harald Welte - * (C) 2010 by On-Waves - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "../../bscconfig.h" - -#define _GNU_SOURCE -#include - -void *tall_sgsn_ctx; - -const char *openbsc_copyright = - "Copyright (C) 2010 Harald Welte and On-Waves\r\n" - "License AGPLv3+: GNU AGPL version 3 or later \r\n" - "This is free software: you are free to change and redistribute it.\r\n" - "There is NO WARRANTY, to the extent permitted by law.\r\n"; - -#define CONFIG_FILE_DEFAULT "osmo-gbproxy.cfg" -#define CONFIG_FILE_LEGACY "osmo_gbproxy.cfg" - -static char *config_file = NULL; -struct gbproxy_config *gbcfg; -static int daemonize = 0; - -/* Pointer to the SGSN peer */ -extern struct gbprox_peer *gbprox_peer_sgsn; - -static void signal_handler(int signum) -{ - fprintf(stdout, "signal %u received\n", signum); - - switch (signum) { - case SIGINT: - case SIGTERM: - osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); - sleep(1); - exit(0); - break; - case SIGABRT: - /* in case of abort, we want to obtain a talloc report and - * then run default SIGABRT handler, who will generate coredump - * and abort the process. abort() should do this for us after we - * return, but program wouldn't exit if an external SIGABRT is - * received. - */ - talloc_report(tall_vty_ctx, stderr); - talloc_report_full(tall_sgsn_ctx, stderr); - signal(SIGABRT, SIG_DFL); - raise(SIGABRT); - break; - case SIGUSR1: - talloc_report(tall_vty_ctx, stderr); - talloc_report_full(tall_sgsn_ctx, stderr); - break; - case SIGUSR2: - talloc_report_full(tall_vty_ctx, stderr); - break; - default: - break; - } -} - -static void print_usage() -{ - printf("Usage: bsc_hack\n"); -} - -static void print_help() -{ - printf(" Some useful help...\n"); - printf(" -h --help this text\n"); - printf(" -d option --debug=DNS:DGPRS,0:0 enable debugging\n"); - printf(" -D --daemonize Fork the process into a background daemon\n"); - printf(" -c --config-file filename The config file to use [%s]\n", CONFIG_FILE_DEFAULT); - printf(" -s --disable-color\n"); - printf(" -T --timestamp Prefix every log line with a timestamp\n"); - printf(" -V --version. Print the version.\n"); - printf(" -e --log-level number. Set a global loglevel.\n"); -} - -static void handle_options(int argc, char **argv) -{ - while (1) { - int option_index = 0, c; - static struct option long_options[] = { - { "help", 0, 0, 'h' }, - { "debug", 1, 0, 'd' }, - { "daemonize", 0, 0, 'D' }, - { "config-file", 1, 0, 'c' }, - { "disable-color", 0, 0, 's' }, - { "timestamp", 0, 0, 'T' }, - { "version", 0, 0, 'V' }, - { "log-level", 1, 0, 'e' }, - { 0, 0, 0, 0 } - }; - - c = getopt_long(argc, argv, "hd:Dc:sTVe:", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'h': - print_usage(); - print_help(); - exit(0); - case 's': - log_set_use_color(osmo_stderr_target, 0); - break; - case 'd': - log_parse_category_mask(osmo_stderr_target, optarg); - break; - case 'D': - daemonize = 1; - break; - case 'c': - config_file = optarg; - break; - case 'T': - log_set_print_timestamp(osmo_stderr_target, 1); - break; - case 'e': - log_set_log_level(osmo_stderr_target, atoi(optarg)); - break; - case 'V': - print_version(1); - exit(0); - break; - default: - break; - } - } - - if (argc > optind) { - fprintf(stderr, "Unsupported positional arguments on command line\n"); - exit(2); - } -} - -static struct vty_app_info vty_info = { - .name = "OsmoGbProxy", - .version = PACKAGE_VERSION, -}; - -/* default categories */ -static struct log_info_cat gprs_categories[] = { - [DGPRS] = { - .name = "DGPRS", - .description = "GPRS Packet Service", - .enabled = 1, .loglevel = LOGL_DEBUG, - }, - [DNS] = { - .name = "DNS", - .description = "GPRS Network Service (NS)", - .enabled = 1, .loglevel = LOGL_INFO, - }, - [DOBJ] = { - .name = "DOBJ", - .description = "GbProxy object allocation/release", - .enabled = 1, - .color = "\033[38;5;121m" - }, -}; - -static const struct log_info gprs_log_info = { - .filter_fn = gprs_log_filter_fn, - .cat = gprs_categories, - .num_cat = ARRAY_SIZE(gprs_categories), -}; - -static bool file_exists(const char *path) -{ - struct stat sb; - return stat(path, &sb) ? false : true; -} - -int gbprox_bssgp_send_cb(void *ctx, struct msgb *msg); - -int main(int argc, char **argv) -{ - int rc; - struct ctrl_handle *ctrl; - - tall_sgsn_ctx = talloc_named_const(NULL, 0, "nsip_proxy"); - msgb_talloc_ctx_init(tall_sgsn_ctx, 0); - vty_info.tall_ctx = tall_sgsn_ctx; - - signal(SIGINT, &signal_handler); - signal(SIGTERM, &signal_handler); - signal(SIGABRT, &signal_handler); - signal(SIGUSR1, &signal_handler); - signal(SIGUSR2, &signal_handler); - osmo_init_ignore_signals(); - - osmo_init_logging2(tall_sgsn_ctx, &gprs_log_info); - - vty_info.copyright = openbsc_copyright; - vty_init(&vty_info); - logging_vty_add_cmds(); - osmo_talloc_vty_add_cmds(); - osmo_stats_vty_add_cmds(); - osmo_fsm_vty_add_cmds(); - gbproxy_vty_init(); - - handle_options(argc, argv); - - /* Backwards compatibility: for years, the default config file name was - * osmo_gbproxy.cfg. All other Osmocom programs use osmo-*.cfg with a - * dash. To be able to use the new config file name without breaking - * previous setups that might rely on the legacy default config file - * name, we need to look for the old config file if no -c option was - * passed AND no file exists with the new default file name. */ - if (!config_file) { - /* No -c option was passed */ - if (file_exists(CONFIG_FILE_LEGACY) - && !file_exists(CONFIG_FILE_DEFAULT)) - config_file = CONFIG_FILE_LEGACY; - else - config_file = CONFIG_FILE_DEFAULT; - } - - rate_ctr_init(tall_sgsn_ctx); - osmo_stats_init(tall_sgsn_ctx); - - gbcfg = talloc_zero(tall_sgsn_ctx, struct gbproxy_config); - if (!gbcfg) { - LOGP(DGPRS, LOGL_FATAL, "Unable to allocate config\n"); - exit(1); - } - gbproxy_init_config(gbcfg); - gbcfg->nsi = gprs_ns2_instantiate(tall_sgsn_ctx, gprs_ns2_prim_cb, gbcfg); - if (!gbcfg->nsi) { - LOGP(DGPRS, LOGL_ERROR, "Unable to instantiate NS\n"); - exit(1); - } - gprs_ns2_vty_init(gbcfg->nsi); - logging_vty_add_deprecated_subsys(tall_sgsn_ctx, "bssgp"); - - bssgp_set_bssgp_callback(gbprox_bssgp_send_cb, gbcfg); - - rc = gbproxy_parse_config(config_file, gbcfg); - if (rc < 0) { - LOGP(DGPRS, LOGL_FATAL, "Cannot parse config file '%s'\n", config_file); - exit(2); - } - - /* start telnet after reading config for vty_get_bind_addr() */ - rc = telnet_init_dynif(tall_sgsn_ctx, NULL, - vty_get_bind_addr(), OSMO_VTY_PORT_GBPROXY); - if (rc < 0) - exit(1); - - /* Start control interface after getting config for - * ctrl_vty_get_bind_addr() */ - ctrl = ctrl_interface_setup_dynip(gbcfg, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_GBPROXY, NULL); - if (!ctrl) { - LOGP(DGPRS, LOGL_FATAL, "Failed to create CTRL interface.\n"); - exit(1); - } - - if (gb_ctrl_cmds_install() != 0) { - LOGP(DGPRS, LOGL_FATAL, "Failed to install CTRL commands.\n"); - exit(1); - } - - if (daemonize) { - rc = osmo_daemonize(); - if (rc < 0) { - perror("Error during daemonize"); - exit(1); - } - } - - while (1) { - rc = osmo_select_main(0); - if (rc < 0) - exit(3); - } - - exit(0); -} diff --git a/src/gbproxy/gb_proxy_peer.c b/src/gbproxy/gb_proxy_peer.c deleted file mode 100644 index 7d6ba864a..000000000 --- a/src/gbproxy/gb_proxy_peer.c +++ /dev/null @@ -1,759 +0,0 @@ -/* Gb proxy peer handling */ - -/* (C) 2010 by Harald Welte - * (C) 2010-2013 by On-Waves - * (C) 2013 by Holger Hans Peter Freyther - * 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 . - * - */ - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -extern void *tall_sgsn_ctx; - -static const struct rate_ctr_desc bvc_ctr_description[] = { - { "blocked", "BVC Block " }, - { "unblocked", "BVC Unblock " }, - { "dropped", "BVC blocked, dropped packet " }, - { "inv-nsei", "NSEI mismatch " }, - { "tx-err", "NS Transmission error " }, -}; - -osmo_static_assert(ARRAY_SIZE(bvc_ctr_description) == GBPROX_PEER_CTR_LAST, everything_described); - -static const struct rate_ctr_group_desc bvc_ctrg_desc = { - .group_name_prefix = "gbproxy:peer", - .group_description = "GBProxy Peer Statistics", - .num_ctr = ARRAY_SIZE(bvc_ctr_description), - .ctr_desc = bvc_ctr_description, - .class_id = OSMO_STATS_CLASS_PEER, -}; - - -/* Find the gbproxy_bvc by its BVCI. There can only be one match */ -struct gbproxy_bvc *gbproxy_bvc_by_bvci(struct gbproxy_nse *nse, uint16_t bvci) -{ - struct gbproxy_bvc *bvc; - hash_for_each_possible(nse->bvcs, bvc, list, bvci) { - if (bvc->bvci == bvci) - return bvc; - } - return NULL; -} - -struct gbproxy_bvc *gbproxy_bvc_alloc(struct gbproxy_nse *nse, uint16_t bvci) -{ - struct gbproxy_bvc *bvc; - OSMO_ASSERT(nse); - struct gbproxy_config *cfg = nse->cfg; - OSMO_ASSERT(cfg); - - bvc = talloc_zero(tall_sgsn_ctx, struct gbproxy_bvc); - if (!bvc) - return NULL; - - bvc->bvci = bvci; - bvc->ctrg = rate_ctr_group_alloc(bvc, &bvc_ctrg_desc, (nse->nsei << 16) | bvci); - if (!bvc->ctrg) { - talloc_free(bvc); - return NULL; - } - bvc->nse = nse; - - hash_add(nse->bvcs, &bvc->list, bvc->bvci); - - LOGPBVC_CAT(bvc, DOBJ, LOGL_INFO, "BVC Created\n"); - - /* We leave allocating the bvc->fi to the caller, as the FSM details depend - * on the type of BVC (SIG/PTP) and role (SGSN/BSS) */ - - return bvc; -} - -void gbproxy_bvc_free(struct gbproxy_bvc *bvc) -{ - struct gbproxy_cell *cell; - - if (!bvc) - return; - - LOGPBVC_CAT(bvc, DOBJ, LOGL_INFO, "BVC Destroying\n"); - - hash_del(&bvc->list); - - rate_ctr_group_free(bvc->ctrg); - bvc->ctrg = NULL; - - osmo_fsm_inst_free(bvc->fi); - - cell = bvc->cell; - if (cell) { - int i; - - if (cell->bss_bvc == bvc) - cell->bss_bvc = NULL; - - /* we could also be a SGSN-side BVC */ - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - if (cell->sgsn_bvc[i] == bvc) - cell->sgsn_bvc[i] = NULL; - } - bvc->cell = NULL; - } - - talloc_free(bvc); -} - -/*! remove BVCs on NSE specified by NSEI. - * \param[in] cfg proxy in which we operate - * \param[in] nsei NS entity in which we should clean up - * \param[in] bvci if 0: remove all PTP BVCs; if != 0: BVCI of the single BVC to clean up */ -int gbproxy_cleanup_bvcs(struct gbproxy_nse *nse, uint16_t bvci) -{ - struct hlist_node *btmp; - struct gbproxy_bvc *bvc; - int j, counter = 0; - - if (!nse) - return 0; - - hash_for_each_safe(nse->bvcs, j, btmp, bvc, list) { - if (bvci && bvc->bvci != bvci) - continue; - if (bvci == 0 && bvc->bvci == 0) - continue; - - gbproxy_bvc_free(bvc); - counter += 1; - } - - return counter; -} - - -/*********************************************************************** - * CELL - ***********************************************************************/ - -/* Allocate a new 'cell' object */ -struct gbproxy_cell *gbproxy_cell_alloc(struct gbproxy_config *cfg, uint16_t bvci) -{ - struct gbproxy_cell *cell; - OSMO_ASSERT(cfg); - - cell = talloc_zero(cfg, struct gbproxy_cell); - if (!cell) - return NULL; - - cell->cfg = cfg; - cell->bvci = bvci; - - hash_add(cfg->cells, &cell->list, cell->bvci); - - LOGPCELL_CAT(cell, DOBJ, LOGL_INFO, "CELL Created\n"); - - return cell; -} - -/* Find cell by BVCI */ -struct gbproxy_cell *gbproxy_cell_by_bvci(struct gbproxy_config *cfg, uint16_t bvci) -{ - struct gbproxy_cell *cell; - - hash_for_each_possible(cfg->cells, cell, list, bvci) { - if (cell->bvci == bvci) - return cell; - } - return NULL; -} - -struct gbproxy_cell *gbproxy_cell_by_bvci_or_new(struct gbproxy_config *cfg, uint16_t bvci) -{ - struct gbproxy_cell *cell; - OSMO_ASSERT(cfg); - - cell = gbproxy_cell_by_bvci(cfg, bvci); - if (!cell) - cell = gbproxy_cell_alloc(cfg, bvci); - - return cell; -} - -void gbproxy_cell_free(struct gbproxy_cell *cell) -{ - unsigned int i; - - if (!cell) - return; - - LOGPCELL_CAT(cell, DOBJ, LOGL_INFO, "CELL Destroying\n"); - - /* remove from cfg.cells */ - hash_del(&cell->list); - - /* remove back-pointers from the BSS side */ - if (cell->bss_bvc && cell->bss_bvc->cell) - cell->bss_bvc->cell = NULL; - - /* remove back-pointers from the SGSN side */ - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - if (!cell->sgsn_bvc[i]) - continue; - if (cell->sgsn_bvc[i]->cell) - cell->sgsn_bvc[i]->cell = NULL; - } - - talloc_free(cell); -} - -bool gbproxy_cell_add_sgsn_bvc(struct gbproxy_cell *cell, struct gbproxy_bvc *bvc) -{ - unsigned int i; - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - if (!cell->sgsn_bvc[i]) { - cell->sgsn_bvc[i] = bvc; - LOGPCELL_CAT(cell, DOBJ, LOGL_DEBUG, "CELL linked to SGSN\n"); - LOGPBVC_CAT(bvc, DOBJ, LOGL_DEBUG, "BVC linked to CELL\n"); - return true; - } - } - return false; -} - - -/*********************************************************************** - * TLLI cache - ***********************************************************************/ - -static inline struct gbproxy_tlli_cache_entry *_get_tlli_entry(struct gbproxy_config *cfg, uint32_t tlli) -{ - struct gbproxy_tlli_cache_entry *cache_entry; - - hash_for_each_possible(cfg->tlli_cache.entries, cache_entry, list, tlli) { - if (cache_entry->tlli == tlli) - return cache_entry; - } - return NULL; -} - -void gbproxy_tlli_cache_update(struct gbproxy_nse *nse, uint32_t tlli) -{ - struct gbproxy_config *cfg = nse->cfg; - struct timespec now; - struct gbproxy_tlli_cache_entry *cache_entry = _get_tlli_entry(cfg, tlli); - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - - if (cache_entry) { - /* Update the entry if it already exists */ - cache_entry->nse = nse; - cache_entry->tstamp = now.tv_sec; - return; - } - - cache_entry = talloc_zero(cfg, struct gbproxy_tlli_cache_entry); - cache_entry->tlli = tlli; - cache_entry->nse = nse; - cache_entry->tstamp = now.tv_sec; - hash_add(cfg->tlli_cache.entries, &cache_entry->list, cache_entry->tlli); -} - -static void _tlli_cache_remove_nse(struct gbproxy_nse *nse) { - uint i; - struct gbproxy_config *cfg = nse->cfg; - struct gbproxy_tlli_cache_entry *tlli_cache; - struct hlist_node *tmp; - - hash_for_each_safe(cfg->tlli_cache.entries, i, tmp, tlli_cache, list) { - if (tlli_cache->nse == nse) { - hash_del(&tlli_cache->list); - talloc_free(tlli_cache); - } - } -} - -void gbproxy_tlli_cache_remove(struct gbproxy_config *cfg, uint32_t tlli) -{ - struct gbproxy_tlli_cache_entry *tlli_cache; - struct hlist_node *tmp; - - hash_for_each_possible_safe(cfg->tlli_cache.entries, tlli_cache, tmp, list, tlli) { - if (tlli_cache->tlli == tlli) { - hash_del(&tlli_cache->list); - talloc_free(tlli_cache); - return; - } - } -} - -int gbproxy_tlli_cache_cleanup(struct gbproxy_config *cfg) -{ - int i, count = 0; - struct gbproxy_tlli_cache_entry *tlli_cache; - struct hlist_node *tmp; - struct timespec now; - time_t expiry; - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - expiry = now.tv_sec - cfg->tlli_cache.timeout; - - hash_for_each_safe(cfg->tlli_cache.entries, i, tmp, tlli_cache, list) { - if (tlli_cache->tstamp < expiry) { - count++; - LOGP(DGPRS, LOGL_NOTICE, "Cache entry for TLLI %08x expired, removing\n", tlli_cache->tlli); - hash_del(&tlli_cache->list); - talloc_free(tlli_cache); - } - } - return count; - -} -/*********************************************************************** - * IMSI cache - ***********************************************************************/ -static inline uint16_t _checksum_imsi(const char *imsi) -{ - size_t len = strlen(imsi); - return osmo_crc16(0, (const uint8_t *)imsi, len); -} - -static inline struct gbproxy_imsi_cache_entry *_get_imsi_entry(struct gbproxy_config *cfg, const char *imsi) -{ - struct gbproxy_imsi_cache_entry *cache_entry; - uint16_t imsi_hash = _checksum_imsi(imsi); - - hash_for_each_possible(cfg->imsi_cache.entries, cache_entry, list, imsi_hash) { - if (!strncmp(cache_entry->imsi, imsi, sizeof(cache_entry->imsi))) - return cache_entry; - } - return NULL; -} - -void gbproxy_imsi_cache_update(struct gbproxy_nse *nse, const char *imsi) -{ - struct gbproxy_config *cfg = nse->cfg; - struct timespec now; - struct gbproxy_imsi_cache_entry *cache_entry = _get_imsi_entry(cfg, imsi); - uint16_t imsi_hash = _checksum_imsi(imsi); - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - - if (cache_entry) { - /* Update the entry if it already exists */ - cache_entry->nse = nse; - cache_entry->tstamp = now.tv_sec; - return; - } - - cache_entry = talloc_zero(cfg, struct gbproxy_imsi_cache_entry); - OSMO_STRLCPY_ARRAY(cache_entry->imsi, imsi); - cache_entry->nse = nse; - cache_entry->tstamp = now.tv_sec; - hash_add(cfg->imsi_cache.entries, &cache_entry->list, imsi_hash); -} - -static void _imsi_cache_remove_nse(struct gbproxy_nse *nse) { - uint i; - struct gbproxy_config *cfg = nse->cfg; - struct gbproxy_imsi_cache_entry *imsi_cache; - struct hlist_node *tmp; - - hash_for_each_safe(cfg->imsi_cache.entries, i, tmp, imsi_cache, list) { - if (imsi_cache->nse == nse) { - hash_del(&imsi_cache->list); - talloc_free(imsi_cache); - } - } -} - -void gbproxy_imsi_cache_remove(struct gbproxy_config *cfg, const char *imsi) -{ - struct gbproxy_imsi_cache_entry *imsi_cache; - struct hlist_node *tmp; - uint16_t imsi_hash = _checksum_imsi(imsi); - - hash_for_each_possible_safe(cfg->imsi_cache.entries, imsi_cache, tmp, list, imsi_hash) { - if (!(strncmp(imsi_cache->imsi, imsi, sizeof(imsi_cache->imsi)))) { - hash_del(&imsi_cache->list); - talloc_free(imsi_cache); - return; - } - } -} - -int gbproxy_imsi_cache_cleanup(struct gbproxy_config *cfg) -{ - int i, count = 0; - struct gbproxy_imsi_cache_entry *imsi_cache; - struct hlist_node *tmp; - struct timespec now; - time_t expiry; - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - expiry = now.tv_sec - cfg->imsi_cache.timeout; - - hash_for_each_safe(cfg->imsi_cache.entries, i, tmp, imsi_cache, list) { - if (imsi_cache->tstamp < expiry) { - count++; - LOGP(DGPRS, LOGL_NOTICE, "Cache entry for IMSI %s expired, removing\n", imsi_cache->imsi); - hash_del(&imsi_cache->list); - talloc_free(imsi_cache); - } - } - return count; -} - -/*********************************************************************** - * NSE - NS Entity - ***********************************************************************/ - -struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei, bool sgsn_facing) -{ - struct gbproxy_nse *nse; - OSMO_ASSERT(cfg); - - nse = talloc_zero(tall_sgsn_ctx, struct gbproxy_nse); - if (!nse) - return NULL; - - nse->nsei = nsei; - nse->cfg = cfg; - nse->sgsn_facing = sgsn_facing; - - if (sgsn_facing) - hash_add(cfg->sgsn_nses, &nse->list, nsei); - else - hash_add(cfg->bss_nses, &nse->list, nsei); - - hash_init(nse->bvcs); - - LOGPNSE_CAT(nse, DOBJ, LOGL_INFO, "NSE Created\n"); - - return nse; -} - -static void _nse_free(struct gbproxy_nse *nse) -{ - struct gbproxy_bvc *bvc; - struct hlist_node *tmp; - int i; - - if (!nse) - return; - - LOGPNSE_CAT(nse, DOBJ, LOGL_INFO, "NSE Destroying\n"); - - hash_del(&nse->list); - /* Clear the cache entries of this NSE */ - _tlli_cache_remove_nse(nse); - _imsi_cache_remove_nse(nse); - - hash_for_each_safe(nse->bvcs, i, tmp, bvc, list) - gbproxy_bvc_free(bvc); - - talloc_free(nse); -} -static void _sgsn_free(struct gbproxy_sgsn *sgsn); - -void gbproxy_nse_free(struct gbproxy_nse *nse) -{ - if (!nse) - return; - OSMO_ASSERT(nse->cfg); - - if (nse->sgsn_facing) { - struct gbproxy_sgsn *sgsn = gbproxy_sgsn_by_nsei(nse->cfg, nse->nsei); - OSMO_ASSERT(sgsn); - _sgsn_free(sgsn); - } - - _nse_free(nse); -} - -struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei, uint32_t flags) -{ - struct gbproxy_nse *nse; - OSMO_ASSERT(cfg); - - if (flags & NSE_F_SGSN) { - hash_for_each_possible(cfg->sgsn_nses, nse, list, nsei) { - if (nse->nsei == nsei) - return nse; - } - } - - if (flags & NSE_F_BSS) { - hash_for_each_possible(cfg->bss_nses, nse, list, nsei) { - if (nse->nsei == nsei) - return nse; - } - } - - return NULL; -} - -struct gbproxy_nse *gbproxy_nse_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei, bool sgsn_facing) -{ - struct gbproxy_nse *nse; - OSMO_ASSERT(cfg); - - nse = gbproxy_nse_by_nsei(cfg, nsei, sgsn_facing ? NSE_F_SGSN : NSE_F_BSS); - if (!nse) - nse = gbproxy_nse_alloc(cfg, nsei, sgsn_facing); - - return nse; -} - -struct gbproxy_nse *gbproxy_nse_by_tlli(struct gbproxy_config *cfg, uint32_t tlli) -{ - struct gbproxy_tlli_cache_entry *tlli_cache; - - hash_for_each_possible(cfg->tlli_cache.entries, tlli_cache, list, tlli) { - if (tlli_cache->tlli == tlli) - return tlli_cache->nse; - } - return NULL; -} - -struct gbproxy_nse *gbproxy_nse_by_imsi(struct gbproxy_config *cfg, const char *imsi) -{ - struct gbproxy_imsi_cache_entry *imsi_cache; - uint16_t imsi_hash = _checksum_imsi(imsi); - - hash_for_each_possible(cfg->imsi_cache.entries, imsi_cache, list, imsi_hash) { - if (!strncmp(imsi_cache->imsi, imsi, sizeof(imsi_cache->imsi))) - return imsi_cache->nse; - } - return NULL; -} - -/*********************************************************************** - * SGSN - Serving GPRS Support Node - ***********************************************************************/ - -/*! Allocate a new SGSN. This ensures the corresponding gbproxy_nse is allocated as well - * \param[in] cfg The gbproxy configuration - * \param[in] nsei The nsei where the SGSN can be reached - * \param[in] name A name to give the SGSN - * \return The SGSN, NULL if it couldn't be allocated - */ -struct gbproxy_sgsn *gbproxy_sgsn_alloc(struct gbproxy_config *cfg, uint16_t nsei, const char *name) -{ - struct gbproxy_sgsn *sgsn; - OSMO_ASSERT(cfg); - - sgsn = talloc_zero(tall_sgsn_ctx, struct gbproxy_sgsn); - if (!sgsn) - return NULL; - - sgsn->nse = gbproxy_nse_alloc(cfg, nsei, true); - if (!sgsn->nse) { - LOGP(DOBJ, LOGL_ERROR, "Could not allocate NSE(%05u) for SGSN(%s)\n", - nsei, sgsn->name); - goto free_sgsn; - } - - if (name) - sgsn->name = talloc_strdup(sgsn, name); - else - sgsn->name = talloc_asprintf(sgsn, "NSE(%05u)", sgsn->nse->nsei); - if (!sgsn->name) - goto free_sgsn; - - sgsn->pool.allow_attach = true; - sgsn->pool.nri_ranges = osmo_nri_ranges_alloc(sgsn); - - llist_add_tail(&sgsn->list, &cfg->sgsns); - LOGPSGSN_CAT(sgsn, DOBJ, LOGL_INFO, "SGSN Created\n"); - return sgsn; - -free_sgsn: - talloc_free(sgsn); - return NULL; -} - -/* Only free gbproxy_sgsn, sgsn can't be NULL */ -static void _sgsn_free(struct gbproxy_sgsn *sgsn) { - struct gbproxy_config *cfg; - - OSMO_ASSERT(sgsn->nse); - cfg = sgsn->nse->cfg; - OSMO_ASSERT(cfg); - - LOGPSGSN_CAT(sgsn, DOBJ, LOGL_INFO, "SGSN Destroying\n"); - llist_del(&sgsn->list); - /* talloc will free ->name and ->pool.nri_ranges */ - talloc_free(sgsn); -} - -/*! Free the SGSN. This ensures the corresponding gbproxy_nse is freed as well - * \param[in] sgsn The SGSN - */ -void gbproxy_sgsn_free(struct gbproxy_sgsn *sgsn) -{ - if (!sgsn) - return; - - OSMO_ASSERT(sgsn->nse) - - _nse_free(sgsn->nse); - _sgsn_free(sgsn); -} - -/*! Return the SGSN for a given NSEI - * \param[in] cfg The gbproxy configuration - * \param[in] nsei The nsei where the SGSN can be reached - * \return Returns the matching SGSN or NULL if it couldn't be found - */ -struct gbproxy_sgsn *gbproxy_sgsn_by_name(struct gbproxy_config *cfg, const char *name) -{ - struct gbproxy_sgsn *sgsn; - OSMO_ASSERT(cfg); - - llist_for_each_entry(sgsn, &cfg->sgsns, list) { - if (!strcmp(sgsn->name, name)) - return sgsn; - } - - return NULL; -} - -/*! Return the SGSN for a given NSEI - * \param[in] cfg The gbproxy configuration - * \param[in] nsei The nsei where the SGSN can be reached - * \return Returns the matching SGSN or NULL if it couldn't be found - */ -struct gbproxy_sgsn *gbproxy_sgsn_by_nsei(struct gbproxy_config *cfg, uint16_t nsei) -{ - struct gbproxy_sgsn *sgsn; - OSMO_ASSERT(cfg); - - llist_for_each_entry(sgsn, &cfg->sgsns, list) { - if (sgsn->nse->nsei == nsei) - return sgsn; - } - - return NULL; -} - -/*! Return the SGSN for a given NSEI, creating a new one if none exists - * \param[in] cfg The gbproxy configuration - * \param[in] nsei The nsei where the SGSN can be reached - * \return Returns the SGSN - */ -struct gbproxy_sgsn *gbproxy_sgsn_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei) -{ - struct gbproxy_sgsn *sgsn; - OSMO_ASSERT(cfg); - - sgsn = gbproxy_sgsn_by_nsei(cfg, nsei); - if (!sgsn) - sgsn = gbproxy_sgsn_alloc(cfg, nsei, NULL); - - return sgsn; -} - -/*! Return the gbproxy_sgsn matching that NRI - * \param[in] cfg proxy in which we operate - * \param[in] nri NRI to look for - * \param[out] null_nri If not NULL this indicates whether the NRI is a null NRI - * \return The SGSN this NRI has been added to, NULL if no matching SGSN could be found - */ -struct gbproxy_sgsn *gbproxy_sgsn_by_nri(struct gbproxy_config *cfg, uint16_t nri, bool *null_nri) -{ - struct gbproxy_sgsn *sgsn; - OSMO_ASSERT(cfg); - - llist_for_each_entry(sgsn, &cfg->sgsns, list) { - if (osmo_nri_v_matches_ranges(nri, sgsn->pool.nri_ranges)) { - /* Also check if the NRI we're looking for is a NULL NRI */ - if (null_nri) { - if (osmo_nri_v_matches_ranges(nri, cfg->pool.null_nri_ranges)) - *null_nri = true; - else - *null_nri = false; - } - return sgsn; - } - } - - return NULL; -} - -/*! Seleect a pseudo-random SGSN for a given TLLI, ignoring any SGSN that is not accepting connections - * \param[in] cfg The gbproxy configuration - * \param[in] sgsn_avoid If not NULL then avoid this SGSN when selecting a new one. Use for load redistribution - * \param[in] tlli The tlli to choose an SGSN for. The same tlli will map to the same SGSN as long as no SGSN is - * added/removed or allow_attach changes. - * \return Returns the sgsn on success, NULL if no SGSN that allows new connections could be found - */ -struct gbproxy_sgsn *gbproxy_sgsn_by_tlli(struct gbproxy_config *cfg, struct gbproxy_sgsn *sgsn_avoid, - uint32_t tlli) -{ - uint32_t i = 0; - uint32_t index, num_sgsns; - OSMO_ASSERT(cfg); - - struct gbproxy_sgsn *sgsn = cfg->pool.nsf_override; - - if (sgsn) { - LOGPSGSN(sgsn, LOGL_DEBUG, "Node selection function is overridden by config\n"); - return sgsn; - } - - /* TODO: We should keep track of count in cfg */ - num_sgsns = llist_count(&cfg->sgsns); - - if (num_sgsns == 0) - return NULL; - - /* FIXME: 256 SGSNs ought to be enough for everyone */ - index = hash_32(tlli, 8) % num_sgsns; - - /* Get the first enabled SGSN after index */ - llist_for_each_entry(sgsn, &cfg->sgsns, list) { - if (i >= index && sgsn->pool.allow_attach) { - return sgsn; - } - i++; - } - /* Start again from the beginning */ - i = 0; - llist_for_each_entry(sgsn, &cfg->sgsns, list) { - if (i >= index) { - break; - } else if (sgsn->pool.allow_attach) { - return sgsn; - } - i++; - } - - return NULL; -} diff --git a/src/gbproxy/gb_proxy_vty.c b/src/gbproxy/gb_proxy_vty.c deleted file mode 100644 index 7ae65d20e..000000000 --- a/src/gbproxy/gb_proxy_vty.c +++ /dev/null @@ -1,778 +0,0 @@ -/* - * (C) 2010 by Harald Welte - * (C) 2010 by On-Waves - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#define GBPROXY_STR "Display information about the Gb proxy\n" -#define NRI_STR "Mapping of Network Resource Indicators to this SGSN, for SGSN pooling\n" -#define NULL_NRI_STR "Define NULL-NRI values that cause re-assignment of an MS to a different SGSN, for SGSN pooling.\n" -#define NRI_FIRST_LAST_STR "First value of the NRI value range, should not surpass the configured 'nri bitlen'.\n" \ - "Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the" \ - " first value; if omitted, apply only the first value.\n" -#define NRI_ARGS_TO_STR_FMT "%s%s%s" -#define NRI_ARGS_TO_STR_ARGS(ARGC, ARGV) ARGV[0], (ARGC>1)? ".." : "", (ARGC>1)? ARGV[1] : "" -#define NRI_WARN(SGSN, FORMAT, args...) do { \ - vty_out(vty, "%% Warning: NSE(%05d/SGSN): " FORMAT "%s", (SGSN)->nse->nsei, ##args, VTY_NEWLINE); \ - LOGP(DLBSSGP, LOGL_ERROR, "NSE(%05d/SGSN): " FORMAT "\n", (SGSN)->nse->nsei, ##args); \ - } while (0) - -static struct gbproxy_config *g_cfg = NULL; - -/* - * vty code for gbproxy below - */ -static struct cmd_node gbproxy_node = { - GBPROXY_NODE, - "%s(config-gbproxy)# ", - 1, -}; - -static void gbprox_vty_print_bvc(struct vty *vty, struct gbproxy_bvc *bvc) -{ - - if (bvc->bvci == 0) { - vty_out(vty, "NSEI %5u, SIG-BVCI %5u [%s]%s", bvc->nse->nsei, bvc->bvci, - osmo_fsm_inst_state_name(bvc->fi), VTY_NEWLINE); - } else { - struct gprs_ra_id raid; - gsm48_parse_ra(&raid, bvc->ra); - vty_out(vty, "NSEI %5u, PTP-BVCI %5u, RAI %s [%s]%s", bvc->nse->nsei, bvc->bvci, - osmo_rai_name(&raid), osmo_fsm_inst_state_name(bvc->fi), VTY_NEWLINE); - } -} - -static void gbproxy_vty_print_nse(struct vty *vty, struct gbproxy_nse *nse, bool show_stats) -{ - struct gbproxy_bvc *bvc; - int j; - - hash_for_each(nse->bvcs, j, bvc, list) { - gbprox_vty_print_bvc(vty, bvc); - - if (show_stats) - vty_out_rate_ctr_group(vty, " ", bvc->ctrg); - } -} - -static void gbproxy_vty_print_cell(struct vty *vty, struct gbproxy_cell *cell, bool show_stats) -{ - struct gprs_ra_id raid; - gsm48_parse_ra(&raid, cell->ra); - unsigned int num_sgsn_bvc = 0; - unsigned int i; - - vty_out(vty, "BVCI %5u RAI %s: ", cell->bvci, osmo_rai_name(&raid)); - if (cell->bss_bvc) - vty_out(vty, "BSS NSEI %5u, SGSN NSEI ", cell->bss_bvc->nse->nsei); - else - vty_out(vty, "BSS NSEI , SGSN NSEI "); - - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { - struct gbproxy_bvc *sgsn_bvc = cell->sgsn_bvc[i]; - if (sgsn_bvc) { - vty_out(vty, "%5u ", sgsn_bvc->nse->nsei); - num_sgsn_bvc++; - } - } - if (num_sgsn_bvc) - vty_out(vty, "%s", VTY_NEWLINE); - else - vty_out(vty, "%s", VTY_NEWLINE); -} - -static int config_write_gbproxy(struct vty *vty) -{ - struct osmo_nri_range *r; - - vty_out(vty, "gbproxy%s", VTY_NEWLINE); - - if (g_cfg->pool.bvc_fc_ratio != 100) - vty_out(vty, " pool bvc-flow-control-ratio %u%s", g_cfg->pool.bvc_fc_ratio, VTY_NEWLINE); - - if (g_cfg->pool.nri_bitlen != OSMO_NRI_BITLEN_DEFAULT) - vty_out(vty, " nri bitlen %u%s", g_cfg->pool.nri_bitlen, VTY_NEWLINE); - - llist_for_each_entry(r, &g_cfg->pool.null_nri_ranges->entries, entry) { - vty_out(vty, " nri null add %d", r->first); - if (r->first != r->last) - vty_out(vty, " %d", r->last); - vty_out(vty, "%s", VTY_NEWLINE); - } - return CMD_SUCCESS; -} - -DEFUN(cfg_gbproxy, - cfg_gbproxy_cmd, - "gbproxy", - "Configure the Gb proxy") -{ - vty->node = GBPROXY_NODE; - return CMD_SUCCESS; -} - -/* VTY code for SGSN (pool) configuration */ -extern const struct bssgp_bvc_fsm_ops sgsn_sig_bvc_fsm_ops; -#include - -static struct cmd_node sgsn_node = { - SGSN_NODE, - "%s(config-sgsn)# ", - 1, -}; - -static void sgsn_write_nri(struct vty *vty, struct gbproxy_sgsn *sgsn, bool verbose) -{ - struct osmo_nri_range *r; - - if (verbose) { - vty_out(vty, "sgsn nsei %d%s", sgsn->nse->nsei, VTY_NEWLINE); - if (llist_empty(&sgsn->pool.nri_ranges->entries)) { - vty_out(vty, " %% no NRI mappings%s", VTY_NEWLINE); - return; - } - } - - llist_for_each_entry(r, &sgsn->pool.nri_ranges->entries, entry) { - if (osmo_nri_range_validate(r, 255)) - vty_out(vty, " %% INVALID RANGE:"); - vty_out(vty, " nri add %d", r->first); - if (r->first != r->last) - vty_out(vty, " %d", r->last); - vty_out(vty, "%s", VTY_NEWLINE); - } -} - -static void write_sgsn(struct vty *vty, struct gbproxy_sgsn *sgsn) -{ - vty_out(vty, "sgsn nsei %u%s", sgsn->nse->nsei, VTY_NEWLINE); - vty_out(vty, " name %s%s", sgsn->name, VTY_NEWLINE); - vty_out(vty, " %sallow-attach%s", sgsn->pool.allow_attach ? "" : "no ", VTY_NEWLINE); - sgsn_write_nri(vty, sgsn, false); -} - -static int config_write_sgsn(struct vty *vty) -{ - struct gbproxy_sgsn *sgsn; - - llist_for_each_entry(sgsn, &g_cfg->sgsns, list) - write_sgsn(vty, sgsn); - - return CMD_SUCCESS; -} - -DEFUN(cfg_sgsn_nsei, - cfg_sgsn_nsei_cmd, - "sgsn nsei <0-65534>", - "Configure the SGSN\n" - "NSEI to be used in the connection with the SGSN\n" - "The NSEI\n") -{ - uint32_t features = 0; // FIXME: make configurable - unsigned int nsei = atoi(argv[0]); - unsigned int num_sgsn = llist_count(&g_cfg->sgsns); - struct gbproxy_sgsn *sgsn; - struct gbproxy_nse *nse; - struct gbproxy_bvc *bvc; - - if (num_sgsn >= GBPROXY_MAX_NR_SGSN) { - vty_out(vty, "%% Too many SGSN NSE defined (%d), increase GBPROXY_MAX_NR_SGSN%s", - num_sgsn, VTY_NEWLINE); - return CMD_WARNING; - } - - /* This will have created the gbproxy_nse as well */ - sgsn = gbproxy_sgsn_by_nsei_or_new(g_cfg, nsei); - if (!sgsn) - goto free_nothing; - nse = sgsn->nse; - if (num_sgsn > 1 && g_cfg->pool.nri_bitlen == 0) - vty_out(vty, "%% Multiple SGSNs defined, but no pooling enabled%s", VTY_NEWLINE); - - - if (!gbproxy_bvc_by_bvci(nse, 0)) { - uint8_t cause = BSSGP_CAUSE_OML_INTERV; - bvc = gbproxy_bvc_alloc(nse, 0); - if (!bvc) - goto free_sgsn; - bvc->fi = bssgp_bvc_fsm_alloc_sig_bss(bvc, nse->cfg->nsi, nsei, features); - if (!bvc->fi) - goto free_bvc; - bssgp_bvc_fsm_set_ops(bvc->fi, &sgsn_sig_bvc_fsm_ops, bvc); - osmo_fsm_inst_dispatch(bvc->fi, BSSGP_BVCFSM_E_REQ_RESET, &cause); - } - - vty->node = SGSN_NODE; - vty->index = sgsn; - return CMD_SUCCESS; - -free_bvc: - gbproxy_bvc_free(bvc); -free_sgsn: - gbproxy_sgsn_free(sgsn); -free_nothing: - vty_out(vty, "%% Unable to create NSE for NSEI=%05u%s", nsei, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(cfg_sgsn_name, - cfg_sgsn_name_cmd, - "name NAME", - "Configure the SGSN\n" - "Name the SGSN\n" - "The name\n") -{ - struct gbproxy_sgsn *sgsn = vty->index; - const char *name = argv[0]; - - - osmo_talloc_replace_string(sgsn, &sgsn->name, name); - if (!sgsn->name) { - vty_out(vty, "%% Unable to set name for SGSN with nsei %05u%s", sgsn->nse->nsei, VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_sgsn_nri_add, cfg_sgsn_nri_add_cmd, - "nri add <0-32767> [<0-32767>]", - NRI_STR "Add NRI value or range to the NRI mapping for this MSC\n" - NRI_FIRST_LAST_STR, - CMD_ATTR_IMMEDIATE) -{ - struct gbproxy_sgsn *sgsn = vty->index; - struct gbproxy_sgsn *other_sgsn; - bool before; - int rc; - const char *message; - struct osmo_nri_range add_range; - - rc = osmo_nri_ranges_vty_add(&message, &add_range, sgsn->pool.nri_ranges, argc, argv, g_cfg->pool.nri_bitlen); - if (message) { - NRI_WARN(sgsn, "%s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv)); - } - if (rc < 0) - return CMD_WARNING; - - /* Issue a warning about NRI range overlaps (but still allow them). - * Overlapping ranges will map to whichever SGSN comes fist in the gbproxy_config->sgsns llist, - * which should be the first one defined in the config */ - before = true; - - llist_for_each_entry(other_sgsn, &g_cfg->sgsns, list) { - if (other_sgsn == sgsn) { - before = false; - continue; - } - if (osmo_nri_range_overlaps_ranges(&add_range, other_sgsn->pool.nri_ranges)) { - uint16_t nsei = sgsn->nse->nsei; - uint16_t other_nsei = other_sgsn->nse->nsei; - NRI_WARN(sgsn, "NRI range [%d..%d] overlaps between NSE %05d and NSE %05d." - " For overlaps, NSE %05d has higher priority than NSE %05d", - add_range.first, add_range.last, nsei, other_nsei, - before ? other_nsei : nsei, before ? nsei : other_nsei); - } - } - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_sgsn_nri_del, cfg_sgsn_nri_del_cmd, - "nri del <0-32767> [<0-32767>]", - NRI_STR "Remove NRI value or range from the NRI mapping for this MSC\n" - NRI_FIRST_LAST_STR, - CMD_ATTR_IMMEDIATE) -{ - struct gbproxy_sgsn *sgsn = vty->index; - int rc; - const char *message; - - rc = osmo_nri_ranges_vty_del(&message, NULL, sgsn->pool.nri_ranges, argc, argv); - if (message) { - NRI_WARN(sgsn, "%s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv)); - } - if (rc < 0) - return CMD_WARNING; - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_sgsn_allow_attach, cfg_sgsn_allow_attach_cmd, - "allow-attach", - "Allow this SGSN to attach new subscribers (default).\n", - CMD_ATTR_IMMEDIATE) -{ - struct gbproxy_sgsn *sgsn = vty->index; - sgsn->pool.allow_attach = true; - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_sgsn_no_allow_attach, cfg_sgsn_no_allow_attach_cmd, - "no allow-attach", - NO_STR - "Do not assign new subscribers to this MSC." - " Useful if an MSC in an MSC pool is configured to off-load subscribers." - " The MSC will still be operational for already IMSI-Attached subscribers," - " but the NAS node selection function will skip this MSC for new subscribers\n", - CMD_ATTR_IMMEDIATE) -{ - struct gbproxy_sgsn *sgsn = vty->index; - sgsn->pool.allow_attach = false; - return CMD_SUCCESS; -} - -DEFUN(sgsn_show_nri_all, show_nri_all_cmd, - "show nri all", - SHOW_STR NRI_STR "Show all SGSNs\n") -{ - struct gbproxy_sgsn *sgsn; - - llist_for_each_entry(sgsn, &g_cfg->sgsns, list) - sgsn_write_nri(vty, sgsn, true); - - return CMD_SUCCESS; -} - -DEFUN(show_nri_nsei, show_nri_nsei_cmd, - "show nri nsei <0-65535>", - SHOW_STR NRI_STR "Identify SGSN by NSEI\n" - "NSEI of the SGSN\n") -{ - struct gbproxy_sgsn *sgsn; - int nsei = atoi(argv[0]); - - sgsn = gbproxy_sgsn_by_nsei(g_cfg, nsei); - if (!sgsn) { - vty_out(vty, "%% No SGSN with found for NSEI %05d%s", nsei, VTY_NEWLINE); - return CMD_SUCCESS; - } - sgsn_write_nri(vty, sgsn, true); - - return CMD_SUCCESS; -} - -DEFUN(cfg_pool_bvc_fc_ratio, - cfg_pool_bvc_fc_ratio_cmd, - "pool bvc-flow-control-ratio <1-100>", - "SGSN Pool related configuration\n" - "Ratio of BSS-advertised bucket size + leak rate advertised to each SGSN\n" - "Ratio of BSS-advertised bucket size + leak rate advertised to each SGSN (Percent)\n") -{ - g_cfg->pool.bvc_fc_ratio = atoi(argv[0]); - return CMD_SUCCESS; -} -DEFUN_ATTR(cfg_gbproxy_nri_bitlen, - cfg_gbproxy_nri_bitlen_cmd, - "nri bitlen <0-15>", - NRI_STR - "Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).\n" - "bit count (0 disables) pooling)\n", - CMD_ATTR_IMMEDIATE) -{ - g_cfg->pool.nri_bitlen = atoi(argv[0]); - - if (llist_count(&g_cfg->sgsns) > 1 && g_cfg->pool.nri_bitlen == 0) - vty_out(vty, "%% Pooling disabled, but multiple SGSNs defined%s", VTY_NEWLINE); - - /* TODO: Verify all nri ranges and warn on mismatch */ - - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_gbproxy_nri_null_add, - cfg_gbproxy_nri_null_add_cmd, - "nri null add <0-32767> [<0-32767>]", - NRI_STR NULL_NRI_STR "Add NULL-NRI value (or range)\n" - NRI_FIRST_LAST_STR, - CMD_ATTR_IMMEDIATE) -{ - int rc; - const char *message; - - rc = osmo_nri_ranges_vty_add(&message, NULL, g_cfg->pool.null_nri_ranges, argc, argv, - g_cfg->pool.nri_bitlen); - if (message) { - vty_out(vty, "%% nri null add: %s: " NRI_ARGS_TO_STR_FMT "%s", message, NRI_ARGS_TO_STR_ARGS(argc, argv), - VTY_NEWLINE); - vty_out(vty, "%s: \n" NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv)); - } - if (rc < 0) - return CMD_WARNING; - return CMD_SUCCESS; -} - -DEFUN_ATTR(cfg_gbproxy_nri_null_del, - cfg_gbproxy_nri_null_del_cmd, - "nri null del <0-32767> [<0-32767>]", - NRI_STR NULL_NRI_STR "Remove NRI value or range from the NRI mapping for this MSC\n" - NRI_FIRST_LAST_STR, - CMD_ATTR_IMMEDIATE) -{ - int rc; - const char *message; - rc = osmo_nri_ranges_vty_del(&message, NULL, g_cfg->pool.null_nri_ranges, argc, argv); - if (message) { - vty_out(vty, "%% %s: " NRI_ARGS_TO_STR_FMT "%s", message, NRI_ARGS_TO_STR_ARGS(argc, argv), - VTY_NEWLINE); - } - if (rc < 0) - return CMD_WARNING; - return CMD_SUCCESS; -} - -static void log_set_bvc_filter(struct log_target *target, - const uint16_t *bvci) -{ - if (bvci) { - uintptr_t bvci_filter = *bvci | BVC_LOG_CTX_FLAG; - target->filter_map |= (1 << LOG_FLT_GB_BVC); - target->filter_data[LOG_FLT_GB_BVC] = (void *)bvci_filter; - } else if (target->filter_data[LOG_FLT_GB_BVC]) { - target->filter_map = ~(1 << LOG_FLT_GB_BVC); - target->filter_data[LOG_FLT_GB_BVC] = NULL; - } -} - -DEFUN(logging_fltr_bvc, - logging_fltr_bvc_cmd, - "logging filter bvc bvci <0-65535>", - LOGGING_STR FILTER_STR - "Filter based on BSSGP VC\n" - "Identify BVC by BVCI\n" - "Numeric identifier\n") -{ - struct log_target *tgt; - uint16_t id = atoi(argv[0]); - - log_tgt_mutex_lock(); - tgt = osmo_log_vty2tgt(vty); - if (!tgt) { - log_tgt_mutex_unlock(); - return CMD_WARNING; - } - - log_set_bvc_filter(tgt, &id); - log_tgt_mutex_unlock(); - return CMD_SUCCESS; -} - -DEFUN(show_gbproxy_bvc, show_gbproxy_bvc_cmd, "show gbproxy bvc (bss|sgsn) [stats]", - SHOW_STR GBPROXY_STR - "Show BSSGP Virtual Connections\n" - "Display BSS-side BVCs\n" - "Display SGSN-side BVCs\n" - "Show statistics\n") -{ - struct gbproxy_nse *nse; - bool show_stats = argc >= 2; - int i; - - if (show_stats) - vty_out_rate_ctr_group(vty, "", g_cfg->ctrg); - - if (!strcmp(argv[0], "bss")) { - hash_for_each(g_cfg->bss_nses, i, nse, list) - gbproxy_vty_print_nse(vty, nse, show_stats); - } else { - hash_for_each(g_cfg->sgsn_nses, i, nse, list) - gbproxy_vty_print_nse(vty, nse, show_stats); - } - return CMD_SUCCESS; -} - -DEFUN(show_gbproxy_cell, show_gbproxy_cell_cmd, "show gbproxy cell [stats]", - SHOW_STR GBPROXY_STR - "Show GPRS Cell Information\n" - "Show statistics\n") -{ - struct gbproxy_cell *cell; - bool show_stats = argc >= 1; - int i; - - hash_for_each(g_cfg->cells, i, cell, list) - gbproxy_vty_print_cell(vty, cell, show_stats); - - return CMD_SUCCESS; -} - -DEFUN(show_gbproxy_links, show_gbproxy_links_cmd, "show gbproxy links", - SHOW_STR GBPROXY_STR "Show logical links\n") -{ - struct gbproxy_nse *nse; - int i, j; - - hash_for_each(g_cfg->bss_nses, i, nse, list) { - struct gbproxy_bvc *bvc; - hash_for_each(nse->bvcs, j, bvc, list) { - gbprox_vty_print_bvc(vty, bvc); - } - } - return CMD_SUCCESS; -} - -DEFUN(show_gbproxy_tlli_cache, show_gbproxy_tlli_cache_cmd, - "show gbproxy tlli-cache", - SHOW_STR GBPROXY_STR "Show TLLI cache entries\n") -{ - struct gbproxy_tlli_cache_entry *entry; - struct timespec now; - time_t expiry; - int i, count = 0; - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - expiry = now.tv_sec - g_cfg->tlli_cache.timeout; - - vty_out(vty, "TLLI cache timeout %us%s", g_cfg->tlli_cache.timeout, VTY_NEWLINE); - hash_for_each(g_cfg->tlli_cache.entries, i, entry, list) { - time_t valid = entry->tstamp - expiry; - struct gbproxy_nse *nse = entry->nse; - - vty_out(vty, " TLLI %08x -> NSE(%05u/%s) valid %lds%s", entry->tlli, nse->nsei, - nse->sgsn_facing ? "SGSN" : "BSS", valid, VTY_NEWLINE); - count++; - } - vty_out(vty, "TLLI cache contains %u entries%s", count, VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN(show_gbproxy_imsi_cache, show_gbproxy_imsi_cache_cmd, - "show gbproxy imsi-cache", - SHOW_STR GBPROXY_STR "Show IMSI cache entries\n") -{ - struct gbproxy_imsi_cache_entry *entry; - struct timespec now; - time_t expiry; - int i, count = 0; - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - expiry = now.tv_sec - g_cfg->imsi_cache.timeout; - - vty_out(vty, "IMSI cache timeout %us%s", g_cfg->imsi_cache.timeout, VTY_NEWLINE); - hash_for_each(g_cfg->imsi_cache.entries, i, entry, list) { - time_t valid = entry->tstamp - expiry; - struct gbproxy_nse *nse = entry->nse; - vty_out(vty, " IMSI %s -> NSE(%05u/%s): valid %lds%s", entry->imsi, nse->nsei, - nse->sgsn_facing ? "SGSN" : "BSS", valid, VTY_NEWLINE); - count++; - } - vty_out(vty, "IMSI cache contains %u entries%s", count, VTY_NEWLINE); - return CMD_SUCCESS; -} - -DEFUN(delete_gb_bvci, delete_gb_bvci_cmd, - "delete-gbproxy-peer <0-65534> bvci <2-65534>", - "Delete a GBProxy bvc by NSEI and optionally BVCI\n" - "NSEI number\n" - "Only delete bvc with a matching BVCI\n" - "BVCI number\n") -{ - const uint16_t nsei = atoi(argv[0]); - const uint16_t bvci = atoi(argv[1]); - struct gbproxy_nse *nse = gbproxy_nse_by_nsei(g_cfg, nsei, NSE_F_BSS); - int counter; - - if (!nse) { - vty_out(vty, "NSE not found%s", VTY_NEWLINE); - return CMD_WARNING; - } - - counter = gbproxy_cleanup_bvcs(nse, bvci); - - if (counter == 0) { - vty_out(vty, "BVC not found%s", VTY_NEWLINE); - return CMD_WARNING; - } - - return CMD_SUCCESS; -} - -DEFUN(delete_gb_nsei, delete_gb_nsei_cmd, - "delete-gbproxy-peer <0-65534> (only-bvc|only-nsvc|all) [dry-run]", - "Delete a GBProxy bvc by NSEI and optionally BVCI\n" - "NSEI number\n" - "Only delete BSSGP connections (BVC)\n" - "Only delete dynamic NS connections (NS-VC)\n" - "Delete BVC and dynamic NS connections\n" - "Show what would be deleted instead of actually deleting\n" - ) -{ - const uint16_t nsei = atoi(argv[0]); - const char *mode = argv[1]; - int dry_run = argc > 2; - int delete_bvc = 0; - int delete_nsvc = 0; - int counter; - - if (strcmp(mode, "only-bvc") == 0) - delete_bvc = 1; - else if (strcmp(mode, "only-nsvc") == 0) - delete_nsvc = 1; - else - delete_bvc = delete_nsvc = 1; - - if (delete_bvc) { - if (!dry_run) { - struct gbproxy_nse *nse = gbproxy_nse_by_nsei(g_cfg, nsei, NSE_F_BSS); - counter = gbproxy_cleanup_bvcs(nse, 0); - gbproxy_nse_free(nse); - } else { - struct gbproxy_nse *nse; - struct gbproxy_bvc *bvc; - int i, j; - counter = 0; - hash_for_each(g_cfg->bss_nses, i, nse, list) { - if (nse->nsei != nsei) - continue; - hash_for_each(nse->bvcs, j, bvc, list) { - vty_out(vty, "BVC: "); - gbprox_vty_print_bvc(vty, bvc); - counter += 1; - } - } - } - vty_out(vty, "%sDeleted %d BVC%s", - dry_run ? "Not " : "", counter, VTY_NEWLINE); - } - - if (delete_nsvc) { - struct gprs_ns2_inst *nsi = g_cfg->nsi; - struct gprs_ns2_nse *nse; - - nse = gprs_ns2_nse_by_nsei(nsi, nsei); - if (!nse) { - vty_out(vty, "NSEI not found%s", VTY_NEWLINE); - return CMD_WARNING; - } - - /* TODO: We should NOT delete a persistent NSEI/NSVC as soon as we can check for these */ - if (!dry_run) - gprs_ns2_free_nse(nse); - - vty_out(vty, "%sDeleted NS-VCs for NSEI %d%s", - dry_run ? "Not " : "", nsei, VTY_NEWLINE); - } - - return CMD_SUCCESS; -} - -/* Only for ttcn3 testing */ -DEFUN_HIDDEN(sgsn_pool_nsf_fixed, sgsn_pool_nsf_fixed_cmd, - "sgsn-pool nsf fixed NAME", - "SGSN pooling: load balancing across multiple SGSNs.\n" - "Customize the Network Selection Function.\n" - "Set a fixed SGSN to use (for testing).\n" - "The name of the SGSN to use.\n") -{ - const char *name = argv[0]; - struct gbproxy_sgsn *sgsn = gbproxy_sgsn_by_name(g_cfg, name); - - if (!sgsn) { - vty_out(vty, "%% Could not find SGSN with name %s%s", name, VTY_NEWLINE); - return CMD_WARNING; - } - - g_cfg->pool.nsf_override = sgsn; - return CMD_SUCCESS; -} - -DEFUN_HIDDEN(sgsn_pool_nsf_normal, sgsn_pool_nsf_normal_cmd, - "sgsn-pool nsf normal", - "SGSN pooling: load balancing across multiple SGSNs.\n" - "Customize the Network Selection Function.\n" - "Reset the NSF back to regular operation (for testing).\n") -{ - g_cfg->pool.nsf_override = NULL; - return CMD_SUCCESS; -} - -int gbproxy_vty_init(void) -{ - install_element_ve(&show_gbproxy_bvc_cmd); - install_element_ve(&show_gbproxy_cell_cmd); - install_element_ve(&show_gbproxy_links_cmd); - install_element_ve(&show_gbproxy_tlli_cache_cmd); - install_element_ve(&show_gbproxy_imsi_cache_cmd); - install_element_ve(&show_nri_all_cmd); - install_element_ve(&show_nri_nsei_cmd); - install_element_ve(&logging_fltr_bvc_cmd); - - install_element(ENABLE_NODE, &delete_gb_bvci_cmd); - install_element(ENABLE_NODE, &delete_gb_nsei_cmd); - install_element(ENABLE_NODE, &sgsn_pool_nsf_fixed_cmd); - install_element(ENABLE_NODE, &sgsn_pool_nsf_normal_cmd); - - install_element(CONFIG_NODE, &cfg_gbproxy_cmd); - install_node(&gbproxy_node, config_write_gbproxy); - install_element(GBPROXY_NODE, &cfg_pool_bvc_fc_ratio_cmd); - install_element(GBPROXY_NODE, &cfg_gbproxy_nri_bitlen_cmd); - install_element(GBPROXY_NODE, &cfg_gbproxy_nri_null_add_cmd); - install_element(GBPROXY_NODE, &cfg_gbproxy_nri_null_del_cmd); - - install_element(CONFIG_NODE, &cfg_sgsn_nsei_cmd); - install_node(&sgsn_node, config_write_sgsn); - install_element(SGSN_NODE, &cfg_sgsn_name_cmd); - install_element(SGSN_NODE, &cfg_sgsn_allow_attach_cmd); - install_element(SGSN_NODE, &cfg_sgsn_no_allow_attach_cmd); - install_element(SGSN_NODE, &cfg_sgsn_nri_add_cmd); - install_element(SGSN_NODE, &cfg_sgsn_nri_del_cmd); - - - return 0; -} - -int gbproxy_parse_config(const char *config_file, struct gbproxy_config *cfg) -{ - int rc; - - g_cfg = cfg; - rc = vty_read_config_file(config_file, NULL); - if (rc < 0) { - fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file); - return rc; - } - - return 0; -} -- cgit v1.2.3