aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacob Erlbeck <jerlbeck@sysmocom.de>2015-02-02 18:03:05 +0100
committerJacob Erlbeck <jerlbeck@sysmocom.de>2015-02-04 11:37:35 +0100
commita648d1436d9b5b6f292a2db508a363ceda931a18 (patch)
tree2be21704cbcc430bd324c5c9ae807974a58a610c
parent5a5a291b5ebd3831d7ae716bd31e89b9363a4cc4 (diff)
sgsn: Select GGSN based on APN
Currently the APN IE in the Activate PDP Contex Request and the PDP data that is stored with the subscriber is ignored completely. This commit adds the sgsn_mm_ctx_find_ggsn_ctx that checks the APN IE against the subscriber's PDP data entries if both are present. If there is no match, the request is rejected. If an APN IE has not been included but PDP data entries are present, the function checks all of these entries against the static 'apn' configuration to find a suitable entry. If an APN has not been determined so far and any APN is allowed, the configuration is checked with an empty APN string, to allow for default configurations based on the IMSI prefix only. If nothing of this succeeded but the request wasn't rejected either, and there is no 'apn' configuration at all or if any APN is allowed but a default configuration ist not present, the GGSN with id 0 is used (if present). Otherwise the request is rejected ('missing APN'). Ticket: OW#1334 Sponsored-by: On-Waves ehf
-rw-r--r--openbsc/include/openbsc/gprs_sgsn.h5
-rw-r--r--openbsc/src/gprs/gprs_gmm.c13
-rw-r--r--openbsc/src/gprs/gprs_sgsn.c110
-rw-r--r--openbsc/tests/sgsn/sgsn_test.c135
-rw-r--r--openbsc/tests/sgsn/sgsn_test.ok1
5 files changed, 258 insertions, 6 deletions
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index 330cb9320..20d535205 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -21,6 +21,8 @@ struct gprs_llc_lle;
struct ctrl_handle;
struct gsm_subscriber;
+enum gsm48_gsm_cause;
+
/* TS 04.08 4.1.3.3 GMM mobility management states on the network side */
enum gprs_mm_state {
GMM_DEREGISTERED, /* 4.1.3.3.1.1 */
@@ -153,6 +155,9 @@ struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm);
void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx);
+struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx,
+ struct tlv_parsed *tp,
+ enum gsm48_gsm_cause *gsm_cause);
enum pdp_ctx_state {
PDP_STATE_NONE,
diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c
index abda327f0..ace0c2ec0 100644
--- a/openbsc/src/gprs/gprs_gmm.c
+++ b/openbsc/src/gprs/gprs_gmm.c
@@ -1541,6 +1541,7 @@ static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
uint8_t transaction_id = (gh->proto_discr >> 4);
struct sgsn_ggsn_ctx *ggsn;
struct sgsn_pdp_ctx *pdp;
+ enum gsm48_gsm_cause gsm_cause;
LOGMMCTXP(LOGL_INFO, mmctx, "-> ACTIVATE PDP CONTEXT REQ: SAPI=%u NSAPI=%u ",
act_req->req_llc_sapi, act_req->req_nsapi);
@@ -1599,9 +1600,6 @@ static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].len = req_pdpa_len;
tp.lv[OSMO_IE_GSM_REQ_PDP_ADDR].val = req_pdpa;
- /* FIXME: determine GGSN based on APN and subscription options */
- if (TLVP_PRESENT(&tp, GSM48_IE_GSM_APN)) {}
-
/* Check if NSAPI is out of range (TS 04.65 / 7.2) */
if (act_req->req_nsapi < 5 || act_req->req_nsapi > 15) {
/* Send reject with GSM_CAUSE_INV_MAND_INFO */
@@ -1631,11 +1629,14 @@ static int gsm48_rx_gsm_act_pdp_req(struct sgsn_mm_ctx *mmctx,
* for re-transmissions */
rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PDP_CTX_ACT]);
- ggsn = sgsn_ggsn_ctx_by_id(0);
+ /* Determine GGSN based on APN and subscription options */
+ ggsn = sgsn_mm_ctx_find_ggsn_ctx(mmctx, &tp, &gsm_cause);
if (!ggsn) {
- LOGP(DGPRS, LOGL_ERROR, "No GGSN context 0 found!\n");
- return -EIO;
+ LOGP(DGPRS, LOGL_ERROR, "No GGSN context found!\n");
+ return gsm48_tx_gsm_act_pdp_rej(mmctx, transaction_id,
+ gsm_cause, 0, NULL);
}
+ LOGMMCTXP(LOGL_DEBUG, mmctx, "Using GGSN %d\n", ggsn->id);
ggsn->gsn = sgsn->gsn;
pdp = sgsn_create_pdp_ctx(ggsn, mmctx, act_req->req_nsapi, &tp);
if (!pdp)
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
index 85f3381c3..94c2b6fbe 100644
--- a/openbsc/src/gprs/gprs_sgsn.c
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -35,6 +35,7 @@
#include <openbsc/sgsn.h>
#include <openbsc/gsm_04_08_gprs.h>
#include <openbsc/gprs_gmm.h>
+#include <openbsc/gprs_utils.h>
#include "openbsc/gprs_llc.h"
#include <time.h>
@@ -581,6 +582,115 @@ void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx)
sgsn_auth_update(mmctx);
}
+struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx,
+ struct tlv_parsed *tp,
+ enum gsm48_gsm_cause *gsm_cause)
+{
+ char req_apn_str[GSM_APN_LENGTH] = {0};
+ const struct apn_ctx *apn_ctx = NULL;
+ const char *selected_apn_str = NULL;
+ struct sgsn_subscriber_pdp_data *pdp;
+ struct sgsn_ggsn_ctx *ggsn = NULL;
+ int allow_any_apn = 0;
+
+ if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) {
+ if (TLVP_LEN(tp, GSM48_IE_GSM_APN) >= GSM_APN_LENGTH - 1) {
+ LOGMMCTXP(LOGL_ERROR, mmctx, "APN IE too long\n");
+ *gsm_cause = GSM_CAUSE_INV_MAND_INFO;
+ return NULL;
+ }
+
+ gprs_apn_to_str(req_apn_str,
+ TLVP_VAL(tp, GSM48_IE_GSM_APN),
+ TLVP_LEN(tp, GSM48_IE_GSM_APN));
+
+ if (strcmp(req_apn_str, "*") == 0)
+ req_apn_str[0] = 0;
+ }
+
+ if (mmctx->subscr == NULL ||
+ llist_empty(&mmctx->subscr->sgsn_data->pdp_list))
+ allow_any_apn = 1;
+
+ if (strlen(req_apn_str) == 0 && !allow_any_apn) {
+ /* No specific APN requested, check for an APN that is both
+ * granted and configured */
+
+ llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) {
+ if (strcmp(pdp->apn_str, "*") == 0)
+ {
+ allow_any_apn = 1;
+ selected_apn_str = "";
+ continue;
+ }
+ if (!llist_empty(&sgsn_apn_ctxts)) {
+ apn_ctx = sgsn_apn_ctx_match(req_apn_str, mmctx->imsi);
+ /* Not configured */
+ if (apn_ctx == NULL)
+ continue;
+ }
+ selected_apn_str = pdp->apn_str;
+ break;
+ }
+ } else if (!allow_any_apn) {
+ /* Check whether the given APN is granted */
+ llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) {
+ if (strcmp(pdp->apn_str, "*") == 0) {
+ selected_apn_str = req_apn_str;
+ allow_any_apn = 1;
+ continue;
+ }
+ if (strcasecmp(pdp->apn_str, req_apn_str) == 0) {
+ selected_apn_str = req_apn_str;
+ break;
+ }
+ }
+ } else if (strlen(req_apn_str) != 0) {
+ /* Any APN is allowed */
+ selected_apn_str = req_apn_str;
+ } else {
+ /* Prefer the GGSN associated with the wildcard APN */
+ selected_apn_str = "";
+ }
+
+ if (!allow_any_apn && selected_apn_str == NULL) {
+ /* Access not granted */
+ LOGMMCTXP(LOGL_NOTICE, mmctx,
+ "The requested APN '%s' is not allowed\n",
+ req_apn_str);
+ *gsm_cause = GSM_CAUSE_REQ_SERV_OPT_NOTSUB;
+ return NULL;
+ }
+
+ if (apn_ctx == NULL && selected_apn_str)
+ apn_ctx = sgsn_apn_ctx_match(selected_apn_str, mmctx->imsi);
+
+ if (apn_ctx != NULL) {
+ ggsn = apn_ctx->ggsn;
+ } else if (llist_empty(&sgsn_apn_ctxts)) {
+ /* No configuration -> use GGSN 0 */
+ ggsn = sgsn_ggsn_ctx_by_id(0);
+ } else if (allow_any_apn &&
+ (selected_apn_str == NULL || strlen(selected_apn_str) == 0)) {
+ /* No APN given and no default configuration -> Use GGSN 0 */
+ ggsn = sgsn_ggsn_ctx_by_id(0);
+ } else {
+ /* No matching configuration found */
+ LOGMMCTXP(LOGL_NOTICE, mmctx,
+ "The selected APN '%s' has not been configured\n",
+ selected_apn_str);
+ *gsm_cause = GSM_CAUSE_MISSING_APN;
+ return NULL;
+ }
+
+ LOGMMCTXP(LOGL_INFO, mmctx,
+ "Found GGSN %d for APN '%s' (requested '%s')\n",
+ ggsn->id, selected_apn_str ? selected_apn_str : "---",
+ req_apn_str);
+
+ return ggsn;
+}
+
static void sgsn_llme_cleanup_free(struct gprs_llc_llme *llme)
{
struct sgsn_mm_ctx *mmctx = NULL;
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 5fa33a266..d55a0fc6a 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -26,6 +26,7 @@
#include <openbsc/gsm_subscriber.h>
#include <openbsc/gprs_gsup_messages.h>
#include <openbsc/gprs_gsup_client.h>
+#include <openbsc/gprs_utils.h>
#include <osmocom/gprs/gprs_bssgp.h>
@@ -1876,6 +1877,139 @@ static void test_apn_matching(void)
OSMO_ASSERT(actx == NULL);
}
+struct sgsn_subscriber_pdp_data* sgsn_subscriber_pdp_data_alloc(
+ struct sgsn_subscriber_data *sdata);
+
+static void test_ggsn_selection(void)
+{
+ struct apn_ctx *actxs[4];
+ struct sgsn_ggsn_ctx *ggc, *ggcs[3];
+ struct gsm_subscriber *s1;
+ const char *imsi1 = "1234567890";
+ struct sgsn_mm_ctx *ctx;
+ struct gprs_ra_id raid = { 0, };
+ uint32_t local_tlli = 0xffeeddcc;
+ enum gsm48_gsm_cause gsm_cause;
+ struct tlv_parsed tp;
+ uint8_t apn_enc[GSM_APN_LENGTH + 10];
+ struct sgsn_subscriber_pdp_data *pdp_data;
+
+ printf("Testing GGSN selection\n");
+
+ gprs_gsup_client_send_cb = my_gprs_gsup_client_send_dummy;
+
+ /* Check for emptiness */
+ OSMO_ASSERT(gprs_subscr_get_by_imsi(imsi1) == NULL);
+
+ /* Create a context */
+ OSMO_ASSERT(count(gprs_llme_list()) == 0);
+ ctx = alloc_mm_ctx(local_tlli, &raid);
+ strncpy(ctx->imsi, imsi1, sizeof(ctx->imsi) - 1);
+
+ /* Allocate and attach a subscriber */
+ s1 = gprs_subscr_get_or_create_by_mmctx(ctx);
+ assert_subscr(s1, imsi1);
+
+ tp.lv[GSM48_IE_GSM_APN].len = 0;
+ tp.lv[GSM48_IE_GSM_APN].val = apn_enc;
+
+ /* TODO: Add PDP info entries to s1 */
+
+ ggcs[0] = sgsn_ggsn_ctx_find_alloc(0);
+ ggcs[1] = sgsn_ggsn_ctx_find_alloc(1);
+ ggcs[2] = sgsn_ggsn_ctx_find_alloc(2);
+
+ actxs[0] = sgsn_apn_ctx_find_alloc("test.apn", "123456");
+ actxs[0]->ggsn = ggcs[0];
+ actxs[1] = sgsn_apn_ctx_find_alloc("*.apn", "123456");
+ actxs[1]->ggsn = ggcs[1];
+ actxs[2] = sgsn_apn_ctx_find_alloc("*", "456789");
+ actxs[2]->ggsn = ggcs[2];
+
+ /* Resolve GGSNs */
+
+ tp.lv[GSM48_IE_GSM_APN].len =
+ gprs_str_to_apn(apn_enc, sizeof(apn_enc), "Test.Apn");
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc != NULL);
+ OSMO_ASSERT(ggc->id == 0);
+
+ tp.lv[GSM48_IE_GSM_APN].len =
+ gprs_str_to_apn(apn_enc, sizeof(apn_enc), "Other.Apn");
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc != NULL);
+ OSMO_ASSERT(ggc->id == 1);
+
+ tp.lv[GSM48_IE_GSM_APN].len = 0;
+ tp.lv[GSM48_IE_GSM_APN].val = NULL;
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc != NULL);
+ OSMO_ASSERT(ggc->id == 0);
+
+ actxs[3] = sgsn_apn_ctx_find_alloc("*", "123456");
+ actxs[3]->ggsn = ggcs[2];
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc != NULL);
+ OSMO_ASSERT(ggc->id == 2);
+
+ sgsn_apn_ctx_free(actxs[3]);
+ tp.lv[GSM48_IE_GSM_APN].val = apn_enc;
+
+ tp.lv[GSM48_IE_GSM_APN].len =
+ gprs_str_to_apn(apn_enc, sizeof(apn_enc), "Foo.Bar");
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc == NULL);
+ OSMO_ASSERT(gsm_cause == GSM_CAUSE_MISSING_APN);
+
+ tp.lv[GSM48_IE_GSM_APN].len = sizeof(apn_enc);
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc == NULL);
+ OSMO_ASSERT(gsm_cause == GSM_CAUSE_INV_MAND_INFO);
+
+ /* Add PDP data entry to subscriber */
+
+ pdp_data = sgsn_subscriber_pdp_data_alloc(s1->sgsn_data);
+ pdp_data->context_id = 1;
+
+ pdp_data->pdp_type = 0x0121;
+ strncpy(pdp_data->apn_str, "Test.Apn", sizeof(pdp_data->apn_str)-1);
+
+ tp.lv[GSM48_IE_GSM_APN].len =
+ gprs_str_to_apn(apn_enc, sizeof(apn_enc), "Test.Apn");
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc != NULL);
+ OSMO_ASSERT(ggc->id == 0);
+
+ tp.lv[GSM48_IE_GSM_APN].len =
+ gprs_str_to_apn(apn_enc, sizeof(apn_enc), "Other.Apn");
+
+ ggc = sgsn_mm_ctx_find_ggsn_ctx(ctx, &tp, &gsm_cause);
+ OSMO_ASSERT(ggc == NULL);
+ OSMO_ASSERT(gsm_cause == GSM_CAUSE_REQ_SERV_OPT_NOTSUB);
+
+ /* Cleanup */
+
+ subscr_put(s1);
+ sgsn_mm_ctx_cleanup_free(ctx);
+
+ assert_no_subscrs();
+
+ sgsn_apn_ctx_free(actxs[0]);
+ sgsn_apn_ctx_free(actxs[1]);
+ sgsn_apn_ctx_free(actxs[2]);
+
+ sgsn_ggsn_ctx_free(ggcs[0]);
+ sgsn_ggsn_ctx_free(ggcs[1]);
+ sgsn_ggsn_ctx_free(ggcs[2]);
+
+ gprs_gsup_client_send_cb = __real_gprs_gsup_client_send;
+}
+
static struct log_info_cat gprs_categories[] = {
[DMM] = {
.name = "DMM",
@@ -1964,6 +2098,7 @@ int main(int argc, char **argv)
test_gmm_cancel();
test_gmm_ptmsi_allocation();
test_apn_matching();
+ test_ggsn_selection();
printf("Done\n");
talloc_report_full(osmo_sgsn_ctx, stderr);
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 9f14721af..bef8c9897 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -27,4 +27,5 @@ Testing P-TMSI allocation
- Repeated Attach Request
- Repeated RA Update Request
Testing APN matching
+Testing GGSN selection
Done