From 5687ae65fa1a66448365f5da0cb0017ec6161efe Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 5 Dec 2020 19:59:45 +0100 Subject: gbproxy major rewrite for SGSN pool support Rewrite of a large part of osmo-gbproxy in order to prepare for SGSN pool support. The amount of changes are of such fundamental nature that it doesn't make sense to try to split this into hundreds of individual changesets. Related: OS#4472 Change-Id: Ie0746f17927a9509c3806cc80dc1a31d25df7937 --- include/osmocom/sgsn/gb_proxy.h | 75 +++- src/gbproxy/gb_proxy.c | 945 +++++++++++++++++++++++++--------------- src/gbproxy/gb_proxy_ctrl.c | 10 +- src/gbproxy/gb_proxy_main.c | 7 - src/gbproxy/gb_proxy_peer.c | 228 ++++++---- src/gbproxy/gb_proxy_vty.c | 55 ++- tests/vty_test_runner.py | 2 +- 7 files changed, 855 insertions(+), 467 deletions(-) diff --git a/include/osmocom/sgsn/gb_proxy.h b/include/osmocom/sgsn/gb_proxy.h index ac03a1134..e61b99101 100644 --- a/include/osmocom/sgsn/gb_proxy.h +++ b/include/osmocom/sgsn/gb_proxy.h @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -48,19 +49,43 @@ enum gbproxy_bvc_ctr { /* global gb-proxy configuration */ struct gbproxy_config { - /* parsed from config file */ - uint16_t nsip_sgsn_nsei; - /* NS instance of libosmogb */ struct gprs_ns2_inst *nsi; /* Linked list of all BSS side Gb peers */ DECLARE_HASHTABLE(bss_nses, 8); + /* hash table of all SGSN-side Gb peers */ + DECLARE_HASHTABLE(sgsn_nses, 8); + + /* hash table of all gbproxy_cell */ + DECLARE_HASHTABLE(cells, 8); + /* Counter */ struct rate_ctr_group *ctrg; }; +/* One Cell within the BSS: Links BSS-side BVC to SGSN-side BVCs */ +struct gbproxy_cell { + /* linked to gbproxy_config.cells hashtable */ + struct hlist_node list; + + /* point back to the config */ + struct gbproxy_config *cfg; + + /* BVCI of PTP BVCs associated to this cell */ + uint16_t bvci; + + /* Routing Area that this BVC is part of (raw 04.08 encoding) */ + uint8_t ra[6]; + + /* pointer to the BSS-side BVC */ + struct gbproxy_bvc *bss_bvc; + + /* pointers to SGSN-side BVC (one for each pool member) */ + struct gbproxy_bvc *sgsn_bvc[16]; +}; + /* One BVC inside an NSE */ struct gbproxy_bvc { /* linked to gbproxy_nse.bvcs */ @@ -75,11 +100,14 @@ struct gbproxy_bvc { /* Routing Area that this BVC is part of (raw 04.08 encoding) */ uint8_t ra[6]; - /* true if this BVC is blocked */ - bool blocked; - /* Counter */ struct rate_ctr_group *ctrg; + + /* the cell to which this BVC belongs */ + struct gbproxy_cell *cell; + + /* per-BVC FSM instance */ + struct osmo_fsm_inst *fi; }; /* one NS Entity that we interact with (BSS/PCU) */ @@ -93,19 +121,24 @@ struct gbproxy_nse { /* NSEI of the NSE */ uint16_t nsei; + /* Are we facing towards a SGSN (true) or BSS (false) */ + bool sgsn_facing; + /* List of all BVCs in this NSE */ DECLARE_HASHTABLE(bvcs, 10); }; /* Convenience logging macros for NSE/BVC */ #define LOGPNSE_CAT(NSE, SUBSYS, LEVEL, FMT, ARGS...) \ - LOGP(SUBSYS, LEVEL, "NSE(%05u/BSS) " FMT, (NSE)->nsei, ## ARGS) + LOGP(SUBSYS, LEVEL, "NSE(%05u/%s) " FMT, (NSE)->nsei, \ + (NSE)->sgsn_facing ? "SGSN" : "BSS", ## ARGS) #define LOGPNSE(NSE, LEVEL, FMT, ARGS...) \ LOGPNSE_CAT(NSE, DGPRS, LEVEL, FMT, ## ARGS) #define LOGPBVC_CAT(BVC, SUBSYS, LEVEL, FMT, ARGS...) \ - LOGP(SUBSYS, LEVEL, "NSE(%05u/BSS)-BVC(%05u/%s) " FMT, (BVC)->nse->nsei, (BVC)->bvci, \ - (BVC)->blocked ? "BLOCKED" : "UNBLOCKED", ## ARGS) + LOGP(SUBSYS, LEVEL, "NSE(%05u/%s)-BVC(%05u/%s) " FMT, (BVC)->nse->nsei, \ + (BVC)->nse->sgsn_facing ? "SGSN" : "BSS", (BVC)->bvci, \ + osmo_fsm_inst_state_name((BVC)->fi), ## ARGS) #define LOGPBVC(BVC, LEVEL, FMT, ARGS...) \ LOGPBVC_CAT(BVC, DGPRS, LEVEL, FMT, ## ARGS) @@ -133,21 +166,23 @@ int gprs_ns2_prim_cb(struct osmo_prim_hdr *oph, void *ctx); void gbprox_reset(struct gbproxy_config *cfg); /* Peer handling */ -struct gbproxy_bvc *gbproxy_bvc_by_bvci( - struct gbproxy_config *cfg, uint16_t bvci); -struct gbproxy_bvc *gbproxy_bvc_by_nsei( - struct gbproxy_config *cfg, uint16_t nsei); -struct gbproxy_bvc *gbproxy_bvc_by_rai( - struct gbproxy_config *cfg, const uint8_t *ra); +#define NSE_F_SGSN 0x0001 +#define NSE_F_BSS 0x0002 + +struct gbproxy_bvc *gbproxy_bvc_by_bvci(struct gbproxy_nse *nse, uint16_t bvci); struct gbproxy_bvc *gbproxy_bvc_alloc(struct gbproxy_nse *nse, uint16_t bvci); void gbproxy_bvc_free(struct gbproxy_bvc *bvc); -void gbproxy_bvc_move(struct gbproxy_bvc *bvc, struct gbproxy_nse *nse); -int gbproxy_cleanup_bvcs(struct gbproxy_config *cfg, uint16_t nsei, uint16_t bvci); +int gbproxy_cleanup_bvcs(struct gbproxy_nse *nse, uint16_t bvci); + +struct gbproxy_cell *gbproxy_cell_alloc(struct gbproxy_config *cfg, uint16_t bvci); +struct gbproxy_cell *gbproxy_cell_by_bvci(struct gbproxy_config *cfg, uint16_t bvci); +void gbproxy_cell_free(struct gbproxy_cell *cell); +bool gbproxy_cell_add_sgsn_bvc(struct gbproxy_cell *cell, struct gbproxy_bvc *bvc); /* NSE handling */ -struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei); +struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei, bool sgsn_facing); void gbproxy_nse_free(struct gbproxy_nse *nse); -struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei); -struct gbproxy_nse *gbproxy_nse_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei); +struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei, uint32_t flags); +struct gbproxy_nse *gbproxy_nse_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei, bool sgsn_facing); #endif diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c index ecb74c056..a92090682 100644 --- a/src/gbproxy/gb_proxy.c +++ b/src/gbproxy/gb_proxy.c @@ -1,6 +1,6 @@ /* NS-over-IP proxy */ -/* (C) 2010 by Harald Welte +/* (C) 2010-2020 by Harald Welte * (C) 2010-2013 by On-Waves * (C) 2013 by Holger Hans Peter Freyther * All Rights Reserved @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -80,29 +81,7 @@ static const struct rate_ctr_group_desc global_ctrg_desc = { static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_bvc *bvc, uint16_t ns_bvci); -static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg, - uint16_t ns_bvci, uint16_t sgsn_nsei); - - -static int gbproxy_is_sgsn_nsei(struct gbproxy_config *cfg, uint16_t nsei) -{ - return nsei == cfg->nsip_sgsn_nsei; -} - -static int check_bvc_nsei(struct gbproxy_bvc *bvc, uint16_t nsei) -{ - OSMO_ASSERT(bvc); - OSMO_ASSERT(bvc->nse); - if (bvc->nse->nsei != nsei) { - LOGPBVC(bvc, LOGL_NOTICE, "Peer entry doesn't match current NSEI " - "via NSE(%05u/BSS)\n", nsei); - rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_INV_NSEI]); - return 0; - } - - return 1; -} /* 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) @@ -125,6 +104,7 @@ static void strip_ns_hdr(struct msgb *msg) 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) @@ -150,6 +130,7 @@ static int gbprox_relay2sgsn(struct gbproxy_config *cfg, struct msgb *old_msg, 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, @@ -208,198 +189,456 @@ static int gbprox_relay2peer(struct msgb *old_msg, struct gbproxy_bvc *bvc, return rc; } -static int block_unblock_bvc(struct gbproxy_config *cfg, uint16_t ptp_bvci, uint8_t pdu_type) +int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) { - struct gbproxy_bvc *bvc; - - bvc = gbproxy_bvc_by_bvci(cfg, ptp_bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_ERROR, "BVC(%05u/??) Cannot find BSS\n", - ptp_bvci); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return -ENOENT; - } - - switch (pdu_type) { - case BSSGP_PDUT_BVC_BLOCK_ACK: - bvc->blocked = true; - rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_BLOCKED]); - break; - case BSSGP_PDUT_BVC_UNBLOCK_ACK: - bvc->blocked = false; - rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_UNBLOCKED]); - break; - default: - break; - } return 0; } -/* Send a message to a bvc identified by ptp_bvci but using ns_bvci - * in the NS hdr */ -static int gbprox_relay2bvci(struct gbproxy_config *cfg, struct msgb *msg, uint16_t ptp_bvci, - uint16_t ns_bvci) + +/*********************************************************************** + * 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 *bvc; + struct gbproxy_bvc *sgsn_bvc; + unsigned int i; - bvc = gbproxy_bvc_by_bvci(cfg, ptp_bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_ERROR, "BVC(%05u/??) Cannot find BSS\n", - ptp_bvci); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return -ENOENT; - } + /* FIXME: derive NRI from TLLI */ + /* FIXME: find the SGSN for that NRI */ - return gbprox_relay2peer(msg, bvc, ns_bvci); + /* 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; } -int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) +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_config *cfg, - struct msgb *msg, uint16_t nsei, - uint16_t ns_bvci) +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); - struct gbproxy_bvc *bvc; + 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, "NSE(%05u/BSS) BVCI=%05u is not PTP\n", nsei, ns_bvci); + 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, "NSE(%05u/%05u) %s not allowed in PTP BVC\n", - nsei, ns_bvci, osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + 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, "NSE(%05u/%05u) %s not allowed in uplink direction\n", - nsei, ns_bvci, osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + 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); } - bvc = gbproxy_bvc_by_bvci(cfg, ns_bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_NOTICE, "BVC(%05u/??) Didn't find bvc " - "for PTP message from NSE(%05u/BSS), " - "discarding message\n", - ns_bvci, nsei); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, - &ns_bvci, 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); } - /* TODO: Should we discard this message if the check fails */ - check_bvc_nsei(bvc, nsei); + 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: + /* TODO: Implement via FSM */ + //rc = osmo_fsm_inst_dispatch(bss_bvc->fi, FIXME, &tp); + LOGPBVC(bss_bvc, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); + 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 gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn_nsei); + return 0; } /* Receive an incoming PTP message from a SGSN-side NS-VC */ -static int gbprox_rx_ptp_from_sgsn(struct gbproxy_config *cfg, - struct msgb *msg, uint16_t nsei, - uint16_t ns_bvci) +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); - struct gbproxy_bvc *bvc; + const char *pdut_name = osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type); + struct gbproxy_bvc *sgsn_bvc, *bss_bvc; + char log_pfx[32]; + + 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, "NSE(%05u/BSS) BVCI=%05u is not PTP\n", nsei, ns_bvci); + 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, "NSE(%05u/%05u) %s not allowed in PTP BVC\n", - nsei, ns_bvci, osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + 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, "NSE(%05u/%05u) %s not allowed in downlink direction\n", - nsei, ns_bvci, osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type)); + 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); } - bvc = gbproxy_bvc_by_bvci(cfg, ns_bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_INFO, "BVC(%05u/??) Didn't find bvc for " - "for message from NSE(%05u/SGSN)\n", - ns_bvci, nsei); - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, - &ns_bvci, 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 (bvc->blocked) { - LOGPBVC(bvc, LOGL_NOTICE, "Dropping PDU for " - "blocked BVC via NSE(%05u/SGSN)\n", nsei); - rate_ctr_inc(&bvc->ctrg->ctr[GBPROX_PEER_CTR_DROPPED]); + 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); } + OSMO_ASSERT(sgsn_bvc->cell); + bss_bvc = sgsn_bvc->cell->bss_bvc; + + 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; + 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) { + struct gbproxy_nse *sgsn_nse; + /* 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); + + /* link us to the cell and vice-versa */ + bvc->cell->bss_bvc = bvc; + + /* allocate the 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); + + if (!sgsn_bvc) { + sgsn_bvc = gbproxy_bvc_alloc(sgsn_nse, bvci); + OSMO_ASSERT(sgsn_bvc); + + sgsn_bvc->cell = bvc->cell; + 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); + } else { + OSMO_ASSERT(sgsn_bvc->cell == bvc->cell); + } + } + } + + /* 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; + } +} - return gbprox_relay2peer(msg, bvc, ns_bvci); +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, +}; + +/* 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 gbprox_rx_bvc_reset_from_bss(struct gbproxy_config *cfg, struct msgb *msg, - uint16_t nsei, struct tlv_parsed *tp) +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; + uint16_t bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); + uint32_t features = 0; // FIXME: make configurable - if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) || !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) { - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); - return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); - } + LOGPNSE(nse, LOGL_INFO, "Rx BVC-RESET (BVCI=%05u)\n", bvci); - bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); - LOGP(DGPRS, LOGL_INFO, "NSE(%05u) Rx BVC RESET (BVCI=%05u)\n", nsei, 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) */ - /* Ensure the NSE bvc is there and clear all PtP BVCs */ - struct gbproxy_nse *nse = gbproxy_nse_by_nsei_or_new(cfg, nsei); - if (!nse) { - LOGP(DGPRS, LOGL_ERROR, "Could not create NSE(%05u)\n", nsei); - bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, 0, msg); - return 0; + 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); } - - gbproxy_cleanup_bvcs(cfg, nsei, 0); - - /* FIXME: only do this if SGSN is alive! */ - LOGPNSE(nse, LOGL_INFO, "Tx fake BVC RESET ACK of BVCI=0\n"); - bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, 0, 0); - return 0; } else { - from_bvc = gbproxy_bvc_by_bvci(cfg, bvci); + from_bvc = gbproxy_bvc_by_bvci(nse, bvci); if (!from_bvc) { - struct gbproxy_nse *nse = gbproxy_nse_by_nsei(cfg, nsei); - if (!nse) { - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u) Got PtP BVC reset before signalling reset for " - "BVCI=%05u\n", nsei, bvci); - bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_STATE, NULL, msg); - return 0; - } /* 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); - LOGPBVC(from_bvc, LOGL_INFO, "Allocated new bvc\n"); + 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); + 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); @@ -410,7 +649,8 @@ static int gbprox_rx_bvc_reset_from_bss(struct gbproxy_config *cfg, struct msgb /* 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 @@ -422,126 +662,158 @@ static int gbprox_rx_bvc_reset_from_bss(struct gbproxy_config *cfg, struct msgb LOGPBVC(from_bvc, LOGL_INFO, "Cell ID %s\n", osmo_rai_name(&raid)); } } - /* continue processing / relaying to SGSN[s] */ - return 1; + /* 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_config *cfg, - struct msgb *msg, uint16_t nsei, - uint16_t ns_bvci) +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); - struct tlv_parsed tp; 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; - struct gprs_ra_id raid; char log_pfx[32]; + uint16_t ptp_bvci; + uint32_t tlli; int rc; - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/BSS)", nsei); + 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 signalling\n", log_pfx, ns_bvci); + 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, - osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, pdu_type)); + 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, - osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, pdu_type)); + 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(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); + 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: - /* We implement RAI snooping during SUSPEND/RESUME, since it - * establishes a relationsip between BVCI/bvc and the routeing - * area identification. The snooped information is then used - * for routing the {SUSPEND,RESUME}_[N]ACK back to the correct - * BSSGP */ - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) - goto err_mand_ie; - from_bvc = gbproxy_bvc_by_nsei(cfg, nsei); + /* 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; - memcpy(from_bvc->ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA), - sizeof(from_bvc->ra)); - gsm48_parse_ra(&raid, from_bvc->ra); - LOGPBVC(from_bvc, LOGL_INFO, "BSSGP SUSPEND/RESUME " - "RAI snooping: RAI %s\n", - osmo_rai_name(&raid)); - /* FIXME: This only supports one BSS per RA */ + gbprox_bss2sgsn_tlli(from_bvc->cell, msg, tlli, true); break; - case BSSGP_PDUT_BVC_RESET: - rc = gbprox_rx_bvc_reset_from_bss(cfg, msg, nsei, &tp); - /* if function retruns 0, we terminate processing here */ - if (rc == 0) - return 0; + default: + LOGPNSE(nse, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); break; } - return gbprox_relay2sgsn(cfg, msg, ns_bvci, cfg->nsip_sgsn_nsei); + return rc; err_no_bvc: - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/BSS) cannot find bvc based on NSEI\n", - nsei); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_INV_NSEI]); + 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); -err_mand_ie: - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/BSS) missing mandatory RA IE\n", - nsei); - rate_ctr_inc(&cfg->ctrg->ctr[GBPROX_GLOB_CTR_PROTO_ERR_BSS]); - return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } /* Receive paging request from SGSN, we need to relay to proper BSS */ -static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct tlv_parsed *tp, - uint32_t nsei, uint16_t ns_bvci) +static int gbprox_rx_paging(struct gbproxy_nse *nse, struct msgb *msg, const char *pdut_name, + struct tlv_parsed *tp, uint16_t ns_bvci) { - struct gbproxy_nse *nse; - struct gbproxy_bvc *bvc; + struct gbproxy_config *cfg = nse->cfg; + struct gbproxy_bvc *sgsn_bvc, *bss_bvc; 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 */ - LOGP(DGPRS, LOGL_INFO, "NSE(%05u/SGSN) BSSGP PAGING\n", - nsei); 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; - bvc = gbproxy_bvc_by_bvci(cfg, bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u/SGSN) BSSGP PAGING: " - "unable to route: BVCI=%05u unknown\n", nsei, bvci); + sgsn_bvc = gbproxy_bvc_by_bvci(nse, bvci); + if (!sgsn_bvc) { + LOGPNSE(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(bvc, LOGL_INFO, "routing by BVCI\n"); - return gbprox_relay2peer(msg, bvc, ns_bvci); + 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, bvc, list) { - if (!memcmp(bvc->ra, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA), 6)) { - LOGPNSE(nse, LOGL_INFO, "routing to NSE (RAI match)\n"); - gbprox_relay2peer(msg, bvc, ns_bvci); + 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; @@ -552,10 +824,11 @@ static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct 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, bvc, list) { - if (!memcmp(bvc->ra, TLVP_VAL(tp, BSSGP_IE_LOCATION_AREA), 5)) { - LOGPNSE(nse, LOGL_INFO, "routing to NSE (LAI match)\n"); - gbprox_relay2peer(msg, bvc, ns_bvci); + 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; @@ -565,23 +838,21 @@ static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct } 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, bvc, list) { - LOGPNSE(nse, LOGL_INFO, "routing to NSE (broadcast)\n"); - gbprox_relay2peer(msg, bvc, ns_bvci); + 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 { - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/SGSN) BSSGP PAGING: " - "unable to route, missing IE\n", nsei); + LOGPNSE(nse, LOGL_ERROR, "BSSGP PAGING: unable to route, missing IE\n"); rate_ctr_inc(&cfg->ctrg->ctr[errctr]); } if (n_nses == 0) { - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/SGSN) BSSGP PAGING: " - "unable to route, no destination found\n", nsei); + LOGPNSE(nse, LOGL_ERROR, "BSSGP PAGING: unable to route, no destination found\n"); rate_ctr_inc(&cfg->ctrg->ctr[errctr]); return -EINVAL; } @@ -589,63 +860,41 @@ static int gbprox_rx_paging(struct gbproxy_config *cfg, struct msgb *msg, struct } /* Receive an incoming BVC-RESET message from the SGSN */ -static int rx_reset_from_sgsn(struct gbproxy_config *cfg, - struct msgb *orig_msg, - struct msgb *msg, struct tlv_parsed *tp, - uint32_t nsei, uint16_t ns_bvci) +static int rx_bvc_reset_from_sgsn(struct gbproxy_nse *nse, struct msgb *msg, struct tlv_parsed *tp, + uint16_t ns_bvci) { - struct gbproxy_nse *nse; - struct gbproxy_bvc *bvc; - uint16_t ptp_bvci; - int i, j; + uint16_t ptp_bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); + struct gbproxy_bvc *from_bvc; - if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) { - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]); - return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, - NULL, orig_msg); - } - ptp_bvci = ntohs(tlvp_val16_unal(tp, BSSGP_IE_BVCI)); - - if (ptp_bvci >= 2) { - /* A reset for a PTP BVC was received, forward it to its - * respective bvc */ - bvc = gbproxy_bvc_by_bvci(cfg, ptp_bvci); - if (!bvc) { - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/SGSN) BVCI=%05u: Cannot find BSS\n", - nsei, ptp_bvci); - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_INV_BVCI]); - return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, - &ptp_bvci, orig_msg); - } - return gbprox_relay2peer(msg, bvc, ns_bvci); - } + LOGPNSE(nse, LOGL_INFO, "Rx BVC-RESET (BVCI=%05u)\n", ptp_bvci); - /* A reset for the Signalling entity has been received - * from the SGSN. As the signalling BVCI is shared - * among all the BSS's that we multiplex, it needs to - * be relayed */ - hash_for_each(cfg->bss_nses, i, nse, list) { - hash_for_each(nse->bvcs, j, bvc, list) - gbprox_relay2peer(msg, bvc, ns_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_config *cfg, - struct msgb *orig_msg, uint32_t nsei, - uint16_t ns_bvci) +static int gbprox_rx_sig_from_sgsn(struct gbproxy_nse *nse, struct msgb *orig_msg, uint16_t ns_bvci) { - struct bssgp_normal_hdr *bgph = - (struct bssgp_normal_hdr *) msgb_bssgph(orig_msg); - struct tlv_parsed tp; + struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(orig_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; - struct gbproxy_nse *nse; - struct gbproxy_bvc *bvc; uint16_t bvci; struct msgb *msg; char log_pfx[32]; @@ -653,7 +902,9 @@ static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg, int cause; int i; - snprintf(log_pfx, sizeof(log_pfx), "NSE(%05u/SGSN)", nsei); + 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); @@ -661,14 +912,12 @@ static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg, } if (!(bssgp_pdu_type_flags(pdu_type) & BSSGP_PDUF_SIG)) { - LOGP(DGPRS, LOGL_NOTICE, "%s %s not allowed in signalling BVC\n", log_pfx, - osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, pdu_type)); + 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, orig_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, - osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, pdu_type)); + 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, orig_msg); } @@ -685,52 +934,62 @@ static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg, 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: - rc = rx_reset_from_sgsn(cfg, msg, orig_msg, &tp, nsei, ns_bvci); + /* 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: - /* simple case: BVCI IE is mandatory */ - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) - goto err_mand_ie; bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - if (bvci == BVCI_SIGNALLING) { - /* TODO: Reset all PTP BVCIs */ - } else { - rc = gbprox_relay2bvci(cfg, msg, bvci, ns_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 */ - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) - goto err_mand_ie; bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - rc = gbprox_relay2bvci(cfg, msg, bvci, ns_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(cfg, msg, &tp, nsei, ns_bvci); + rc = gbprox_rx_paging(nse, msg, pdut_name, &tp, ns_bvci); break; case BSSGP_PDUT_STATUS: /* Some exception has occurred */ - LOGP(DGPRS, LOGL_NOTICE, - "NSE(%05u/SGSN) BSSGP STATUS ", nsei); - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_CAUSE, 1)) { - LOGPC(DGPRS, LOGL_NOTICE, "\n"); - goto err_mand_ie; - } cause = *TLVP_VAL(&tp, BSSGP_IE_CAUSE); - LOGPC(DGPRS, LOGL_NOTICE, - "cause=0x%02x(%s) ", *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); - - if (cause == BSSGP_CAUSE_UNKNOWN_BVCI) - rc = gbprox_relay2bvci(cfg, msg, bvci, ns_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; @@ -739,46 +998,30 @@ static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg, case BSSGP_PDUT_SUSPEND_NACK: case BSSGP_PDUT_RESUME_ACK: case BSSGP_PDUT_RESUME_NACK: - /* RAI IE is mandatory */ - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) - goto err_mand_ie; - bvc = gbproxy_bvc_by_rai(cfg, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA)); - if (!bvc) - goto err_no_bvc; - rc = gbprox_relay2peer(msg, bvc, ns_bvci); - break; - case BSSGP_PDUT_BVC_BLOCK_ACK: - case BSSGP_PDUT_BVC_UNBLOCK_ACK: - if (!TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) - goto err_mand_ie; - bvci = ntohs(tlvp_val16_unal(&tp, BSSGP_IE_BVCI)); - if (bvci == 0) { - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u/SGSN) BSSGP " - "%sBLOCK_ACK for signalling BVCI ?!?\n", nsei, - pdu_type == BSSGP_PDUT_BVC_UNBLOCK_ACK ? "UN":""); - /* TODO: should we send STATUS ? */ - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_INV_BVCI]); - } else { - /* Mark BVC as (un)blocked */ - block_unblock_bvc(cfg, bvci, pdu_type); - } - rc = gbprox_relay2bvci(cfg, msg, bvci, ns_bvci); + /* 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: - LOGP(DGPRS, LOGL_DEBUG, - "NSE(%05u/SGSN) BSSGP %s: broadcasting\n", nsei, bssgp_pdu_str(pdu_type)); + 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, orig_msg); + break; default: - LOGP(DGPRS, LOGL_NOTICE, "NSE(%05u/SGSN) BSSGP PDU type %s not supported\n", nsei, - bssgp_pdu_str(pdu_type)); - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]); + 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, orig_msg); break; } @@ -786,21 +1029,19 @@ static int gbprox_rx_sig_from_sgsn(struct gbproxy_config *cfg, msgb_free(msg); return rc; -err_mand_ie: - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/SGSN) missing mandatory IE\n", - nsei); - rate_ctr_inc(&cfg->ctrg-> - ctr[GBPROX_GLOB_CTR_PROTO_ERR_SGSN]); - msgb_free(msg); - return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, orig_msg); + err_no_bvc: - LOGP(DGPRS, LOGL_ERROR, "NSE(%05u/SGSN) cannot find bvc based on RAI\n", - nsei); + LOGPNSE(nse, LOGL_ERROR, "Rx %s: Cannot find BVC\n", pdut_name); rate_ctr_inc(&cfg->ctrg-> ctr[GBPROX_GLOB_CTR_INV_RAI]); msgb_free(msg); return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, orig_msg); } + +/*********************************************************************** + * libosmogb NS/BSSGP integration + ***********************************************************************/ + int gbprox_bssgp_send_cb(void *ctx, struct msgb *msg) { int rc; @@ -820,34 +1061,36 @@ int gbprox_bssgp_send_cb(void *ctx, struct msgb *msg) /* Main input function for Gb proxy */ int gbprox_rcvmsg(void *ctx, struct msgb *msg) { - int rc; - uint16_t nsei = msgb_nsei(msg); - uint16_t ns_bvci = msgb_bvci(msg); struct gbproxy_config *cfg = (struct gbproxy_config *) ctx; - - int remote_end_is_sgsn = gbproxy_is_sgsn_nsei(cfg, nsei); + 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); - /* Only BVCI=0 messages need special treatment */ - if (ns_bvci == 0 || ns_bvci == 1) { - if (remote_end_is_sgsn) - rc = gbprox_rx_sig_from_sgsn(cfg, msg, nsei, ns_bvci); + 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 - rc = gbprox_rx_sig_from_bss(cfg, msg, nsei, ns_bvci); - } else { - /* All other BVCI are PTP */ - if (remote_end_is_sgsn) - rc = gbprox_rx_ptp_from_sgsn(cfg, msg, nsei, - ns_bvci); + 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 - rc = gbprox_rx_ptp_from_bss(cfg, msg, nsei, - ns_bvci); + return gbprox_rx_ptp_from_bss(nse, msg, ns_bvci); } - return rc; + return 0; } /* TODO: What about handling: @@ -865,7 +1108,9 @@ void gprs_ns_prim_status_cb(struct gbproxy_config *cfg, struct osmo_gprs_ns2_pri /* 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: @@ -874,16 +1119,16 @@ void gprs_ns_prim_status_cb(struct gbproxy_config *cfg, struct osmo_gprs_ns2_pri case NS_AFF_CAUSE_RECOVERY: LOGP(DPCU, LOGL_NOTICE, "NS-NSE %d became available\n", nsp->nsei); - if (gbproxy_is_sgsn_nsei(cfg, nsp->nsei)) { - /* look-up or create the BTS context for this BVC */ - struct bssgp_bvc_ctx *bctx = btsctx_by_bvci_nsei(nsp->bvci, nsp->nsei); - if (!bctx) - bctx = btsctx_alloc(nsp->bvci, nsp->nsei); - - bssgp_tx_bvc_reset_nsei_bvci(cfg->nsip_sgsn_nsei, 0, BSSGP_CAUSE_OML_INTERV, NULL, 0); + 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 */ @@ -902,9 +1147,11 @@ void gprs_ns_prim_status_cb(struct gbproxy_config *cfg, struct osmo_gprs_ns2_pri if (!bvc->blocked) break; - bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK, cfg->nsip_sgsn_nsei, - bvc->bvci, 0); + 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: @@ -973,6 +1220,8 @@ void gbprox_reset(struct gbproxy_config *cfg) gbproxy_nse_free(nse); } + /* FIXME: cells */ + /* FIXME: SGSN side BVCs (except signaling) */ rate_ctr_group_free(cfg->ctrg); gbproxy_init_config(cfg); diff --git a/src/gbproxy/gb_proxy_ctrl.c b/src/gbproxy/gb_proxy_ctrl.c index 21e56dd03..456163466 100644 --- a/src/gbproxy/gb_proxy_ctrl.c +++ b/src/gbproxy/gb_proxy_ctrl.c @@ -61,9 +61,11 @@ static int get_nsvc_state(struct ctrl_cmd *cmd, void *data) cmd->reply = talloc_strdup(cmd, ""); /* NS-VCs for SGSN */ - nse = gprs_ns2_nse_by_nsei(nsi, cfg->nsip_sgsn_nsei); - if (nse) - gprs_ns2_nse_foreach_nsvc(nse, &ctrl_nsvc_state_cb, cmd); + 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) { @@ -95,7 +97,7 @@ static int get_gbproxy_state(struct ctrl_cmd *cmd, void *data) nse_peer->nsei, bvc->bvci, raid.mcc, raid.mnc, raid.lac, raid.rac, - bvc->blocked ? "BLOCKED" : "UNBLOCKED"); + osmo_fsm_inst_state_name(bvc->fi)); } } diff --git a/src/gbproxy/gb_proxy_main.c b/src/gbproxy/gb_proxy_main.c index a9c132e86..318f3dc87 100644 --- a/src/gbproxy/gb_proxy_main.c +++ b/src/gbproxy/gb_proxy_main.c @@ -315,13 +315,6 @@ int main(int argc, char **argv) exit(1); } - if (!gprs_ns2_nse_by_nsei(gbcfg->nsi, gbcfg->nsip_sgsn_nsei)) { - LOGP(DGPRS, LOGL_FATAL, "You cannot proxy to NSE(%05u) " - "without creating that NSEI before\n", - gbcfg->nsip_sgsn_nsei); - exit(2); - } - if (daemonize) { rc = osmo_daemonize(); if (rc < 0) { diff --git a/src/gbproxy/gb_proxy_peer.c b/src/gbproxy/gb_proxy_peer.c index 052e5770b..a0586fefe 100644 --- a/src/gbproxy/gb_proxy_peer.c +++ b/src/gbproxy/gb_proxy_peer.c @@ -55,56 +55,13 @@ static const struct rate_ctr_group_desc bvc_ctrg_desc = { /* Find the gbproxy_bvc by its BVCI. There can only be one match */ -struct gbproxy_bvc *gbproxy_bvc_by_bvci(struct gbproxy_config *cfg, uint16_t bvci) +struct gbproxy_bvc *gbproxy_bvc_by_bvci(struct gbproxy_nse *nse, uint16_t bvci) { - struct gbproxy_nse *nse; - int i; - - hash_for_each(cfg->bss_nses, i, nse, list) { - struct gbproxy_bvc *bvc; - hash_for_each_possible(nse->bvcs, bvc, list, bvci) { - if (bvc->bvci == bvci) - return bvc; - } - } - return NULL; -} - -/* Find the gbproxy_bvc by its NSEI */ -/* FIXME: Only returns the first bvc, but we could have multiple on this nsei */ -struct gbproxy_bvc *gbproxy_bvc_by_nsei(struct gbproxy_config *cfg, - uint16_t nsei) -{ - struct gbproxy_nse *nse = gbproxy_nse_by_nsei(cfg, nsei); struct gbproxy_bvc *bvc; - int i; - - if (!nse || hash_empty(nse->bvcs)) - return NULL; - - /* return the first entry we find */ - hash_for_each(nse->bvcs, i, bvc, list) - return bvc; - - return NULL; -} - -/* look-up a bvc by its Routeing Area Identification (RAI) */ -/* FIXME: this doesn't make sense, as RA can span multiple bvcs! */ -struct gbproxy_bvc *gbproxy_bvc_by_rai(struct gbproxy_config *cfg, - const uint8_t *ra) -{ - struct gbproxy_nse *nse; - int i, j; - - hash_for_each(cfg->bss_nses, i, nse, list) { - struct gbproxy_bvc *bvc; - hash_for_each(nse->bvcs, j, bvc, list) { - if (!memcmp(bvc->ra, ra, 6)) - return bvc; - } + hash_for_each_possible(nse->bvcs, bvc, list, bvci) { + if (bvc->bvci == bvci) + return bvc; } - return NULL; } @@ -129,11 +86,13 @@ struct gbproxy_bvc *gbproxy_bvc_alloc(struct gbproxy_nse *nse, uint16_t bvci) hash_add(nse->bvcs, &bvc->list, bvc->bvci); - return bvc; + return bvc; } void gbproxy_bvc_free(struct gbproxy_bvc *bvc) { + struct gbproxy_cell *cell; + if (!bvc) return; @@ -142,45 +101,141 @@ void gbproxy_bvc_free(struct gbproxy_bvc *bvc) 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); } -void gbproxy_bvc_move(struct gbproxy_bvc *bvc, struct gbproxy_nse *nse) +/*! 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) { - hash_del(&bvc->list); - hash_add(nse->bvcs, &bvc->list, bvc->bvci); - bvc->nse = nse; + 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; } -/*! remove bvcs (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 BVCs; if != 0: BVCI of the single BVC to clean up */ -int gbproxy_cleanup_bvcs(struct gbproxy_config *cfg, uint16_t nsei, uint16_t bvci) + +/*********************************************************************** + * CELL + ***********************************************************************/ + +/* Allocate a new 'cell' object */ +struct gbproxy_cell *gbproxy_cell_alloc(struct gbproxy_config *cfg, uint16_t bvci) { - int i, j, counter = 0; - struct gbproxy_nse *nse; - struct hlist_node *ntmp; + 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); + + 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); - hash_for_each_safe(cfg->bss_nses, i, ntmp, nse, list) { - struct gbproxy_bvc *bvc; - struct hlist_node *btmp; - if (nse->nsei != nsei) + 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; + + /* 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; - hash_for_each_safe(nse->bvcs, j, btmp, bvc, list) { - if (bvci && bvc->bvci != bvci) - continue; + if (cell->sgsn_bvc[i]->cell) + cell->sgsn_bvc[i]->cell = NULL; + } - gbproxy_bvc_free(bvc); - counter += 1; + 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; + return true; } } - - return counter; + return false; } -struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei) +/*********************************************************************** + * 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); @@ -191,8 +246,12 @@ struct gbproxy_nse *gbproxy_nse_alloc(struct gbproxy_config *cfg, uint16_t nsei) nse->nsei = nsei; nse->cfg = cfg; + nse->sgsn_facing = sgsn_facing; - hash_add(cfg->bss_nses, &nse->list, nsei); + if (sgsn_facing) + hash_add(cfg->sgsn_nses, &nse->list, nsei); + else + hash_add(cfg->bss_nses, &nse->list, nsei); hash_init(nse->bvcs); @@ -216,27 +275,36 @@ void gbproxy_nse_free(struct gbproxy_nse *nse) talloc_free(nse); } -struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei) +struct gbproxy_nse *gbproxy_nse_by_nsei(struct gbproxy_config *cfg, uint16_t nsei, uint32_t flags) { struct gbproxy_nse *nse; OSMO_ASSERT(cfg); - hash_for_each_possible(cfg->bss_nses, nse, list, nsei) { - if (nse->nsei == nsei) - return nse; + 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) +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); + nse = gbproxy_nse_by_nsei(cfg, nsei, sgsn_facing ? NSE_F_SGSN : NSE_F_BSS); if (!nse) - nse = gbproxy_nse_alloc(cfg, nsei); + nse = gbproxy_nse_alloc(cfg, nsei, sgsn_facing); return nse; } diff --git a/src/gbproxy/gb_proxy_vty.c b/src/gbproxy/gb_proxy_vty.c index da5710a7a..a9db596df 100644 --- a/src/gbproxy/gb_proxy_vty.c +++ b/src/gbproxy/gb_proxy_vty.c @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -43,6 +44,7 @@ #include #include + static struct gbproxy_config *g_cfg = NULL; /* @@ -61,7 +63,7 @@ static void gbprox_vty_print_bvc(struct vty *vty, struct gbproxy_bvc *bvc) vty_out(vty, "NSEI %5u, PTP-BVCI %5u, " "RAI %s", bvc->nse->nsei, bvc->bvci, osmo_rai_name(&raid)); - if (bvc->blocked) + if (bssgp_bvc_fsm_is_unblocked(bvc->fi)) vty_out(vty, " [BVC-BLOCKED]"); vty_out(vty, "%s", VTY_NEWLINE); @@ -69,10 +71,14 @@ static void gbprox_vty_print_bvc(struct vty *vty, struct gbproxy_bvc *bvc) static int config_write_gbproxy(struct vty *vty) { + struct gbproxy_nse *nse; + int i; + vty_out(vty, "gbproxy%s", VTY_NEWLINE); - vty_out(vty, " sgsn nsei %u%s", g_cfg->nsip_sgsn_nsei, - VTY_NEWLINE); + hash_for_each(g_cfg->sgsn_nses, i, nse, list) { + vty_out(vty, " sgsn nsei %u%s", nse->nsei, VTY_NEWLINE); + } return CMD_SUCCESS; } @@ -86,6 +92,9 @@ DEFUN(cfg_gbproxy, return CMD_SUCCESS; } +extern const struct bssgp_bvc_fsm_ops sgsn_sig_bvc_fsm_ops; +#include + DEFUN(cfg_nsip_sgsn_nsei, cfg_nsip_sgsn_nsei_cmd, "sgsn nsei <0-65534>", @@ -93,10 +102,36 @@ DEFUN(cfg_nsip_sgsn_nsei, "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]); + struct gbproxy_nse *nse; + struct gbproxy_bvc *bvc; + + nse = gbproxy_nse_by_nsei_or_new(g_cfg, nsei, true); + if (!nse) + goto free_nothing; + + if (!gbproxy_bvc_by_bvci(nse, 0)) { + uint8_t cause = BSSGP_CAUSE_OML_INTERV; + bvc = gbproxy_bvc_alloc(nse, 0); + if (!bvc) + goto free_nse; + 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); + } - g_cfg->nsip_sgsn_nsei = nsei; return CMD_SUCCESS; + +free_bvc: + gbproxy_bvc_free(bvc); +free_nse: + gbproxy_nse_free(nse); +free_nothing: + vty_out(vty, "%% Unable to create NSE for NSEI=%05u%s", nsei, VTY_NEWLINE); + return CMD_WARNING; } static void log_set_bvc_filter(struct log_target *target, @@ -181,9 +216,15 @@ DEFUN(delete_gb_bvci, delete_gb_bvci_cmd, { 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; - counter = gbproxy_cleanup_bvcs(g_cfg, nsei, bvci); + 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); @@ -219,8 +260,8 @@ DEFUN(delete_gb_nsei, delete_gb_nsei_cmd, if (delete_bvc) { if (!dry_run) { - struct gbproxy_nse *nse = gbproxy_nse_by_nsei(g_cfg, nsei); - counter = gbproxy_cleanup_bvcs(g_cfg, nsei, 0); + 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; diff --git a/tests/vty_test_runner.py b/tests/vty_test_runner.py index beb0ee126..4587564bc 100755 --- a/tests/vty_test_runner.py +++ b/tests/vty_test_runner.py @@ -102,7 +102,7 @@ class TestVTYGbproxy(TestVTYBase): def testVtyDeletePeer(self): self.vty.enable() - self.assertTrue(self.vty.verify('delete-gbproxy-peer 9999 bvci 7777', ['BVC not found'])) + self.assertTrue(self.vty.verify('delete-gbproxy-peer 9999 bvci 7777', ['NSE not found'])) res = self.vty.command("delete-gbproxy-peer 9999 all dry-run") self.assertTrue(res.find('Not Deleted 0 BVC') >= 0) self.assertTrue(res.find('NSEI not found') >= 0) -- cgit v1.2.3