[SMF/MME] Recover the IMS PDN on an EPC GTP-U Error Indication
Some checks failed
Meson Continuous Integration / Build and Test on Ubuntu Latest (push) Has been cancelled

On a GTP-U Error Indication for an EPC session, the SMF deleted the whole
PFCP session for any Error Indication and never notified the MME. A single
transient, handover-induced Error Indication on a dedicated (voice) bearer
therefore tore down the entire IMS PDN, and because no Delete Bearer
Request was sent to the MME, the MME and UE kept a stale IMS PDN
connection - the UE could not place a new VoLTE call until it re-attached.

SMF (src/smf/n4-handler.c, src/smf/pfcp-path.c):
Map the Error Indication FAR to its bearer and run a PGW-initiated bearer
deactivation instead of a blind session deletion, per 3GPP TS 23.007:
  - dedicated bearer -> deactivate only that bearer (IMS PDN survives);
  - default bearer   -> deactivate the whole PDN connection.
smf_gtp2_send_delete_bearer_request() sets the Linked EBI (default) or the
EPS Bearer ID (dedicated), so the MME deactivates the EPS bearer toward the
UE and stays in sync. The PFCP session is deleted on the Delete Bearer
Response (wait_pfcp_deletion). The default-bearer case uses cause
"Reactivation requested" so the UE re-establishes the PDN.

