aboutsummaryrefslogtreecommitdiffstats
path: root/src/gprs/gprs_sgsn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gprs/gprs_sgsn.c')
-rw-r--r--src/gprs/gprs_sgsn.c383
1 files changed, 383 insertions, 0 deletions
diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c
new file mode 100644
index 00000000..44365541
--- /dev/null
+++ b/src/gprs/gprs_sgsn.c
@@ -0,0 +1,383 @@
+/* GPRS SGSN functionality */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+
+#include <osmocore/linuxlist.h>
+#include <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/rate_ctr.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/gprs_sgsn.h>
+#include <openbsc/gprs_ns.h>
+#include <openbsc/gprs_bssgp.h>
+#include <openbsc/sgsn.h>
+#include <openbsc/gsm_04_08_gprs.h>
+#include <openbsc/gprs_gmm.h>
+
+extern struct sgsn_instance *sgsn;
+
+LLIST_HEAD(sgsn_mm_ctxts);
+LLIST_HEAD(sgsn_ggsn_ctxts);
+LLIST_HEAD(sgsn_apn_ctxts);
+LLIST_HEAD(sgsn_pdp_ctxts);
+
+static const struct rate_ctr_desc mmctx_ctr_description[] = {
+ { "sign.packets.in", "Signalling Messages ( In)" },
+ { "sign.packets.out", "Signalling Messages (Out)" },
+ { "udata.packets.in", "User Data Messages ( In)" },
+ { "udata.packets.out", "User Data Messages (Out)" },
+ { "udata.bytes.in", "User Data Bytes ( In)" },
+ { "udata.bytes.out", "User Data Bytes (Out)" },
+ { "pdp_ctx_act", "PDP Context Activations " },
+ { "suspend", "SUSPEND Count " },
+ { "paging.ps", "Paging Packet Switched " },
+ { "paging.cs", "Paging Circuit Switched " },
+ { "ra_update", "Routing Area Update " },
+};
+
+static const struct rate_ctr_group_desc mmctx_ctrg_desc = {
+ .group_name_prefix = "sgsn.mmctx",
+ .group_description = "SGSN MM Context Statistics",
+ .num_ctr = ARRAY_SIZE(mmctx_ctr_description),
+ .ctr_desc = mmctx_ctr_description,
+};
+
+static const struct rate_ctr_desc pdpctx_ctr_description[] = {
+ { "udata.packets.in", "User Data Messages ( In)" },
+ { "udata.packets.out", "User Data Messages (Out)" },
+ { "udata.bytes.in", "User Data Bytes ( In)" },
+ { "udata.bytes.out", "User Data Bytes (Out)" },
+};
+
+static const struct rate_ctr_group_desc pdpctx_ctrg_desc = {
+ .group_name_prefix = "sgsn.pdpctx",
+ .group_description = "SGSN PDP Context Statistics",
+ .num_ctr = ARRAY_SIZE(pdpctx_ctr_description),
+ .ctr_desc = pdpctx_ctr_description,
+};
+
+static int ra_id_equals(const struct gprs_ra_id *id1,
+ const struct gprs_ra_id *id2)
+{
+ return (id1->mcc == id2->mcc && id1->mnc == id2->mnc &&
+ id1->lac == id2->lac && id1->rac == id2->rac);
+}
+
+/* See 03.02 Chapter 2.6 */
+static inline uint32_t tlli_foreign(uint32_t tlli)
+{
+ return ((tlli | 0x80000000) & ~0x40000000);
+}
+
+/* look-up a SGSN MM context based on TLLI + RAI */
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
+ const struct gprs_ra_id *raid)
+{
+ struct sgsn_mm_ctx *ctx;
+ int tlli_type;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (tlli == ctx->tlli &&
+ ra_id_equals(raid, &ctx->ra))
+ return ctx;
+ }
+
+ tlli_type = gprs_tlli_type(tlli);
+ switch (tlli_type) {
+ case TLLI_LOCAL:
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if ((ctx->p_tmsi | 0xC0000000) == tlli ||
+ (ctx->p_tmsi_old && (ctx->p_tmsi_old | 0xC0000000) == tlli)) {
+ ctx->tlli = tlli;
+ return ctx;
+ }
+ }
+ break;
+ case TLLI_FOREIGN:
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (tlli == tlli_foreign(ctx->tlli) &&
+ ra_id_equals(raid, &ctx->ra))
+ return ctx;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (p_tmsi == ctx->p_tmsi ||
+ (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi))
+ return ctx;
+ }
+ return NULL;
+}
+
+struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
+ if (!strcmp(imsi, ctx->imsi))
+ return ctx;
+ }
+ return NULL;
+
+}
+
+/* Allocate a new SGSN MM context */
+struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
+ const struct gprs_ra_id *raid)
+{
+ struct sgsn_mm_ctx *ctx;
+
+ ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
+ if (!ctx)
+ return NULL;
+
+ memcpy(&ctx->ra, raid, sizeof(ctx->ra));
+ ctx->tlli = tlli;
+ ctx->mm_state = GMM_DEREGISTERED;
+ ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli);
+ INIT_LLIST_HEAD(&ctx->pdp_list);
+
+ llist_add(&ctx->list, &sgsn_mm_ctxts);
+
+ return ctx;
+}
+
+void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm)
+{
+ struct sgsn_pdp_ctx *pdp, *pdp2;
+
+ /* Unlink from global list of MM contexts */
+ llist_del(&mm->list);
+
+ /* Free all PDP contexts */
+ llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list)
+ sgsn_pdp_ctx_free(pdp);
+
+ rate_ctr_group_free(mm->ctrg);
+
+ talloc_free(mm);
+}
+
+/* look up PDP context by MM context and NSAPI */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
+ uint8_t nsapi)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->nsapi == nsapi)
+ return pdp;
+ }
+ return NULL;
+}
+
+/* look up PDP context by MM context and transaction ID */
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
+ uint8_t tid)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->ti == tid)
+ return pdp;
+ }
+ return NULL;
+}
+
+struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
+ uint8_t nsapi)
+{
+ struct sgsn_pdp_ctx *pdp;
+
+ pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
+ if (pdp)
+ return NULL;
+
+ pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
+ if (!pdp)
+ return NULL;
+
+ pdp->mm = mm;
+ pdp->nsapi = nsapi;
+ pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi);
+ llist_add(&pdp->list, &mm->pdp_list);
+ llist_add(&pdp->g_list, &sgsn_pdp_ctxts);
+
+ return pdp;
+}
+
+void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp)
+{
+ rate_ctr_group_free(pdp->ctrg);
+ llist_del(&pdp->list);
+ llist_del(&pdp->g_list);
+ talloc_free(pdp);
+}
+
+/* GGSN contexts */
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ ggc = talloc_zero(tall_bsc_ctx, struct sgsn_ggsn_ctx);
+ if (!ggc)
+ return NULL;
+
+ ggc->id = id;
+ ggc->gtp_version = 1;
+ ggc->remote_restart_ctr = -1;
+ /* if we are called from config file parse, this gsn doesn't exist yet */
+ ggc->gsn = sgsn->gsn;
+ llist_add(&ggc->list, &sgsn_ggsn_ctxts);
+
+ return ggc;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+ if (id == ggc->id)
+ return ggc;
+ }
+ return NULL;
+}
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
+ if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr)))
+ return ggc;
+ }
+ return NULL;
+}
+
+
+struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id)
+{
+ struct sgsn_ggsn_ctx *ggc;
+
+ ggc = sgsn_ggsn_ctx_by_id(id);
+ if (!ggc)
+ ggc = sgsn_ggsn_ctx_alloc(id);
+ return ggc;
+}
+
+/* APN contexts */
+
+#if 0
+struct apn_ctx *apn_ctx_alloc(const char *ap_name)
+{
+ struct apn_ctx *actx;
+
+ actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx);
+ if (!actx)
+ return NULL;
+ actx->name = talloc_strdup(actx, ap_name);
+
+ return actx;
+}
+
+struct apn_ctx *apn_ctx_by_name(const char *name)
+{
+ struct apn_ctx *actx;
+
+ llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
+ if (!strcmp(name, actx->name))
+ return actx;
+ }
+ return NULL;
+}
+
+struct apn_ctx *apn_ctx_find_alloc(const char *name)
+{
+ struct apn_ctx *actx;
+
+ actx = apn_ctx_by_name(name);
+ if (!actx)
+ actx = apn_ctx_alloc(name);
+
+ return actx;
+}
+#endif
+
+uint32_t sgsn_alloc_ptmsi(void)
+{
+ struct sgsn_mm_ctx *mm;
+ uint32_t ptmsi;
+
+restart:
+ ptmsi = rand();
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+ if (mm->p_tmsi == ptmsi)
+ goto restart;
+ }
+
+ return ptmsi;
+}
+
+static void drop_one_pdp(struct sgsn_pdp_ctx *pdp)
+{
+ if (pdp->mm->mm_state == GMM_REGISTERED_NORMAL)
+ gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
+ else {
+ /* FIXME: GPRS paging in case MS is SUSPENDED */
+ LOGP(DGPRS, LOGL_NOTICE, "Hard-dropping PDP ctx due to GGSN "
+ "recovery\n");
+ sgsn_pdp_ctx_free(pdp);
+ }
+}
+
+/* High-level function to be called in case a GGSN has disappeared or
+ * ottherwise lost state (recovery procedure) */
+int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn)
+{
+ struct sgsn_mm_ctx *mm;
+ int num = 0;
+
+ llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
+ struct sgsn_pdp_ctx *pdp;
+ llist_for_each_entry(pdp, &mm->pdp_list, list) {
+ if (pdp->ggsn == ggsn) {
+ drop_one_pdp(pdp);
+ num++;
+ }
+ }
+ }
+
+ return num;
+}