diff --git a/openbsc/include/openbsc/gprs_gmm.h b/openbsc/include/openbsc/gprs_gmm.h index b91b489d3..bd129ae4d 100644 --- a/openbsc/include/openbsc/gprs_gmm.h +++ b/openbsc/include/openbsc/gprs_gmm.h @@ -4,6 +4,7 @@ #include #include +int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause); int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, uint8_t cause, uint8_t pco_len, uint8_t *pco_v); int gsm48_tx_gsm_act_pdp_acc(struct sgsn_pdp_ctx *pdp); diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h index b32c5cd4c..fb1b2882b 100644 --- a/openbsc/include/openbsc/gprs_sgsn.h +++ b/openbsc/include/openbsc/gprs_sgsn.h @@ -125,6 +125,10 @@ enum pdp_ctx_state { PDP_STATE_NONE, PDP_STATE_CR_REQ, PDP_STATE_CR_CONF, + + /* 04.08 / Figure 6.2 / 6.1.2.2 */ + PDP_STATE_INACT_PEND, + PDP_STATE_INACTIVE = PDP_STATE_NONE, }; enum pdp_type { @@ -162,6 +166,10 @@ struct sgsn_pdp_ctx { uint32_t rx_gtp_snu; //uint32_t charging_id; int reordering_reqd; + + struct timer_list timer; + unsigned int T; /* Txxxx number */ + unsigned int num_T_exp; /* number of consecutive T expirations */ }; @@ -182,10 +190,12 @@ struct sgsn_ggsn_ctx { uint32_t id; unsigned int gtp_version; struct in_addr remote_addr; + int remote_restart_ctr; struct gsn_t *gsn; }; struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id); struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id); +struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_addr(struct in_addr *addr); struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_find_alloc(uint32_t id); struct apn_ctx { @@ -202,4 +212,8 @@ extern struct llist_head sgsn_pdp_ctxts; uint32_t sgsn_alloc_ptmsi(void); +/* 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); + #endif /* _GPRS_SGSN_H */ diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c index 158f577a7..ee6e3665b 100644 --- a/openbsc/src/gprs/gprs_gmm.c +++ b/openbsc/src/gprs/gprs_gmm.c @@ -67,6 +67,12 @@ #define GSM0408_T3314_SECS 44 /* force to STBY on expiry */ #define GSM0408_T3316_SECS 44 +/* Section 11.3 / Table 11.2d Timers of Session Management - network side */ +#define GSM0408_T3385_SECS 8 /* wait for ACT PDP CTX REQ */ +#define GSM0408_T3386_SECS 8 /* wait for MODIFY PDP CTX ACK */ +#define GSM0408_T3395_SECS 8 /* wait for DEACT PDP CTX ACK */ +#define GSM0408_T3397_SECS 8 /* wait for DEACT AA PDP CTX ACK */ + extern struct sgsn_instance *sgsn; /* Protocol related stuff, should go into libosmocore */ @@ -1092,6 +1098,25 @@ static void mmctx_timer_cb(void *_mm) /* GPRS SESSION MANAGEMENT */ +static void pdpctx_timer_cb(void *_mm); + +static void pdpctx_timer_start(struct sgsn_pdp_ctx *pdp, unsigned int T, + unsigned int seconds) +{ + if (bsc_timer_pending(&pdp->timer)) + LOGP(DMM, LOGL_ERROR, "Starting MM timer %u while old " + "timer %u pending\n", T, pdp->T); + pdp->T = T; + pdp->num_T_exp = 0; + + /* FIXME: we should do this only once ? */ + pdp->timer.data = pdp; + pdp->timer.cb = &pdpctx_timer_cb; + + bsc_schedule_timer(&pdp->timer, seconds, 0); +} + + static void msgb_put_pdp_addr_ipv4(struct msgb *msg, uint32_t ipaddr) { uint8_t v[6]; @@ -1179,6 +1204,33 @@ int gsm48_tx_gsm_act_pdp_rej(struct sgsn_mm_ctx *mm, uint8_t tid, return gsm48_gmm_sendmsg(msg, 0, mm); } +/* Section 9.5.8: Deactivate PDP Context Request */ +static int _gsm48_tx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, uint8_t tid, + uint8_t sm_cause) +{ + struct msgb *msg = gsm48_msgb_alloc(); + struct gsm48_hdr *gh; + uint8_t transaction_id = tid ^ 0x8; /* flip */ + + DEBUGP(DMM, "<- DEACTIVATE PDP CONTEXT REQ\n"); + + mmctx2msgid(msg, mm); + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_SM_GPRS | (transaction_id << 4); + gh->msg_type = GSM48_MT_GSM_DEACT_PDP_REQ; + + msgb_v_put(msg, sm_cause); + + return gsm48_gmm_sendmsg(msg, 0, mm); +} +int gsm48_tx_gsm_deact_pdp_req(struct sgsn_pdp_ctx *pdp, uint8_t sm_cause) +{ + pdpctx_timer_start(pdp, 3395, GSM0408_T3395_SECS); + + return _gsm48_tx_gsm_deact_pdp_req(pdp->mm, pdp->ti, sm_cause); +} + /* Section 9.5.9: Deactivate PDP Context Accept */ static int _gsm48_tx_gsm_deact_pdp_acc(struct sgsn_mm_ctx *mm, uint8_t tid) { @@ -1341,6 +1393,26 @@ static int gsm48_rx_gsm_deact_pdp_req(struct sgsn_mm_ctx *mm, struct msgb *msg) return sgsn_delete_pdp_ctx(pdp); } +/* Section 9.5.9: Deactivate PDP Context Accept */ +static int gsm48_rx_gsm_deact_pdp_ack(struct sgsn_mm_ctx *mm, struct msgb *msg) +{ + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); + uint8_t transaction_id = (gh->proto_discr >> 4); + struct sgsn_pdp_ctx *pdp; + + DEBUGP(DMM, "-> DEACTIVATE PDP CONTEXT ACK\n"); + + pdp = sgsn_pdp_ctx_by_tid(mm, transaction_id); + if (!pdp) { + LOGP(DMM, LOGL_NOTICE, "Deactivate PDP Context Accept for " + "non-existing PDP Context (IMSI=%s, TI=%u)\n", + mm->imsi, transaction_id); + return 0; + } + + return sgsn_delete_pdp_ctx(pdp); +} + static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); @@ -1351,6 +1423,30 @@ static int gsm48_rx_gsm_status(struct sgsn_mm_ctx *ctx, struct msgb *msg) return 0; } +static void pdpctx_timer_cb(void *_pdp) +{ + struct sgsn_pdp_ctx *pdp = _pdp; + + pdp->num_T_exp++; + + switch (pdp->T) { + case 3395: /* waiting for PDP CTX DEACT ACK */ + if (pdp->num_T_exp >= 4) { + LOGP(DMM, LOGL_NOTICE, "T3395 expired >= 5 times\n"); + pdp->state = PDP_STATE_INACTIVE; + sgsn_delete_pdp_ctx(pdp); + break; + } + gsm48_tx_gsm_deact_pdp_req(pdp, GSM_CAUSE_NET_FAIL); + bsc_schedule_timer(&pdp->timer, GSM0408_T3395_SECS, 0); + break; + default: + LOGP(DMM, LOGL_ERROR, "timer expired in unknown mode %u\n", + pdp->T); + } +} + + /* GPRS Session Management */ static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, struct gprs_llc_llme *llme) @@ -1373,6 +1469,9 @@ static int gsm0408_rcv_gsm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, case GSM48_MT_GSM_DEACT_PDP_REQ: rc = gsm48_rx_gsm_deact_pdp_req(mmctx, msg); break; + case GSM48_MT_GSM_DEACT_PDP_ACK: + rc = gsm48_rx_gsm_deact_pdp_ack(mmctx, msg); + break; case GSM48_MT_GSM_STATUS: rc = gsm48_rx_gsm_status(mmctx, msg); break; diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c index 48a00b822..f5580037e 100644 --- a/openbsc/src/gprs/gprs_sgsn.c +++ b/openbsc/src/gprs/gprs_sgsn.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include extern struct sgsn_instance *sgsn; @@ -240,6 +242,7 @@ struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_alloc(uint32_t id) 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); @@ -258,6 +261,18 @@ struct sgsn_ggsn_ctx *sgsn_ggsn_ctx_by_id(uint32_t id) 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; @@ -320,3 +335,35 @@ 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; +} diff --git a/openbsc/src/gprs/sgsn_libgtp.c b/openbsc/src/gprs/sgsn_libgtp.c index 5278e4d88..e5defe738 100644 --- a/openbsc/src/gprs/sgsn_libgtp.c +++ b/openbsc/src/gprs/sgsn_libgtp.c @@ -325,15 +325,38 @@ static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) } /* Confirmation of an GTP ECHO request */ -static int echo_conf(int recovery) +static int echo_conf(struct pdp_t *pdp, void *cbp, int recovery) { if (recovery < 0) { DEBUGP(DGPRS, "GTP Echo Request timed out\n"); /* FIXME: if version == 1, retry with version 0 */ } else { DEBUGP(DGPRS, "GTP Rx Echo Response\n"); - /* FIXME: check if recovery counter has incremented and - * release all PDP context (if it has) */ + } + return 0; +} + +/* Any message received by GGSN contains a recovery IE */ +static int cb_recovery(struct sockaddr_in *peer, uint8_t recovery) +{ + struct sgsn_ggsn_ctx *ggsn; + + ggsn = sgsn_ggsn_ctx_by_addr(&peer->sin_addr); + if (!ggsn) { + DEBUGP(DGPRS, "Received Recovery IE for unknown GGSN\n"); + return -EINVAL; + } + + if (ggsn->remote_restart_ctr == -1) { + /* First received ECHO RESPONSE, note the restart ctr */ + ggsn->remote_restart_ctr = recovery; + } else if (ggsn->remote_restart_ctr != recovery) { + /* counter has changed (GGSN restart): release all PDP */ + LOGP(DGPRS, LOGL_NOTICE, "GGSN recovery (%u->%u), " + "releasing all PDP contexts\n", + ggsn->remote_restart_ctr, recovery); + ggsn->remote_restart_ctr = recovery; + drop_all_pdp_for_ggsn(ggsn); } return 0; } @@ -351,7 +374,7 @@ static int cb_conf(int type, int cause, struct pdp_t *pdp, void *cbp) switch (type) { case GTP_ECHO_REQ: /* libgtp hands us the RECOVERY number instead of a cause */ - return echo_conf(cause); + return echo_conf(pdp, cbp, cause); case GTP_CREATE_PDP_REQ: return create_pdp_conf(pdp, cbp, cause); case GTP_DELETE_PDP_REQ: @@ -579,6 +602,7 @@ int sgsn_gtp_init(struct sgsn_instance *sgi) /* Register callbackcs with libgtp */ gtp_set_cb_delete_context(gsn, cb_delete_context); gtp_set_cb_conf(gsn, cb_conf); + gtp_set_cb_recovery(gsn, cb_recovery); gtp_set_cb_data_ind(gsn, cb_data_ind); gtp_set_cb_unsup_ind(gsn, cb_unsup_ind); gtp_set_cb_extheader_ind(gsn, cb_extheader_ind);