blob: 19b0a1b7ce2619f5ec0c1eb6d2b3aed2c8972783 [file] [log] [blame]
Harald Welte9b455bf2010-03-14 15:45:01 +08001/* GPRS SGSN functionality */
2
3/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
4 *
5 * All Rights Reserved
6 *
7 * This program is free software; you can redistribute it and/or modify
Harald Welte9af6ddf2011-01-01 15:25:50 +01008 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
Harald Welte9b455bf2010-03-14 15:45:01 +080010 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte9af6ddf2011-01-01 15:25:50 +010015 * GNU Affero General Public License for more details.
Harald Welte9b455bf2010-03-14 15:45:01 +080016 *
Harald Welte9af6ddf2011-01-01 15:25:50 +010017 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Harald Welte9b455bf2010-03-14 15:45:01 +080019 *
20 */
21
Harald Welteeaa614c2010-05-02 11:26:34 +020022#include <stdint.h>
Harald Welte9b455bf2010-03-14 15:45:01 +080023
Pablo Neira Ayuso136f4532011-03-22 16:47:59 +010024#include <osmocom/core/linuxlist.h>
25#include <osmocom/core/talloc.h>
26#include <osmocom/core/timer.h>
27#include <osmocom/core/rate_ctr.h>
Jacob Erlbeck46caed82015-11-02 15:15:38 +010028#include <osmocom/core/stats.h>
Harald Weltefdf453c2012-07-14 12:15:19 +020029#include <osmocom/core/backtrace.h>
Harald Welteea34a4e2012-06-16 14:59:56 +080030#include <osmocom/gprs/gprs_ns.h>
31#include <osmocom/gprs/gprs_bssgp.h>
Harald Welte53373bc2016-04-20 17:11:43 +020032#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
Harald Welteea34a4e2012-06-16 14:59:56 +080033
Harald Welte9b455bf2010-03-14 15:45:01 +080034#include <openbsc/gsm_subscriber.h>
Harald Weltecb991632010-04-26 19:18:54 +020035#include <openbsc/debug.h>
Harald Welte9b455bf2010-03-14 15:45:01 +080036#include <openbsc/gprs_sgsn.h>
Harald Welteab1d5622010-05-18 19:58:38 +020037#include <openbsc/sgsn.h>
Harald Weltea9b473a2010-12-24 21:13:26 +010038#include <openbsc/gprs_gmm.h>
Jacob Erlbeck277b71e2015-02-02 18:03:05 +010039#include <openbsc/gprs_utils.h>
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +020040#include <openbsc/signal.h>
Jacob Erlbeck99985b52014-10-13 10:32:00 +020041#include "openbsc/gprs_llc.h"
Daniel Willmann6292c8d2016-05-21 17:35:57 +020042#include <openbsc/iu.h>
Harald Welteab1d5622010-05-18 19:58:38 +020043
Neels Hofmeyrf4daf162016-05-21 00:44:50 +020044#include <pdp.h>
45
Jacob Erlbeck81ffb742015-01-23 11:33:51 +010046#include <time.h>
47
Daniel Willmann044ce5f2015-10-12 19:36:33 +020048#include <openssl/rand.h>
49
Jacob Erlbeck81ffb742015-01-23 11:33:51 +010050#define GPRS_LLME_CHECK_TICK 30
51
Harald Welteab1d5622010-05-18 19:58:38 +020052extern struct sgsn_instance *sgsn;
Harald Welte9b455bf2010-03-14 15:45:01 +080053
Harald Welted193cb32010-05-17 22:58:03 +020054LLIST_HEAD(sgsn_mm_ctxts);
55LLIST_HEAD(sgsn_ggsn_ctxts);
56LLIST_HEAD(sgsn_apn_ctxts);
57LLIST_HEAD(sgsn_pdp_ctxts);
Harald Welte9b455bf2010-03-14 15:45:01 +080058
Harald Welte8acd88f2010-05-18 10:57:45 +020059static const struct rate_ctr_desc mmctx_ctr_description[] = {
60 { "sign.packets.in", "Signalling Messages ( In)" },
61 { "sign.packets.out", "Signalling Messages (Out)" },
62 { "udata.packets.in", "User Data Messages ( In)" },
63 { "udata.packets.out", "User Data Messages (Out)" },
64 { "udata.bytes.in", "User Data Bytes ( In)" },
65 { "udata.bytes.out", "User Data Bytes (Out)" },
66 { "pdp_ctx_act", "PDP Context Activations " },
67 { "suspend", "SUSPEND Count " },
68 { "paging.ps", "Paging Packet Switched " },
69 { "paging.cs", "Paging Circuit Switched " },
70 { "ra_update", "Routing Area Update " },
71};
72
73static const struct rate_ctr_group_desc mmctx_ctrg_desc = {
74 .group_name_prefix = "sgsn.mmctx",
75 .group_description = "SGSN MM Context Statistics",
76 .num_ctr = ARRAY_SIZE(mmctx_ctr_description),
77 .ctr_desc = mmctx_ctr_description,
Jacob Erlbeck46caed82015-11-02 15:15:38 +010078 .class_id = OSMO_STATS_CLASS_SUBSCRIBER,
Harald Welte8acd88f2010-05-18 10:57:45 +020079};
80
Harald Welteefbdee92010-06-10 00:20:12 +020081static const struct rate_ctr_desc pdpctx_ctr_description[] = {
82 { "udata.packets.in", "User Data Messages ( In)" },
83 { "udata.packets.out", "User Data Messages (Out)" },
84 { "udata.bytes.in", "User Data Bytes ( In)" },
85 { "udata.bytes.out", "User Data Bytes (Out)" },
86};
87
88static const struct rate_ctr_group_desc pdpctx_ctrg_desc = {
89 .group_name_prefix = "sgsn.pdpctx",
90 .group_description = "SGSN PDP Context Statistics",
91 .num_ctr = ARRAY_SIZE(pdpctx_ctr_description),
92 .ctr_desc = pdpctx_ctr_description,
Jacob Erlbeck46caed82015-11-02 15:15:38 +010093 .class_id = OSMO_STATS_CLASS_SUBSCRIBER,
Harald Welteefbdee92010-06-10 00:20:12 +020094};
95
Alexander Couzens14314bd2016-07-05 09:52:52 +020096static const struct rate_ctr_desc sgsn_ctr_description[] = {
Alexander Couzens4e699a92016-07-05 11:04:27 +020097 { "llc.dl_bytes", "Count sent LLC bytes before giving it to the bssgp layer" },
98 { "llc.ul_bytes", "Count sucessful received LLC bytes (encrypt & fcs correct)" },
99 { "llc.dl_packets", "Count sucessful sent LLC packets before giving it to the bssgp layer" },
100 { "llc.ul_packets", "Count sucessful received LLC packets (encrypt & fcs correct)" },
Alexander Couzens14314bd2016-07-05 09:52:52 +0200101 { "gprs.attach_requested", "Received attach requests" },
102 { "gprs.attach_accepted", "Sent attach accepts" },
103 { "gprs.attach_rejected", "Sent attach rejects" },
104 { "gprs.detach_requested", "Received detach requests" },
105 { "gprs.detach_acked", "Sent detach acks" },
106 { "gprs.routing_area_requested", "Received routing area requests" },
107 { "gprs.routing_area_requested", "Sent routing area acks" },
108 { "gprs.routing_area_requested", "Sent routing area rejects" },
109 { "pdp.activate_requested", "Received activate requests" },
110 { "pdp.activate_rejected", "Sent activate rejects" },
111 { "pdp.activate_accepted", "Sent activate accepts" },
112 { "pdp.request_activated", "unused" },
113 { "pdp.request_activate_rejected", "unused" },
114 { "pdp.modify_requested", "unused" },
115 { "pdp.modify_accepted", "unused" },
116 { "pdp.dl_deactivate_requested", "Sent deactivate requests" },
117 { "pdp.dl_deactivate_accepted", "Sent deactivate accepted" },
118 { "pdp.ul_deactivate_requested", "Received deactivate requests" },
119 { "pdp.ul_deactivate_accepted", "Received deactivate accepts" },
120};
121
122static const struct rate_ctr_group_desc sgsn_ctrg_desc = {
123 "sgsn",
124 "SGSN Overall Statistics",
125 OSMO_STATS_CLASS_GLOBAL,
126 ARRAY_SIZE(sgsn_ctr_description),
127 sgsn_ctr_description,
128};
129
130void sgsn_rate_ctr_init() {
131 sgsn->rate_ctrs = rate_ctr_group_alloc(tall_bsc_ctx, &sgsn_ctrg_desc, 0);
132}
133
Daniel Willmann6292c8d2016-05-21 17:35:57 +0200134/* look-up an SGSN MM context based on Iu UE context (struct ue_conn_ctx)*/
135struct sgsn_mm_ctx *sgsn_mm_ctx_by_ue_ctx(const void *uectx)
136{
137 struct sgsn_mm_ctx *ctx;
138
139 llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
140 if (ctx->ran_type == MM_CTX_T_UTRAN_Iu
141 && uectx == ctx->iu.ue_ctx)
142 return ctx;
143 }
144
145 return NULL;
146}
147
Harald Welte9b455bf2010-03-14 15:45:01 +0800148/* look-up a SGSN MM context based on TLLI + RAI */
Harald Welteeaa614c2010-05-02 11:26:34 +0200149struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli(uint32_t tlli,
Harald Welte9b455bf2010-03-14 15:45:01 +0800150 const struct gprs_ra_id *raid)
151{
152 struct sgsn_mm_ctx *ctx;
153
154 llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
Harald Weltef97ee042015-12-25 19:12:21 +0100155 if ((tlli == ctx->gb.tlli || tlli == ctx->gb.tlli_new) &&
Jacob Erlbecke7bcdc32016-01-04 18:43:34 +0100156 gprs_ra_id_equals(raid, &ctx->ra))
Harald Welte9b455bf2010-03-14 15:45:01 +0800157 return ctx;
158 }
Harald Welteab1d5622010-05-18 19:58:38 +0200159
Harald Welte9b455bf2010-03-14 15:45:01 +0800160 return NULL;
161}
162
Jacob Erlbeck5ac4aad2016-01-04 18:43:38 +0100163struct sgsn_mm_ctx *sgsn_mm_ctx_by_tlli_and_ptmsi(uint32_t tlli,
164 const struct gprs_ra_id *raid)
165{
166 struct sgsn_mm_ctx *ctx;
167 int tlli_type;
168
169 /* TODO: Also check the P_TMSI signature to be safe. That signature
170 * should be different (at least with a sufficiently high probability)
171 * after SGSN restarts and for multiple SGSN instances.
172 */
173
174 tlli_type = gprs_tlli_type(tlli);
175 if (tlli_type != TLLI_FOREIGN && tlli_type != TLLI_LOCAL)
176 return NULL;
177
178 llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
179 if ((gprs_tmsi2tlli(ctx->p_tmsi, tlli_type) == tlli ||
180 gprs_tmsi2tlli(ctx->p_tmsi_old, tlli_type) == tlli) &&
181 gprs_ra_id_equals(raid, &ctx->ra))
182 return ctx;
183 }
184
185 return NULL;
186}
187
Harald Welteeaa614c2010-05-02 11:26:34 +0200188struct sgsn_mm_ctx *sgsn_mm_ctx_by_ptmsi(uint32_t p_tmsi)
Harald Welte9b455bf2010-03-14 15:45:01 +0800189{
190 struct sgsn_mm_ctx *ctx;
191
192 llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
Harald Weltec2e8cc42010-05-31 20:23:38 +0200193 if (p_tmsi == ctx->p_tmsi ||
194 (ctx->p_tmsi_old && ctx->p_tmsi_old == p_tmsi))
Harald Welte9b455bf2010-03-14 15:45:01 +0800195 return ctx;
196 }
197 return NULL;
198}
199
200struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi)
201{
202 struct sgsn_mm_ctx *ctx;
203
204 llist_for_each_entry(ctx, &sgsn_mm_ctxts, list) {
205 if (!strcmp(imsi, ctx->imsi))
206 return ctx;
207 }
208 return NULL;
209
210}
211
212/* Allocate a new SGSN MM context */
Harald Welteeaa614c2010-05-02 11:26:34 +0200213struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli,
Harald Welte9b455bf2010-03-14 15:45:01 +0800214 const struct gprs_ra_id *raid)
215{
Harald Welte2720e732010-05-17 00:44:57 +0200216 struct sgsn_mm_ctx *ctx;
Harald Welte9b455bf2010-03-14 15:45:01 +0800217
Harald Welte2720e732010-05-17 00:44:57 +0200218 ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
Harald Welte9b455bf2010-03-14 15:45:01 +0800219 if (!ctx)
220 return NULL;
221
222 memcpy(&ctx->ra, raid, sizeof(ctx->ra));
Harald Weltef97ee042015-12-25 19:12:21 +0100223 ctx->ran_type = MM_CTX_T_GERAN_Gb;
224 ctx->gb.tlli = tlli;
Harald Welte9b455bf2010-03-14 15:45:01 +0800225 ctx->mm_state = GMM_DEREGISTERED;
Jacob Erlbeckbd0cf112014-12-01 12:33:33 +0100226 ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
Maxb997f842016-07-06 15:57:01 +0200227 ctx->ciph_algo = sgsn->cfg.cipher;
Harald Welte8acd88f2010-05-18 10:57:45 +0200228 ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, tlli);
Harald Welte6ffbaab2010-05-18 12:44:45 +0200229 INIT_LLIST_HEAD(&ctx->pdp_list);
Harald Welte9b455bf2010-03-14 15:45:01 +0800230
231 llist_add(&ctx->list, &sgsn_mm_ctxts);
232
233 return ctx;
234}
Harald Welted193cb32010-05-17 22:58:03 +0200235
Daniel Willmann6292c8d2016-05-21 17:35:57 +0200236/* Allocate a new SGSN MM context */
237struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx)
238{
239 struct sgsn_mm_ctx *ctx;
240
241 ctx = talloc_zero(tall_bsc_ctx, struct sgsn_mm_ctx);
242 if (!ctx)
243 return NULL;
244
245 ctx->ran_type = MM_CTX_T_UTRAN_Iu;
246 ctx->iu.ue_ctx = uectx;
247 ctx->mm_state = GMM_DEREGISTERED;
248 ctx->auth_triplet.key_seq = GSM_KEY_SEQ_INVAL;
249 ctx->ctrg = rate_ctr_group_alloc(ctx, &mmctx_ctrg_desc, 0);
250
251 /* Need to get RAID from IU conn */
252 ctx->ra = ctx->iu.ue_ctx->ra_id;
253
254 INIT_LLIST_HEAD(&ctx->pdp_list);
255
256 llist_add(&ctx->list, &sgsn_mm_ctxts);
257
258 return ctx;
259}
260
261
Harald Welte7b022ee2012-07-14 12:04:04 +0200262/* this is a hard _free_ function, it doesn't clean up the PDP contexts
263 * in libgtp! */
Holger Hans Peter Freytherb448dd82015-05-03 11:46:58 +0200264static void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm)
Harald Weltec728eea2010-12-24 23:07:18 +0100265{
266 struct sgsn_pdp_ctx *pdp, *pdp2;
267
Jacob Erlbecke671d252015-01-26 14:43:07 +0100268 /* Unlink from global list of MM contexts */
269 llist_del(&mm->list);
270
271 /* Free all PDP contexts */
272 llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list)
273 sgsn_pdp_ctx_free(pdp);
274
275 rate_ctr_group_free(mm->ctrg);
276
277 talloc_free(mm);
278}
279
280void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *mm)
281{
Daniel Willmann7ec8ca42016-05-21 00:48:49 +0200282 struct gprs_llc_llme *llme = NULL;
Harald Weltef97ee042015-12-25 19:12:21 +0100283 uint32_t tlli = mm->gb.tlli;
Jacob Erlbecke671d252015-01-26 14:43:07 +0100284 struct sgsn_pdp_ctx *pdp, *pdp2;
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +0200285 struct sgsn_signal_data sig_data;
Jacob Erlbecke671d252015-01-26 14:43:07 +0100286
Daniel Willmann7ec8ca42016-05-21 00:48:49 +0200287 if (mm->ran_type == MM_CTX_T_GERAN_Gb)
288 llme = mm->gb.llme;
289 else
290 OSMO_ASSERT(mm->gb.llme == NULL);
291
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800292 /* Forget about ongoing look-ups */
293 if (mm->ggsn_lookup) {
294 LOGMMCTXP(LOGL_NOTICE, mm,
295 "Cleaning mmctx with on-going query.\n");
296 mm->ggsn_lookup->mmctx = NULL;
297 mm->ggsn_lookup = NULL;
298 }
299
Jacob Erlbecke671d252015-01-26 14:43:07 +0100300 /* delete all existing PDP contexts for this MS */
301 llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) {
302 LOGMMCTXP(LOGL_NOTICE, mm,
303 "Dropping PDP context for NSAPI=%u\n", pdp->nsapi);
304 sgsn_pdp_ctx_terminate(pdp);
305 }
306
Jacob Erlbeckae20b4b2014-10-20 16:05:55 +0200307 if (osmo_timer_pending(&mm->timer)) {
308 LOGMMCTXP(LOGL_INFO, mm, "Cancelling MM timer %u\n", mm->T);
309 osmo_timer_del(&mm->timer);
310 }
311
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +0200312 memset(&sig_data, 0, sizeof(sig_data));
313 sig_data.mm = mm;
314 osmo_signal_dispatch(SS_SGSN, S_SGSN_MM_FREE, &sig_data);
315
316
Jacob Erlbeckbe2c8d92014-11-12 10:18:09 +0100317 /* Detach from subscriber which is possibly freed then */
318 if (mm->subscr) {
Jacob Erlbeck306bb992015-01-26 13:41:11 +0100319 struct gsm_subscriber *subscr = subscr_get(mm->subscr);
Jacob Erlbeck3e4e58f2015-01-26 11:07:24 +0100320 gprs_subscr_cleanup(subscr);
Jacob Erlbeck37139e52015-01-23 13:52:55 +0100321 subscr_put(subscr);
Jacob Erlbeckbe2c8d92014-11-12 10:18:09 +0100322 }
323
Jacob Erlbecke671d252015-01-26 14:43:07 +0100324 sgsn_mm_ctx_free(mm);
325 mm = NULL;
Harald Weltec728eea2010-12-24 23:07:18 +0100326
Daniel Willmann7ec8ca42016-05-21 00:48:49 +0200327 if (llme) {
328 /* TLLI unassignment, must be called after sgsn_mm_ctx_free */
Max5aa51962016-07-06 11:33:04 +0200329 gprs_llgmm_assign(llme, tlli, 0xffffffff);
Daniel Willmann7ec8ca42016-05-21 00:48:49 +0200330 }
Harald Weltec728eea2010-12-24 23:07:18 +0100331}
Harald Welte77289c22010-05-18 14:32:29 +0200332
Jacob Erlbecke671d252015-01-26 14:43:07 +0100333
Harald Welte96df6062010-06-03 06:37:26 +0200334/* look up PDP context by MM context and NSAPI */
Harald Welted193cb32010-05-17 22:58:03 +0200335struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_nsapi(const struct sgsn_mm_ctx *mm,
336 uint8_t nsapi)
337{
338 struct sgsn_pdp_ctx *pdp;
339
340 llist_for_each_entry(pdp, &mm->pdp_list, list) {
341 if (pdp->nsapi == nsapi)
342 return pdp;
343 }
344 return NULL;
345}
346
Harald Welte96df6062010-06-03 06:37:26 +0200347/* look up PDP context by MM context and transaction ID */
Harald Welte77289c22010-05-18 14:32:29 +0200348struct sgsn_pdp_ctx *sgsn_pdp_ctx_by_tid(const struct sgsn_mm_ctx *mm,
349 uint8_t tid)
350{
351 struct sgsn_pdp_ctx *pdp;
352
353 llist_for_each_entry(pdp, &mm->pdp_list, list) {
354 if (pdp->ti == tid)
355 return pdp;
356 }
357 return NULL;
358}
359
Harald Welte7b022ee2012-07-14 12:04:04 +0200360/* you don't want to use this directly, call sgsn_create_pdp_ctx() */
Harald Welted193cb32010-05-17 22:58:03 +0200361struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm,
362 uint8_t nsapi)
363{
364 struct sgsn_pdp_ctx *pdp;
365
366 pdp = sgsn_pdp_ctx_by_nsapi(mm, nsapi);
367 if (pdp)
368 return NULL;
369
370 pdp = talloc_zero(tall_bsc_ctx, struct sgsn_pdp_ctx);
371 if (!pdp)
372 return NULL;
373
374 pdp->mm = mm;
375 pdp->nsapi = nsapi;
Harald Welteefbdee92010-06-10 00:20:12 +0200376 pdp->ctrg = rate_ctr_group_alloc(pdp, &pdpctx_ctrg_desc, nsapi);
Harald Welted193cb32010-05-17 22:58:03 +0200377 llist_add(&pdp->list, &mm->pdp_list);
378 llist_add(&pdp->g_list, &sgsn_pdp_ctxts);
379
380 return pdp;
381}
382
Jacob Erlbeck99985b52014-10-13 10:32:00 +0200383/*
384 * This function will not trigger any GSM DEACT PDP ACK messages, so you
385 * probably want to call sgsn_delete_pdp_ctx() instead if the connection
386 * isn't detached already.
387 */
388void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp)
389{
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +0200390 struct sgsn_signal_data sig_data;
391
Jacob Erlbeck99985b52014-10-13 10:32:00 +0200392 OSMO_ASSERT(pdp->mm != NULL);
393
394 /* There might still be pending callbacks in libgtp. So the parts of
395 * this object relevant to GTP need to remain intact in this case. */
396
397 LOGPDPCTXP(LOGL_INFO, pdp, "Forcing release of PDP context\n");
398
Daniel Willmannf9f43872016-05-20 22:36:23 +0200399 if (pdp->mm->ran_type == MM_CTX_T_GERAN_Gb) {
400 /* Force the deactivation of the SNDCP layer */
401 sndcp_sm_deactivate_ind(&pdp->mm->gb.llme->lle[pdp->sapi], pdp->nsapi);
402 }
Jacob Erlbeck99985b52014-10-13 10:32:00 +0200403
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +0200404 memset(&sig_data, 0, sizeof(sig_data));
405 sig_data.pdp = pdp;
406 osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_TERMINATE, &sig_data);
407
Jacob Erlbeck99985b52014-10-13 10:32:00 +0200408 /* Detach from MM context */
409 llist_del(&pdp->list);
410 pdp->mm = NULL;
411
412 sgsn_delete_pdp_ctx(pdp);
413}
414
415/*
416 * Don't call this function directly unless you know what you are doing.
417 * In normal conditions use sgsn_delete_pdp_ctx and in unspecified or
418 * implementation dependent abnormal ones sgsn_pdp_ctx_terminate.
419 */
Harald Welted193cb32010-05-17 22:58:03 +0200420void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp)
421{
Holger Hans Peter Freytherb1008952015-05-02 19:55:38 +0200422 struct sgsn_signal_data sig_data;
423
424 memset(&sig_data, 0, sizeof(sig_data));
425 sig_data.pdp = pdp;
426 osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_FREE, &sig_data);
427
Harald Welte376d5e52010-06-28 18:57:21 +0200428 rate_ctr_group_free(pdp->ctrg);
Jacob Erlbeck99985b52014-10-13 10:32:00 +0200429 if (pdp->mm)
430 llist_del(&pdp->list);
Harald Welted193cb32010-05-17 22:58:03 +0200431 llist_del(&pdp->g_list);
Harald Weltefdf453c2012-07-14 12:15:19 +0200432
433 /* _if_ we still have a library handle, at least set it to NULL
434 * to avoid any dereferences of the now-deleted PDP context from
435 * sgsn_libgtp:cb_data_ind() */
436 if (pdp->lib) {
437 struct pdp_t *lib = pdp->lib;
Daniel Willmann46553142014-09-03 17:46:44 +0200438 LOGPDPCTXP(LOGL_NOTICE, pdp, "freeing PDP context that still "
Harald Weltefdf453c2012-07-14 12:15:19 +0200439 "has a libgtp handle attached to it, this shouldn't "
440 "happen!\n");
441 osmo_generate_backtrace();
442 lib->priv = NULL;
443 }
444
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800445 if (pdp->destroy_ggsn)
446 sgsn_ggsn_ctx_free(pdp->ggsn);
Harald Welted193cb32010-05-17 22:58:03 +0200447 talloc_free(pdp);
448}
449
450/* GGSN contexts */
451
Harald Welte77289c22010-05-18 14:32:29 +0200452struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id)
Harald Welted193cb32010-05-17 22:58:03 +0200453{
Harald Welte77289c22010-05-18 14:32:29 +0200454 struct sgsn_ggsn_ctx *ggc;
Harald Welted193cb32010-05-17 22:58:03 +0200455
Harald Welte77289c22010-05-18 14:32:29 +0200456 ggc = talloc_zero(tall_bsc_ctx, struct sgsn_ggsn_ctx);
Harald Welted193cb32010-05-17 22:58:03 +0200457 if (!ggc)
458 return NULL;
459
460 ggc->id = id;
461 ggc->gtp_version = 1;
Harald Weltea9b473a2010-12-24 21:13:26 +0100462 ggc->remote_restart_ctr = -1;
Harald Welteab1d5622010-05-18 19:58:38 +0200463 /* if we are called from config file parse, this gsn doesn't exist yet */
464 ggc->gsn = sgsn->gsn;
Harald Welte119c2ba2010-05-18 18:39:00 +0200465 llist_add(&ggc->list, &sgsn_ggsn_ctxts);
Harald Welted193cb32010-05-17 22:58:03 +0200466
467 return ggc;
468}
469
Jacob Erlbeckf3456122015-02-03 19:53:15 +0100470void sgsn_ggsn_ctx_free(struct sgsn_ggsn_ctx *ggc)
471{
472 llist_del(&ggc->list);
473 talloc_free(ggc);
474}
475
Harald Welte77289c22010-05-18 14:32:29 +0200476struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id)
Harald Welted193cb32010-05-17 22:58:03 +0200477{
Harald Welte77289c22010-05-18 14:32:29 +0200478 struct sgsn_ggsn_ctx *ggc;
Harald Welted193cb32010-05-17 22:58:03 +0200479
480 llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
481 if (id == ggc->id)
482 return ggc;
483 }
484 return NULL;
485}
486
Harald Weltea9b473a2010-12-24 21:13:26 +0100487struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr)
488{
489 struct sgsn_ggsn_ctx *ggc;
490
491 llist_for_each_entry(ggc, &sgsn_ggsn_ctxts, list) {
492 if (!memcmp(addr, &ggc->remote_addr, sizeof(*addr)))
493 return ggc;
494 }
495 return NULL;
496}
497
498
Harald Welte77289c22010-05-18 14:32:29 +0200499struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id)
Harald Welted193cb32010-05-17 22:58:03 +0200500{
Harald Welte77289c22010-05-18 14:32:29 +0200501 struct sgsn_ggsn_ctx *ggc;
Harald Welted193cb32010-05-17 22:58:03 +0200502
Harald Welte77289c22010-05-18 14:32:29 +0200503 ggc = sgsn_ggsn_ctx_by_id(id);
Harald Welted193cb32010-05-17 22:58:03 +0200504 if (!ggc)
Harald Welte77289c22010-05-18 14:32:29 +0200505 ggc = sgsn_ggsn_ctx_alloc(id);
Harald Welted193cb32010-05-17 22:58:03 +0200506 return ggc;
507}
508
509/* APN contexts */
510
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100511static struct apn_ctx *sgsn_apn_ctx_alloc(const char *ap_name, const char *imsi_prefix)
Harald Welted193cb32010-05-17 22:58:03 +0200512{
513 struct apn_ctx *actx;
514
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100515 actx = talloc_zero(tall_bsc_ctx, struct apn_ctx);
Harald Welted193cb32010-05-17 22:58:03 +0200516 if (!actx)
517 return NULL;
518 actx->name = talloc_strdup(actx, ap_name);
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100519 actx->imsi_prefix = talloc_strdup(actx, imsi_prefix);
520
521 llist_add_tail(&actx->list, &sgsn_apn_ctxts);
Harald Welted193cb32010-05-17 22:58:03 +0200522
523 return actx;
524}
525
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100526void sgsn_apn_ctx_free(struct apn_ctx *actx)
527{
528 llist_del(&actx->list);
529 talloc_free(actx);
530}
531
532struct apn_ctx *sgsn_apn_ctx_match(const char *name, const char *imsi)
533{
534 struct apn_ctx *actx;
535 struct apn_ctx *found_actx = NULL;
536 size_t imsi_prio = 0;
537 size_t name_prio = 0;
538 size_t name_req_len = strlen(name);
539
540 llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
541 size_t name_ref_len, imsi_ref_len;
542 const char *name_ref_start, *name_match_start;
543
544 imsi_ref_len = strlen(actx->imsi_prefix);
545 if (strncmp(actx->imsi_prefix, imsi, imsi_ref_len) != 0)
546 continue;
547
548 if (imsi_ref_len < imsi_prio)
549 continue;
550
551 /* IMSI matches */
552
553 name_ref_start = &actx->name[0];
554 if (name_ref_start[0] == '*') {
555 /* Suffix match */
556 name_ref_start += 1;
557 name_ref_len = strlen(name_ref_start);
558 if (name_ref_len > name_req_len)
559 continue;
560 } else {
561 name_ref_len = strlen(name_ref_start);
562 if (name_ref_len != name_req_len)
563 continue;
564 }
565
566 name_match_start = name + (name_req_len - name_ref_len);
567 if (strcasecmp(name_match_start, name_ref_start) != 0)
568 continue;
569
570 /* IMSI and name match */
571
572 if (imsi_ref_len == imsi_prio && name_ref_len < name_prio)
573 /* Lower priority, skip */
574 continue;
575
576 imsi_prio = imsi_ref_len;
577 name_prio = name_ref_len;
578 found_actx = actx;
579 }
580 return found_actx;
581}
582
583struct apn_ctx *sgsn_apn_ctx_by_name(const char *name, const char *imsi_prefix)
Harald Welted193cb32010-05-17 22:58:03 +0200584{
585 struct apn_ctx *actx;
586
587 llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100588 if (strcasecmp(name, actx->name) == 0 &&
589 strcasecmp(imsi_prefix, actx->imsi_prefix) == 0)
Harald Welted193cb32010-05-17 22:58:03 +0200590 return actx;
591 }
592 return NULL;
593}
594
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100595struct apn_ctx *sgsn_apn_ctx_find_alloc(const char *name, const char *imsi_prefix)
Harald Welted193cb32010-05-17 22:58:03 +0200596{
597 struct apn_ctx *actx;
598
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100599 actx = sgsn_apn_ctx_by_name(name, imsi_prefix);
Harald Welted193cb32010-05-17 22:58:03 +0200600 if (!actx)
Jacob Erlbeckcb1db8b2015-02-03 13:47:53 +0100601 actx = sgsn_apn_ctx_alloc(name, imsi_prefix);
Harald Welted193cb32010-05-17 22:58:03 +0200602
603 return actx;
604}
Harald Welte6463c072010-05-18 17:04:55 +0200605
606uint32_t sgsn_alloc_ptmsi(void)
607{
608 struct sgsn_mm_ctx *mm;
609 uint32_t ptmsi;
Jacob Erlbeckd8a65532015-01-15 18:51:31 +0100610 int max_retries = 100;
Harald Welte6463c072010-05-18 17:04:55 +0200611
612restart:
Daniel Willmann044ce5f2015-10-12 19:36:33 +0200613 if (RAND_bytes((uint8_t *) &ptmsi, sizeof(ptmsi)) != 1)
614 goto failed;
615
Jacob Erlbeckd8a65532015-01-15 18:51:31 +0100616 /* Enforce that the 2 MSB are set without loosing the distance between
617 * identical values. Since rand() has no duplicate values within a
618 * period (because the size of the state is the same like the size of
619 * the random value), this leads to a distance of period/4 when the
620 * distribution of the 2 MSB is uniform. This approach fails with a
621 * probability of (3/4)^max_retries, only 1% of the approaches will
622 * need more than 16 numbers (even distribution assumed).
623 *
624 * Alternatively, a freeze list could be used if another PRNG is used
625 * or when this approach proves to be not sufficient.
626 */
627 if (ptmsi >= 0xC0000000) {
628 if (!max_retries--)
629 goto failed;
630 goto restart;
631 }
632 ptmsi |= 0xC0000000;
633
634 if (ptmsi == GSM_RESERVED_TMSI) {
635 if (!max_retries--)
636 goto failed;
637 goto restart;
638 }
639
Harald Welte6463c072010-05-18 17:04:55 +0200640 llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
Jacob Erlbeck08fbeb82014-09-19 09:28:42 +0200641 if (mm->p_tmsi == ptmsi) {
642 if (!max_retries--)
643 goto failed;
Harald Welte6463c072010-05-18 17:04:55 +0200644 goto restart;
Jacob Erlbeck08fbeb82014-09-19 09:28:42 +0200645 }
Harald Welte6463c072010-05-18 17:04:55 +0200646 }
647
648 return ptmsi;
Jacob Erlbeck08fbeb82014-09-19 09:28:42 +0200649
650failed:
651 LOGP(DGPRS, LOGL_ERROR, "Failed to allocate a P-TMSI\n");
652 return GSM_RESERVED_TMSI;
Harald Welte6463c072010-05-18 17:04:55 +0200653}
Harald Weltea9b473a2010-12-24 21:13:26 +0100654
655static void drop_one_pdp(struct sgsn_pdp_ctx *pdp)
656{
657 if (pdp->mm->mm_state == GMM_REGISTERED_NORMAL)
658 gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL);
659 else {
660 /* FIXME: GPRS paging in case MS is SUSPENDED */
Daniel Willmann46553142014-09-03 17:46:44 +0200661 LOGPDPCTXP(LOGL_NOTICE, pdp, "Hard-dropping PDP ctx due to GGSN "
Harald Weltea9b473a2010-12-24 21:13:26 +0100662 "recovery\n");
Harald Welte7b022ee2012-07-14 12:04:04 +0200663 /* FIXME: how to tell this to libgtp? */
Harald Weltea9b473a2010-12-24 21:13:26 +0100664 sgsn_pdp_ctx_free(pdp);
665 }
666}
667
668/* High-level function to be called in case a GGSN has disappeared or
Holger Hans Peter Freyther19e990d2014-10-27 10:24:37 +0100669 * otherwise lost state (recovery procedure) */
Harald Weltea9b473a2010-12-24 21:13:26 +0100670int drop_all_pdp_for_ggsn(struct sgsn_ggsn_ctx *ggsn)
671{
672 struct sgsn_mm_ctx *mm;
673 int num = 0;
674
675 llist_for_each_entry(mm, &sgsn_mm_ctxts, list) {
676 struct sgsn_pdp_ctx *pdp;
677 llist_for_each_entry(pdp, &mm->pdp_list, list) {
678 if (pdp->ggsn == ggsn) {
679 drop_one_pdp(pdp);
680 num++;
681 }
682 }
683 }
684
685 return num;
686}
Jacob Erlbeck78ecaf02014-09-05 14:32:36 +0200687
Jacob Erlbeck555b2e52015-01-26 13:52:42 +0100688void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx)
Jacob Erlbeck423f8bf2014-10-24 18:09:54 +0200689{
Jacob Erlbeck555b2e52015-01-26 13:52:42 +0100690 OSMO_ASSERT(mmctx != NULL);
Jacob Erlbeckc9391962014-12-18 09:53:07 +0100691 LOGMMCTXP(LOGL_INFO, mmctx, "Subscriber data update\n");
Jacob Erlbeckbe2c8d92014-11-12 10:18:09 +0100692
Jacob Erlbecka0b6efb2014-11-13 10:48:39 +0100693 sgsn_auth_update(mmctx);
Jacob Erlbeck423f8bf2014-10-24 18:09:54 +0200694}
Jacob Erlbeck81ffb742015-01-23 11:33:51 +0100695
Holger Hans Peter Freyther8cedded2015-04-23 11:33:35 -0400696static void insert_qos(struct tlv_parsed *tp, struct sgsn_subscriber_pdp_data *pdp)
697{
698 tp->lv[OSMO_IE_GSM_SUB_QOS].len = pdp->qos_subscribed_len;
699 tp->lv[OSMO_IE_GSM_SUB_QOS].val = pdp->qos_subscribed;
700}
701
702/**
703 * The tlv_parsed tp parameter will be modified to insert a
704 * OSMO_IE_GSM_SUB_QOS in case the data is available in the
705 * PDP context handling.
706 */
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100707struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx,
708 struct tlv_parsed *tp,
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800709 enum gsm48_gsm_cause *gsm_cause,
710 char *out_apn_str)
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100711{
712 char req_apn_str[GSM_APN_LENGTH] = {0};
713 const struct apn_ctx *apn_ctx = NULL;
714 const char *selected_apn_str = NULL;
715 struct sgsn_subscriber_pdp_data *pdp;
716 struct sgsn_ggsn_ctx *ggsn = NULL;
717 int allow_any_apn = 0;
718
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800719 out_apn_str[0] = '\0';
720
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100721 if (TLVP_PRESENT(tp, GSM48_IE_GSM_APN)) {
722 if (TLVP_LEN(tp, GSM48_IE_GSM_APN) >= GSM_APN_LENGTH - 1) {
723 LOGMMCTXP(LOGL_ERROR, mmctx, "APN IE too long\n");
724 *gsm_cause = GSM_CAUSE_INV_MAND_INFO;
725 return NULL;
726 }
727
728 gprs_apn_to_str(req_apn_str,
729 TLVP_VAL(tp, GSM48_IE_GSM_APN),
730 TLVP_LEN(tp, GSM48_IE_GSM_APN));
731
732 if (strcmp(req_apn_str, "*") == 0)
733 req_apn_str[0] = 0;
734 }
735
Holger Hans Peter Freyther9270d992015-05-24 20:51:17 +0800736 if (mmctx->subscr == NULL)
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100737 allow_any_apn = 1;
738
739 if (strlen(req_apn_str) == 0 && !allow_any_apn) {
740 /* No specific APN requested, check for an APN that is both
741 * granted and configured */
742
743 llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) {
744 if (strcmp(pdp->apn_str, "*") == 0)
745 {
746 allow_any_apn = 1;
747 selected_apn_str = "";
Holger Hans Peter Freyther8cedded2015-04-23 11:33:35 -0400748 insert_qos(tp, pdp);
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100749 continue;
750 }
751 if (!llist_empty(&sgsn_apn_ctxts)) {
752 apn_ctx = sgsn_apn_ctx_match(req_apn_str, mmctx->imsi);
753 /* Not configured */
754 if (apn_ctx == NULL)
755 continue;
756 }
Holger Hans Peter Freyther8cedded2015-04-23 11:33:35 -0400757 insert_qos(tp, pdp);
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100758 selected_apn_str = pdp->apn_str;
759 break;
760 }
761 } else if (!allow_any_apn) {
762 /* Check whether the given APN is granted */
763 llist_for_each_entry(pdp, &mmctx->subscr->sgsn_data->pdp_list, list) {
764 if (strcmp(pdp->apn_str, "*") == 0) {
Holger Hans Peter Freyther8cedded2015-04-23 11:33:35 -0400765 insert_qos(tp, pdp);
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100766 selected_apn_str = req_apn_str;
767 allow_any_apn = 1;
768 continue;
769 }
770 if (strcasecmp(pdp->apn_str, req_apn_str) == 0) {
Holger Hans Peter Freyther8cedded2015-04-23 11:33:35 -0400771 insert_qos(tp, pdp);
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100772 selected_apn_str = req_apn_str;
773 break;
774 }
775 }
776 } else if (strlen(req_apn_str) != 0) {
777 /* Any APN is allowed */
778 selected_apn_str = req_apn_str;
779 } else {
780 /* Prefer the GGSN associated with the wildcard APN */
781 selected_apn_str = "";
782 }
783
784 if (!allow_any_apn && selected_apn_str == NULL) {
785 /* Access not granted */
786 LOGMMCTXP(LOGL_NOTICE, mmctx,
787 "The requested APN '%s' is not allowed\n",
788 req_apn_str);
789 *gsm_cause = GSM_CAUSE_REQ_SERV_OPT_NOTSUB;
790 return NULL;
791 }
792
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800793 /* copy the selected apn_str */
Holger Hans Peter Freytherf2e114a2015-06-02 09:33:31 +0200794 if (selected_apn_str)
795 strcpy(out_apn_str, selected_apn_str);
796 else
797 out_apn_str[0] = '\0';
Holger Hans Peter Freyther39c430e2015-05-25 12:26:49 +0800798
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100799 if (apn_ctx == NULL && selected_apn_str)
800 apn_ctx = sgsn_apn_ctx_match(selected_apn_str, mmctx->imsi);
801
802 if (apn_ctx != NULL) {
803 ggsn = apn_ctx->ggsn;
804 } else if (llist_empty(&sgsn_apn_ctxts)) {
805 /* No configuration -> use GGSN 0 */
806 ggsn = sgsn_ggsn_ctx_by_id(0);
807 } else if (allow_any_apn &&
808 (selected_apn_str == NULL || strlen(selected_apn_str) == 0)) {
809 /* No APN given and no default configuration -> Use GGSN 0 */
810 ggsn = sgsn_ggsn_ctx_by_id(0);
811 } else {
812 /* No matching configuration found */
813 LOGMMCTXP(LOGL_NOTICE, mmctx,
814 "The selected APN '%s' has not been configured\n",
815 selected_apn_str);
816 *gsm_cause = GSM_CAUSE_MISSING_APN;
817 return NULL;
818 }
819
Holger Hans Peter Freyther08bb84b2015-05-25 14:35:10 +0800820 if (!ggsn) {
821 LOGMMCTXP(LOGL_NOTICE, mmctx,
822 "No static GGSN configured. Selected APN '%s'\n",
823 selected_apn_str);
824 return NULL;
825 }
826
Jacob Erlbeck277b71e2015-02-02 18:03:05 +0100827 LOGMMCTXP(LOGL_INFO, mmctx,
828 "Found GGSN %d for APN '%s' (requested '%s')\n",
829 ggsn->id, selected_apn_str ? selected_apn_str : "---",
830 req_apn_str);
831
832 return ggsn;
833}
834
Jacob Erlbeck81ffb742015-01-23 11:33:51 +0100835static void sgsn_llme_cleanup_free(struct gprs_llc_llme *llme)
836{
837 struct sgsn_mm_ctx *mmctx = NULL;
838
839 llist_for_each_entry(mmctx, &sgsn_mm_ctxts, list) {
Harald Weltef97ee042015-12-25 19:12:21 +0100840 if (llme == mmctx->gb.llme) {
Jacob Erlbeck81ffb742015-01-23 11:33:51 +0100841 gsm0408_gprs_access_cancelled(mmctx, SGSN_ERROR_CAUSE_NONE);
842 return;
843 }
844 }
845
846 /* No MM context found */
847 LOGP(DGPRS, LOGL_INFO, "Deleting orphaned LLME, TLLI 0x%08x\n",
848 llme->tlli);
Max39550252016-06-28 17:39:20 +0200849 gprs_llgmm_unassign(llme);
Jacob Erlbeck81ffb742015-01-23 11:33:51 +0100850}
851
852static void sgsn_llme_check_cb(void *data_)
853{
854 struct gprs_llc_llme *llme, *llme_tmp;
855 struct timespec now_tp;
856 time_t now, age;
857 time_t max_age = gprs_max_time_to_idle();
858
859 int rc;
860
861 rc = clock_gettime(CLOCK_MONOTONIC, &now_tp);
862 OSMO_ASSERT(rc >= 0);
863 now = now_tp.tv_sec;
864
865 LOGP(DGPRS, LOGL_DEBUG,
866 "Checking for inactive LLMEs, time = %u\n", (unsigned)now);
867
868 llist_for_each_entry_safe(llme, llme_tmp, &gprs_llc_llmes, list) {
869 if (llme->age_timestamp == GPRS_LLME_RESET_AGE)
870 llme->age_timestamp = now;
871
872 age = now - llme->age_timestamp;
873
874 if (age > max_age || age < 0) {
875 LOGP(DGPRS, LOGL_INFO,
876 "Inactivity timeout for TLLI 0x%08x, age %d\n",
877 llme->tlli, (int)age);
878 sgsn_llme_cleanup_free(llme);
879 }
880 }
881
882 osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0);
883}
884
885void sgsn_inst_init()
886{
887 sgsn->llme_timer.cb = sgsn_llme_check_cb;
888 sgsn->llme_timer.data = NULL;
889
890 osmo_timer_schedule(&sgsn->llme_timer, GPRS_LLME_CHECK_TICK, 0);
891}
892