SGSN: Implement network-initiated PDP CTX DEACT when GGSN restarts
If the GGSN restarts, its restart counter will increase. We can detect that and accordingly release/delete all PDP contexts for that GGSN.
This commit is contained in:
parent
3357add225
commit
a9b473a3c2
|
@ -4,6 +4,7 @@
|
|||
#include <osmocore/msgb.h>
|
||||
#include <openbsc/gprs_sgsn.h>
|
||||
|
||||
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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue