| /* 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 <osmocom/core/linuxlist.h> |
| #include <osmocom/core/talloc.h> |
| #include <osmocom/core/timer.h> |
| #include <osmocom/core/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; |
| } |