diff options
-rw-r--r-- | openbsc/include/openbsc/gprs_sgsn.h | 5 | ||||
-rw-r--r-- | openbsc/src/gprs/gprs_gmm.c | 13 | ||||
-rw-r--r-- | openbsc/src/gprs/gprs_sgsn.c | 110 | ||||
-rw-r--r-- | openbsc/tests/sgsn/sgsn_test.c | 135 | ||||
-rw-r--r-- | openbsc/tests/sgsn/sgsn_test.ok | 1 |
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 |