From fdea4cc181fe4af7630584db01acff3ebf18cd95 Mon Sep 17 00:00:00 2001 From: Sukchan Lee Date: Wed, 1 Jul 2026 20:05:05 +0900 Subject: [PATCH] [SMF/MME] Recover the IMS PDN on an EPC GTP-U Error Indication 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 --- src/mme/esm-sm.c | 6 +++-- src/mme/mme-context.h | 3 +++ src/mme/mme-path.c | 10 +++++++- src/mme/mme-s11-handler.c | 38 ++++++++++++++++++++++++++++-- src/mme/nas-path.c | 5 ++-- src/mme/nas-path.h | 3 ++- src/smf/n4-handler.c | 49 +++++++++++++++++++++++++++++++++++---- src/smf/pfcp-path.c | 31 +++++++++++++++++++++++++ 8 files changed, 132 insertions(+), 13 deletions(-) diff --git a/src/mme/esm-sm.c b/src/mme/esm-sm.c index 8dc394757..c5e987d8b 100644 --- a/src/mme/esm-sm.c +++ b/src/mme/esm-sm.c @@ -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); } diff --git a/src/mme/mme-context.h b/src/mme/mme-context.h index 68d4bfb63..ad2e63851 100644 --- a/src/mme/mme-context.h +++ b/src/mme/mme-context.h @@ -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 */ diff --git a/src/mme/mme-path.c b/src/mme/mme-path.c index 3661e09f9..95938a90c 100644 --- a/src/mme/mme-path.c +++ b/src/mme/mme-path.c @@ -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); } diff --git a/src/mme/mme-s11-handler.c b/src/mme/mme-s11-handler.c index 94c6ad322..f3bbbab65 100644 --- a/src/mme/mme-s11-handler.c +++ b/src/mme/mme-s11-handler.c @@ -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); } diff --git a/src/mme/nas-path.c b/src/mme/nas-path.c index 4bf60f335..82e6b8c26 100644 --- a/src/mme/nas-path.c +++ b/src/mme/nas-path.c @@ -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; diff --git a/src/mme/nas-path.h b/src/mme/nas-path.h index fc8de96b7..c8460496d 100644 --- a/src/mme/nas-path.h +++ b/src/mme/nas-path.h @@ -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( diff --git a/src/smf/n4-handler.c b/src/smf/n4-handler.c index e0817362a..673c7c8e1 100644 --- a/src/smf/n4-handler.c +++ b/src/smf/n4-handler.c @@ -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); diff --git a/src/smf/pfcp-path.c b/src/smf/pfcp-path.c index d768082a2..0047f20ae 100644 --- a/src/smf/pfcp-path.c +++ b/src/smf/pfcp-path.c @@ -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();