aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Willmann <dwillmann@sysmocom.de>2020-12-21 18:53:55 +0100
committerDaniel Willmann <dwillmann@sysmocom.de>2020-12-28 19:27:08 +0100
commitb0ff672359217f3658c8d1d68162aa2b88fd81c3 (patch)
tree3cd6ad5926d5217ec494a12bdcc575a9b653f524
parentfb07da8e4283396aba2137d818f92fed687bba18 (diff)
gbproxy: Add SGSN pooling support
-rw-r--r--include/osmocom/sgsn/gb_proxy.h6
-rw-r--r--src/gbproxy/gb_proxy.c149
-rw-r--r--src/gbproxy/gb_proxy_main.c2
-rw-r--r--src/gbproxy/gb_proxy_peer.c67
4 files changed, 195 insertions, 29 deletions
diff --git a/include/osmocom/sgsn/gb_proxy.h b/include/osmocom/sgsn/gb_proxy.h
index 46decc0e..dd529a31 100644
--- a/include/osmocom/sgsn/gb_proxy.h
+++ b/include/osmocom/sgsn/gb_proxy.h
@@ -60,6 +60,10 @@ struct gbproxy_config {
/* NRI bitlen and usable NULL-NRI ranges */
uint8_t nri_bitlen;
struct osmo_nri_ranges *null_nri_ranges;
+
+ /* Used for testing: If not NULL then this SGSN is returned by
+ * gbproxy_sgsn_by_tlli() */
+ struct gbproxy_sgsn *nsf_override;
} pool;
/* hash table of all BSS side Gb peers */
@@ -229,5 +233,7 @@ void gbproxy_sgsn_free(struct gbproxy_sgsn *sgsn);
struct gbproxy_sgsn *gbproxy_sgsn_by_nsei(struct gbproxy_config *cfg, uint16_t nsei);
struct gbproxy_sgsn *gbproxy_sgsn_by_nsei_or_new(struct gbproxy_config *cfg, uint16_t nsei);
struct gbproxy_sgsn *gbproxy_sgsn_by_nri(struct gbproxy_config *cfg, uint16_t nri, bool *null_nri);
+struct gbproxy_sgsn *gbproxy_sgsn_by_tlli(struct gbproxy_config *cfg, struct gbproxy_sgsn *sgsn_avoid,
+ uint32_t tlli);
#endif
diff --git a/src/gbproxy/gb_proxy.c b/src/gbproxy/gb_proxy.c
index 4b6dc091..bce40559 100644
--- a/src/gbproxy/gb_proxy.c
+++ b/src/gbproxy/gb_proxy.c
@@ -20,6 +20,7 @@
*
*/
+#include "osmocom/vty/command.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
@@ -38,6 +39,7 @@
#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stats.h>
+#include <osmocom/core/utils.h>
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/gprs/gprs_bssgp.h>
@@ -45,6 +47,7 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/bssgp_bvc_fsm.h>
+#include <osmocom/gsm/gsm23236.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/sgsn/signal.h>
@@ -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 e85e9515..c660edef 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 863ec501..3b120467 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;
+}