/* 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 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 /* 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, PRIM_NS_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 ***********************************************************************/ /* route an uplink message on a PTP-BVC to a SGSN using the TLLI */ static int gbprox_bss2sgsn_tlli(struct gbproxy_cell *cell, struct msgb *msg, uint32_t tlli, bool sig_bvci) { struct gbproxy_bvc *sgsn_bvc; unsigned int i; /* FIXME: derive NRI from TLLI */ /* FIXME: find the SGSN for that NRI */ /* HACK: we currently simply pick the first SGSN we find */ for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { sgsn_bvc = cell->sgsn_bvc[i]; if (sgsn_bvc) return gbprox_relay2peer(msg, sgsn_bvc, sig_bvci ? 0 : sgsn_bvc->bvci); } return 0; } static int gbprox_bss2sgsn_null_nri(struct gbproxy_cell *cell, struct msgb *msg) { struct gbproxy_bvc *sgsn_bvc; unsigned int i; /* FIXME: find the SGSN for that NRI */ /* HACK: we currently simply pick the first SGSN we find */ for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { sgsn_bvc = cell->sgsn_bvc[i]; if (sgsn_bvc) return gbprox_relay2peer(msg, sgsn_bvc, sgsn_bvc->bvci); } return 0; } /* 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)); rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, tlli, false); } else if (TLVP_PRESENT(&tp, BSSGP_IE_IMSI)) { rc = gbprox_bss2sgsn_null_nri(bss_bvc->cell, msg); } 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: /* TODO: Implement via state tracking of PAGING-PS + DUMMY_PAGING_PS */ LOGPBVC(bss_bvc, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); 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); default: 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 */ ptp_bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); 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: /* FIXME: Implement TLLI Cache. Every SUSPEND/RESUME we must * take record of the TLLI->BVC mapping so we can map * back from TLLI->BVC when the SUSPEND/RESUME-ACK * arrives. Cache should have a timeout of 1-3 seconds * and the ACK should explicitly delete entries. */ #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; 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) { 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)) { /* 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; 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_PAGING_PS: case BSSGP_PDUT_PAGING_CS: /* process the paging request (LAI/RAI lookup) */ rc = gbprox_rx_paging(nse, msg, pdut_name, &tp, ns_bvci); 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: /* FIXME: handle based on TLLI cache. The RA-ID is not a unique * criterion, so we have to rely on the TLLI->BVC state created * while processing the SUSPEND/RESUME in uplink */ /* FIXME: route to SGSN baed on NRI derived from TLLI */ 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, PRIM_NS_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: * NS_AFF_CAUSE_VC_FAILURE, NS_AFF_CAUSE_VC_RECOVERY, NS_AFF_CAUSE_FAILURE, NS_AFF_CAUSE_RECOVERY, osmocom own causes NS_AFF_CAUSE_SNS_CONFIGURED, NS_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 NS_AFF_CAUSE_SNS_FAILURE: case NS_AFF_CAUSE_SNS_CONFIGURED: break; case NS_AFF_CAUSE_RECOVERY: LOGP(DPCU, 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 NS_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(DPCU, LOGL_NOTICE, "NS-NSE %d became unavailable\n", nsp->nsei); break; default: LOGP(DPCU, 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(DPCU, 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 PRIM_NS_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 PRIM_NS_STATUS: gprs_ns_prim_status_cb(cfg, nsp); break; default: LOGP(DPCU, 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); } 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); hash_init(cfg->bss_nses); hash_init(cfg->sgsn_nses); hash_init(cfg->cells); INIT_LLIST_HEAD(&cfg->sgsns); 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; }