From b0ff672359217f3658c8d1d68162aa2b88fd81c3 Mon Sep 17 00:00:00 2001 From: Daniel Willmann Date: Mon, 21 Dec 2020 18:53:55 +0100 Subject: gbproxy: Add SGSN pooling support Change-Id: I58b9f55065f6bd43450e4b07cffe7ba132b1fd9b Related: OS#4472 --- src/gbproxy/gb_proxy.c | 149 +++++++++++++++++++++++++++++++++++--------- src/gbproxy/gb_proxy_main.c | 2 + src/gbproxy/gb_proxy_peer.c | 67 +++++++++++++++++++- 3 files changed, 189 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c index 4b6dc091f..bce405596 100644 --- a/src/gbproxy/gb_proxy.c +++ b/src/gbproxy/gb_proxy.c @@ -20,6 +20,7 @@ * */ +#include "osmocom/vty/command.h" #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include @@ -45,6 +47,7 @@ #include #include +#include #include #include @@ -201,41 +204,130 @@ int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) * 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) +/* FIXME: Handle the tlli NULL case correctly, + * This function should take a generic selector + * and choose an sgsn based on that + */ +static struct gbproxy_sgsn *gbproxy_select_sgsn(struct gbproxy_config *cfg, const uint32_t *tlli) { - struct gbproxy_bvc *sgsn_bvc; - unsigned int i; + struct gbproxy_sgsn *sgsn = NULL; + struct gbproxy_sgsn *sgsn_avoid = NULL; - /* FIXME: derive NRI from TLLI */ - /* FIXME: find the SGSN for that NRI */ + int tlli_type; + int16_t nri; + bool null_nri = false; - /* 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); + if (!tlli) { + sgsn = llist_first_entry(&cfg->sgsns, struct gbproxy_sgsn, list); + if (!sgsn) { + return NULL; + } + LOGPSGSN(sgsn, LOGL_INFO, "Could not get TLLI, using first SGSN\n"); + return sgsn; } - return 0; + + if (cfg->pool.nri_bitlen == 0) { + /* Pooling is disabled */ + sgsn = llist_first_entry(&cfg->sgsns, struct gbproxy_sgsn, list); + if (!sgsn) { + return NULL; + } + + LOGPSGSN(sgsn, LOGL_INFO, "Pooling disabled, using first configured SGSN\n"); + } else { + /* Pooling is enabled, try to use the NRI for routing to an SGSN + * See 3GPP TS 23.236 Ch. 5.3.2 */ + tlli_type = gprs_tlli_type(*tlli); + if (tlli_type == TLLI_LOCAL || tlli_type == TLLI_FOREIGN) { + /* Only get/use the NRI if tlli type is local */ + osmo_tmsi_nri_v_get(&nri, *tlli, cfg->pool.nri_bitlen); + if (nri >= 0) { + /* Get the SGSN for the NRI */ + sgsn = gbproxy_sgsn_by_nri(cfg, nri, &null_nri); + if (sgsn && !null_nri) + return sgsn; + /* If the NRI is the null NRI, we need to avoid the chosen SGSN */ + if (null_nri && sgsn) { + sgsn_avoid = sgsn; + } + } else { + /* We couldn't get the NRI from the TLLI */ + LOGP(DGPRS, LOGL_ERROR, "Could not extract NRI from local TLLI %u\n", *tlli); + } + } + } + + /* If we haven't found an SGSN yet we need to choose one, but avoid the one in sgsn_avoid + * NOTE: This function is not stable if the number of SGSNs or allow_attach changes + * We could implement TLLI tracking here, but 3GPP TS 23.236 Ch. 5.3.2 (see NOTE) argues that + * we can just wait for the MS to reattempt the procedure. + */ + if (!sgsn) + sgsn = gbproxy_sgsn_by_tlli(cfg, sgsn_avoid, *tlli); + + if (!sgsn) { + LOGP(DGPRS, LOGL_ERROR, "No suitable SGSN found for TLLI %u\n", *tlli); + return NULL; + } + + return sgsn; } -static int gbprox_bss2sgsn_null_nri(struct gbproxy_cell *cell, struct msgb *msg) +/*! Find the correct gbproxy_bvc given a cell and an SGSN + * \param[in] cfg The gbproxy configuration + * \param[in] cell The cell the message belongs to + * \param[in] tlli An optional TLLI used for tracking + * \return Returns 0 on success, otherwise a negative value + */ +static struct gbproxy_bvc *gbproxy_select_sgsn_bvc(struct gbproxy_config *cfg, struct gbproxy_cell *cell, const uint32_t *tlli) { - struct gbproxy_bvc *sgsn_bvc; - unsigned int i; + struct gbproxy_sgsn *sgsn; + struct gbproxy_bvc *sgsn_bvc = NULL; - /* FIXME: find the SGSN for that NRI */ + sgsn = gbproxy_select_sgsn(cfg, tlli); + if (!sgsn) { + LOGPCELL(cell, LOGL_ERROR, "Could not find any SGSN, dropping message!\n"); + return NULL; + } - /* HACK: we currently simply pick the first SGSN we find */ - for (i = 0; i < ARRAY_SIZE(cell->sgsn_bvc); i++) { + /* Get the BVC for this SGSN/NSE */ + for (int 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); + if (!sgsn_bvc) + continue; + if (sgsn->nse != sgsn_bvc->nse) + continue; + + return sgsn_bvc; } - return 0; + + /* This shouldn't happen */ + LOGPCELL(cell, LOGL_ERROR, "Could not find matching BVC for SGSN, dropping message!\n"); + return NULL; } +/*! Send a message to the next SGSN, possibly ignoring the null SGSN + * route an uplink message on a PTP-BVC to a SGSN using the TLLI + * \param[in] cell The cell the message belongs to + * \param[in] msg The BSSGP message + * \param[in] null_sgsn If not NULL then avoid this SGSN (because this message contains its null NRI) + * \param[in] tlli An optional TLLI used for tracking + * \return Returns 0 on success, otherwise a negative value + */ +static int gbprox_bss2sgsn_tlli(struct gbproxy_cell *cell, struct msgb *msg, const uint32_t *tlli, + bool sig_bvci) +{ + struct gbproxy_config *cfg = cell->cfg; + struct gbproxy_bvc *sgsn_bvc; + + sgsn_bvc = gbproxy_select_sgsn_bvc(cfg, cell, tlli); + if (!sgsn_bvc) { + LOGPCELL(cell, LOGL_NOTICE, "Could not find any SGSN for TLLI %u, dropping message!\n", *tlli); + return -EINVAL; + } + + return gbprox_relay2peer(msg, sgsn_bvc, sig_bvci ? 0 : sgsn_bvc->bvci); +} /* Receive an incoming PTP message from a BSS-side NS-VC */ static int gbprox_rx_ptp_from_bss(struct gbproxy_nse *nse, struct msgb *msg, uint16_t ns_bvci) @@ -314,18 +406,20 @@ static int gbprox_rx_ptp_from_bss(struct gbproxy_nse *nse, struct msgb *msg, uin 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); + 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); + 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); + 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); + // FIXME: Use the IMSI as selector? + rc = gbprox_bss2sgsn_tlli(bss_bvc->cell, msg, NULL, false); + //rc = gbprox_bss2sgsn_hashed(bss_bvc->cell, msg, NULL); } else LOGPBVC(bss_bvc, LOGL_ERROR, "Rx RADIO-STATUS without any of the conditional IEs\n"); break; @@ -828,7 +922,7 @@ static int gbprox_rx_sig_from_bss(struct gbproxy_nse *nse, struct msgb *msg, uin 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); + gbprox_bss2sgsn_tlli(from_bvc->cell, msg, &tlli, true); break; default: LOGPNSE(nse, LOGL_ERROR, "Rx %s: Implementation missing\n", pdut_name); @@ -1288,7 +1382,6 @@ int gbproxy_init_config(struct gbproxy_config *cfg) /* 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); diff --git a/src/gbproxy/gb_proxy_main.c b/src/gbproxy/gb_proxy_main.c index e85e9515b..c660edef8 100644 --- a/src/gbproxy/gb_proxy_main.c +++ b/src/gbproxy/gb_proxy_main.c @@ -303,6 +303,8 @@ int main(int argc, char **argv) gprs_ns2_vty_create(); + /* TODO: Warn if we create a gbproxy_nse for an NSEI which we don't have a bind */ + /* start telnet after reading config for vty_get_bind_addr() */ rc = telnet_init_dynif(tall_sgsn_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_GBPROXY); diff --git a/src/gbproxy/gb_proxy_peer.c b/src/gbproxy/gb_proxy_peer.c index 863ec5017..3b120467f 100644 --- a/src/gbproxy/gb_proxy_peer.c +++ b/src/gbproxy/gb_proxy_peer.c @@ -343,7 +343,15 @@ struct gbproxy_nse *gbproxy_nse_by_nsei_or_new(struct gbproxy_config *cfg, uint1 return nse; } -/* SGSN */ +/*********************************************************************** + * SGSN - Serving GPRS Support Node + ***********************************************************************/ + +/*! Allocate a new SGSN. This ensures the corresponding gbproxy_nse is allocated as well + * \param[in] cfg The gbproxy configuration + * \param[in] nsei The nsei where the SGSN can be reached + * \return The SGSN, NULL if it couldn't be allocated + */ struct gbproxy_sgsn *gbproxy_sgsn_alloc(struct gbproxy_config *cfg, uint16_t nsei) { struct gbproxy_sgsn *sgsn; @@ -381,6 +389,9 @@ static void _sgsn_free(struct gbproxy_sgsn *sgsn) { talloc_free(sgsn); } +/*! Free the SGSN. This ensures the corresponding gbproxy_nse is freed as well + * \param[in] sgsn The SGSN + */ void gbproxy_sgsn_free(struct gbproxy_sgsn *sgsn) { if (!sgsn) @@ -392,6 +403,11 @@ void gbproxy_sgsn_free(struct gbproxy_sgsn *sgsn) _sgsn_free(sgsn); } +/*! Return the SGSN for a given NSEI + * \param[in] cfg The gbproxy configuration + * \param[in] nsei The nsei where the SGSN can be reached + * \return Returns the matching SGSN or NULL if it couldn't be found + */ struct gbproxy_sgsn *gbproxy_sgsn_by_nsei(struct gbproxy_config *cfg, uint16_t nsei) { struct gbproxy_sgsn *sgsn; @@ -405,6 +421,11 @@ struct gbproxy_sgsn *gbproxy_sgsn_by_nsei(struct gbproxy_config *cfg, uint16_t n return NULL; } +/*! Return the SGSN for a given NSEI, creating a new one if none exists + * \param[in] cfg The gbproxy configuration + * \param[in] nsei The nsei where the SGSN can be reached + * \return Returns the SGSN + */ struct gbproxy_sgsn *gbproxy_sgsn_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei) { struct gbproxy_sgsn *sgsn; @@ -443,3 +464,47 @@ struct gbproxy_sgsn *gbproxy_sgsn_by_nri(struct gbproxy_config *cfg, uint16_t nr return NULL; } + +/*! Seleect a pseudo-random SGSN for a given TLLI, ignoring any SGSN that is not accepting connections + * \param[in] cfg The gbproxy configuration + * \param[in] sgsn_avoid If not NULL then avoid this SGSN when selecting a new one. Use for load redistribution + * \param[in] tlli The tlli to choose an SGSN for. The same tlli will map to the same SGSN as long as no SGSN is + added/removed or allow_attach changes. + * \return Returns the sgsn on success, NULL if no SGSN that allows new connections could be found + */ +struct gbproxy_sgsn *gbproxy_sgsn_by_tlli(struct gbproxy_config *cfg, struct gbproxy_sgsn *sgsn_avoid, + uint32_t tlli) +{ + uint32_t i = 0; + uint32_t index, num_sgsns; + struct gbproxy_sgsn *sgsn; + OSMO_ASSERT(cfg); + + // TODO: We should keep track of count in cfg + num_sgsns = llist_count(&cfg->sgsns); + + if (num_sgsns == 0) + return NULL; + + // FIXME: 256 SGSNs ought to be enough for everyone + index = hash_32(tlli, 8) % num_sgsns; + + // Get the first enabled SGSN after index + llist_for_each_entry(sgsn, &cfg->sgsns, list) { + if (i >= index && sgsn->pool.allow_attach) { + return sgsn; + } + i++; + } + // Start again from the beginning + llist_for_each_entry(sgsn, &cfg->sgsns, list) { + if (i > index) { + break; + } else if (sgsn->pool.allow_attach) { + return sgsn; + } + i++; + } + + return NULL; +} -- cgit v1.2.3