MME (src/mme/mme-s11-handler.c, mme-path.c, mme-context.h, nas-path.c,
nas-path.h, esm-sm.c):
Map the GTPv2 Cause "Reactivation requested" (#8) on a default-bearer
Delete Bearer Request to NAS ESM cause #39 "reactivation requested"
(TS 29.274 Table C.3) so the UE re-establishes the IMS PDN. The mapping is
restricted to the default bearer (Linked EBI). Carry the ESM cause across
paging in mme_ue->paging.esm_cause so an ECM-IDLE UE also receives it after
paging; previously the post-paging path always used REGULAR_DEACTIVATION.

The TS 29.274 Table C.3 "re-attach required" mapping for the last PDN
connection is intentionally not implemented (Attach without PDN
connectivity and SCEF PDN connections are not supported here); the last
default bearer is deactivated with ESM cause #39 as well.

Issues: #4659
This commit is contained in:
Sukchan Lee 2026-07-01 20:05:05 +09:00
parent 32d140e9a5
commit fdea4cc181
8 changed files with 132 additions and 13 deletions

View file

@ -134,7 +134,8 @@ void esm_state_inactive(ogs_fsm_t *s, mme_event_t *e)
mme_gtp_send_delete_session_request(enb_ue, sgw_ue, sess,
OGS_GTP_DELETE_SEND_DEACTIVATE_BEARER_CONTEXT_REQUEST));
} else {
r = nas_eps_send_deactivate_bearer_context_request(bearer);
r = nas_eps_send_deactivate_bearer_context_request(
bearer, OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
@ -342,7 +343,8 @@ void esm_state_active(ogs_fsm_t *s, mme_event_t *e)
mme_gtp_send_delete_session_request(enb_ue, sgw_ue, sess,
OGS_GTP_DELETE_SEND_DEACTIVATE_BEARER_CONTEXT_REQUEST));
} else {
r = nas_eps_send_deactivate_bearer_context_request(bearer);
r = nas_eps_send_deactivate_bearer_context_request(
bearer, OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}

View file

@ -752,6 +752,7 @@ struct mme_ue_s {
ogs_debug("[%s] Clear Paging Info", (__mME)->imsi_bcd); \
(__mME)->paging.type = 0; \
(__mME)->paging.failed = false; \
(__mME)->paging.esm_cause = OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION; \
} while(0)
#define MME_STORE_PAGING_INFO(__mME, __tYPE, __dATA) \
@ -775,6 +776,8 @@ struct mme_ue_s {
int type;
void *data;
bool failed;
uint8_t esm_cause; /* NAS ESM cause carried across paging to the
* deferred (post-paging) deactivation/detach */
} paging;
/* SGW UE context */

View file

@ -280,7 +280,15 @@ void mme_send_after_paging(mme_ue_t *mme_ue, bool failed)
mme_gtp_send_delete_bearer_response(
bearer, OGS_GTP2_CAUSE_UNABLE_TO_PAGE_UE));
} else {
r = nas_eps_send_deactivate_bearer_context_request(bearer);
/*
* Use the NAS ESM cause carried across paging (stored when the
* Delete Bearer Request was received). This preserves
* "reactivation requested" for an ECM-IDLE UE so the (IMS) PDN
* connection is re-established after paging. See
* mme_s11_handle_delete_bearer_request().
*/
r = nas_eps_send_deactivate_bearer_context_request(
bearer, mme_ue->paging.esm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}

View file

@ -859,7 +859,8 @@ void mme_s11_handle_delete_session_response(
return;
}
r = nas_eps_send_deactivate_bearer_context_request(bearer);
r = nas_eps_send_deactivate_bearer_context_request(
bearer, OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
@ -1336,6 +1337,7 @@ void mme_s11_handle_delete_bearer_request(
{
int r;
uint8_t cause_value = OGS_GTP2_CAUSE_UNDEFINED_VALUE;
ogs_nas_esm_cause_t esm_cause = OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION;
mme_bearer_t *bearer = NULL;
mme_sess_t *sess = NULL;
@ -1438,15 +1440,47 @@ void mme_s11_handle_delete_bearer_request(
ogs_assert(xact->id >= OGS_MIN_POOL_ID && xact->id <= OGS_MAX_POOL_ID);
bearer->delete.xact_id = xact->id;
/*
* 3GPP TS 29.274 Table C.3: map the GTPv2 Cause in the Delete Bearer
* Request to the NAS ESM cause. For the default bearer, "Reactivation
* requested" (GTPv2 cause #8) is mapped to ESM cause #39 "reactivation
* requested" so the UE re-establishes the PDN connection (e.g. the IMS
* PDN for VoLTE). Any other case results in a regular deactivation.
*
* Cause #8 is defined for the default bearer (PGW-initiated default
* bearer deactivation), so the mapping is restricted to the Linked EBI
* (default bearer) case. A Cause #8 carried on a dedicated bearer is
* ignored and that bearer is deactivated normally.
*
* The mapped cause is applied on the ECM-CONNECTED path below, and is
* carried across paging via mme_ue->paging.esm_cause for the ECM-IDLE
* path (see mme-path.c, MME_PAGING_TYPE_DELETE_BEARER).
*
* LIMITATION: Table C.3 also maps "reactivation requested" to the NAS
* "re-attach required" detach type when this is the *last* PDN
* connection in E-UTRAN. That is not implemented: this EPC does not
* support Attach without PDN connectivity or SCEF PDN connections, and
* in a normal deployment a UE keeps at least one other (e.g. internet)
* PDN, so the last-PDN case does not arise here. The last default
* bearer is deactivated with ESM cause #39 as well.
*/
if (req->cause.presence && req->cause.data &&
req->linked_eps_bearer_id.presence) {
ogs_gtp2_cause_t *cause = req->cause.data;
if (cause->value == OGS_GTP2_CAUSE_REACTIVATION_REQUESTED)
esm_cause = OGS_NAS_ESM_CAUSE_REACTIVATION_REQUESTED;
}
if (ECM_IDLE(mme_ue)) {
MME_STORE_PAGING_INFO(mme_ue,
MME_PAGING_TYPE_DELETE_BEARER, bearer->id);
mme_ue->paging.esm_cause = esm_cause;
r = s1ap_send_paging(mme_ue, S1AP_CNDomain_ps);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
} else {
MME_CLEAR_PAGING_INFO(mme_ue);
r = nas_eps_send_deactivate_bearer_context_request(bearer);
r = nas_eps_send_deactivate_bearer_context_request(bearer, esm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}

View file

@ -723,7 +723,8 @@ int nas_eps_send_modify_bearer_context_request(
return rv;
}
int nas_eps_send_deactivate_bearer_context_request(mme_bearer_t *bearer)
int nas_eps_send_deactivate_bearer_context_request(
mme_bearer_t *bearer, ogs_nas_esm_cause_t esm_cause)
{
int rv;
ogs_pkbuf_t *s1apbuf = NULL;
@ -746,7 +747,7 @@ int nas_eps_send_deactivate_bearer_context_request(mme_bearer_t *bearer)
}
esmbuf = esm_build_deactivate_bearer_context_request(
bearer, OGS_NAS_ESM_CAUSE_REGULAR_DEACTIVATION);
bearer, esm_cause);
if (!esmbuf) {
ogs_error("esm_build_deactivate_bearer_context_request() failed");
return OGS_ERROR;

View file

@ -56,7 +56,8 @@ int nas_eps_send_activate_dedicated_bearer_context_request(
void nas_eps_send_activate_all_dedicated_bearers(mme_bearer_t *default_bearer);
int nas_eps_send_modify_bearer_context_request(
mme_bearer_t *bearer, int qos_presence, int tft_presence);
int nas_eps_send_deactivate_bearer_context_request(mme_bearer_t *bearer);
int nas_eps_send_deactivate_bearer_context_request(
mme_bearer_t *bearer, ogs_nas_esm_cause_t esm_cause);
int nas_eps_send_bearer_resource_allocation_reject(
mme_ue_t *mme_ue, uint8_t pti, ogs_nas_esm_cause_t esm_cause);
int nas_eps_send_bearer_resource_modification_reject(

View file

@ -1796,11 +1796,50 @@ uint8_t smf_n4_handle_session_report_request(
/* Error Indication is handled last */
if (report_type.error_indication_report && far) {
if (sess->epc == true) {
ogs_error("[%s:%s] Error Indication from SGW-C",
smf_ue->imsi_bcd, sess->session.name);
ogs_assert(OGS_OK ==
smf_epc_pfcp_send_session_deletion_request(
sess, OGS_INVALID_POOL_ID));
/*
* 3GPP TS 23.007 (GTP-U Error Indication at the PGW):
* - default bearer -> deactivate all bearers of the PDN
* connection (the PDN connection is released).
* - dedicated bearer -> deactivate only that bearer.
*
* The Error Indication Report identifies the broken GTP-U tunnel
* through the remote F-TEID carried in the downlink FAR. Running a
* PGW-initiated bearer deactivation makes the PFCP modification
* response send a Delete Bearer Request to the SGW-C/MME, so the
* MME deactivates the EPS bearer towards the UE (NAS Deactivate
* EPS Bearer Context Request).
*
* Deleting the PFCP session locally (as before) never notified the
* MME, so the UE kept a stale PDN connection: data on other APNs
* still worked, but the UE could not originate a new VoLTE call
* until it re-attached (airplane-mode toggle).
*/
ogs_list_for_each(&sess->bearer_list, bearer) {
if (bearer->dl_far == far)
break;
}
if (!bearer) {
ogs_error("[%s:%s] Error Indication from SGW-U: "
"no bearer found for FAR",
smf_ue->imsi_bcd, sess->session.name);
} else if (bearer == smf_default_bearer_in_sess(sess)) {
ogs_error("[%s:%s] Error Indication from SGW-U "
"[EBI:%d] (default bearer)",
smf_ue->imsi_bcd, sess->session.name, bearer->ebi);
ogs_assert(OGS_OK ==
smf_epc_pfcp_send_deactivation(
sess, OGS_GTP2_CAUSE_REACTIVATION_REQUESTED));
} else {
ogs_error("[%s:%s] Error Indication from SGW-U "
"[EBI:%d] (dedicated bearer)",
smf_ue->imsi_bcd, sess->session.name, bearer->ebi);
ogs_assert(OGS_OK ==
smf_epc_pfcp_send_one_bearer_modification_request(
bearer, OGS_INVALID_POOL_ID,
OGS_PFCP_MODIFY_DL_ONLY|OGS_PFCP_MODIFY_DEACTIVATE,
OGS_NAS_PROCEDURE_TRANSACTION_IDENTITY_UNASSIGNED,
OGS_GTP2_CAUSE_UNDEFINED_VALUE));
}
} else {
ogs_warn("[%s:%s] Error Indication from gNB",
smf_ue->supi, sess->session.name);

View file

@ -1050,6 +1050,37 @@ int smf_epc_pfcp_send_deactivation(smf_sess_t *sess, uint8_t gtp_cause)
}
break;
case OGS_GTP2_CAUSE_REACTIVATION_REQUESTED:
/*
* GTP-U Error Indication on the default bearer (TS 23.007):
* deactivate all bearers of this PDN connection. The PFCP
* modification response then sends a Delete Bearer Request for the
* default bearer (Linked EBI) to the SGW-C/MME.
*
* The "Reactivation requested" cause is used so the MME maps it to
* NAS ESM cause #39 "reactivation requested" (3GPP TS 29.274
* clause 7.2.9.2 and Table C.3), instructing the UE to re-establish
* the PDN connection (e.g. the IMS PDN for VoLTE) instead of just
* deactivating it.
*/
if (ogs_list_first(&sess->bearer_list) == NULL) {
ogs_error("No Bearer List in Session");
return OGS_ERROR;
}
/* Deactivate this PDN connection */
rv = smf_epc_pfcp_send_all_pdr_modification_request(
sess, OGS_INVALID_POOL_ID, NULL,
OGS_PFCP_MODIFY_DL_ONLY|OGS_PFCP_MODIFY_DEACTIVATE,
OGS_NAS_PROCEDURE_TRANSACTION_IDENTITY_UNASSIGNED,
OGS_GTP2_CAUSE_REACTIVATION_REQUESTED);
if (rv != OGS_OK) {
ogs_error("smf_epc_pfcp_send_all_pdr_modification_request() "
"failed");
return OGS_ERROR;
}
break;
default:
ogs_fatal("Invalid GTP-Cause[%d]", gtp_cause);
ogs_assert_if_reached();