diff --git a/lib/pfcp/build.c b/lib/pfcp/build.c index 55ed2e4a9..a1b3e15fb 100644 --- a/lib/pfcp/build.c +++ b/lib/pfcp/build.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -36,7 +36,7 @@ ogs_pkbuf_t *ogs_pfcp_build_heartbeat_request(uint8_t type) req = &pfcp_message->pfcp_heartbeat_request; req->recovery_time_stamp.presence = 1; - req->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + req->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; pfcp_message->h.type = type; pkbuf = ogs_pfcp_build_msg(pfcp_message); @@ -64,7 +64,7 @@ ogs_pkbuf_t *ogs_pfcp_build_heartbeat_response(uint8_t type) rsp = &pfcp_message->pfcp_heartbeat_response; rsp->recovery_time_stamp.presence = 1; - rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; pfcp_message->h.type = type; pkbuf = ogs_pfcp_build_msg(pfcp_message); @@ -105,7 +105,7 @@ ogs_pkbuf_t *ogs_pfcp_cp_build_association_setup_request(uint8_t type) req->node_id.len = node_id_len; req->recovery_time_stamp.presence = 1; - req->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + req->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; req->cp_function_features.presence = 1; req->cp_function_features.u8 = ogs_pfcp_self()->cp_function_features.octet5; @@ -153,7 +153,7 @@ ogs_pkbuf_t *ogs_pfcp_cp_build_association_setup_response(uint8_t type, rsp->cause.u8 = cause; rsp->recovery_time_stamp.presence = 1; - rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; rsp->cp_function_features.presence = 1; rsp->cp_function_features.u8 = ogs_pfcp_self()->cp_function_features.octet5; @@ -202,7 +202,7 @@ ogs_pkbuf_t *ogs_pfcp_up_build_association_setup_request(uint8_t type) req->node_id.len = node_id_len; req->recovery_time_stamp.presence = 1; - req->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + req->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; ogs_assert(ogs_pfcp_self()->up_function_features_len); req->up_function_features.presence = 1; @@ -273,7 +273,7 @@ ogs_pkbuf_t *ogs_pfcp_up_build_association_setup_response(uint8_t type, rsp->cause.u8 = cause; rsp->recovery_time_stamp.presence = 1; - rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->pfcp_started; + rsp->recovery_time_stamp.u32 = ogs_pfcp_self()->local_recovery; ogs_assert(ogs_pfcp_self()->up_function_features_len); rsp->up_function_features.presence = 1; diff --git a/lib/pfcp/context.c b/lib/pfcp/context.c index 7646385a6..a76560307 100644 --- a/lib/pfcp/context.c +++ b/lib/pfcp/context.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -44,7 +44,7 @@ void ogs_pfcp_context_init(void) /* Initialize SMF context */ memset(&self, 0, sizeof(ogs_pfcp_context_t)); - self.pfcp_started = ogs_time_ntp32_now(); + self.local_recovery = ogs_time_ntp32_now(); ogs_log_install_domain(&__ogs_pfcp_domain, "pfcp", ogs_core()->log.level); @@ -455,9 +455,6 @@ int ogs_pfcp_context_parse_config(const char *local, const char *remote) uint64_t nr_cell_id[OGS_MAX_NUM_OF_CELL_ID] = {0,}; int num_of_nr_cell_id = 0; - /* full list RR enabled by default */ - uint8_t rr_enable = 1; - if (ogs_yaml_iter_type(&pfcp_array) == YAML_MAPPING_NODE) { memcpy(&pfcp_iter, &pfcp_array, @@ -621,9 +618,6 @@ int ogs_pfcp_context_parse_config(const char *local, const char *remote) } while ( ogs_yaml_iter_type(&nr_cell_id_iter) == YAML_SEQUENCE_NODE); - } else if (!strcmp(pfcp_key, "rr")) { - const char *v = ogs_yaml_iter_value(&pfcp_iter); - if (v) rr_enable = atoi(v); } else ogs_warn("unknown key `%s`", pfcp_key); } @@ -664,7 +658,6 @@ int ogs_pfcp_context_parse_config(const char *local, const char *remote) memcpy(node->nr_cell_id, nr_cell_id, sizeof(node->nr_cell_id)); - node->rr_enable = rr_enable; } while (ogs_yaml_iter_type(&pfcp_array) == YAML_SEQUENCE_NODE); } diff --git a/lib/pfcp/context.h b/lib/pfcp/context.h index dcc6cda1f..a14f0e3c4 100644 --- a/lib/pfcp/context.h +++ b/lib/pfcp/context.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -55,7 +55,7 @@ typedef struct ogs_pfcp_context_s { ogs_sockaddr_t *pfcp_addr; /* PFCP IPv4 Address */ ogs_sockaddr_t *pfcp_addr6; /* PFCP IPv6 Address */ - uint32_t pfcp_started; /* UTC time when the PFCP entity started */ + uint32_t local_recovery; /* UTC time */ /* CP Function Features */ ogs_pfcp_cp_function_features_t cp_function_features; @@ -105,8 +105,8 @@ typedef struct ogs_pfcp_node_s { uint64_t nr_cell_id[OGS_MAX_NUM_OF_CELL_ID]; uint8_t num_of_nr_cell_id; - /* flag to enable/ disable full list RR for this node */ - uint8_t rr_enable; + uint32_t remote_recovery; /* UTC time */ + bool restoration_required; ogs_list_t gtpu_resource_list; /* User Plane IP Resource Information */ diff --git a/lib/pfcp/handler.c b/lib/pfcp/handler.c index be48308b4..356bd8195 100644 --- a/lib/pfcp/handler.c +++ b/lib/pfcp/handler.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -24,7 +24,27 @@ bool ogs_pfcp_handle_heartbeat_request( ogs_pfcp_heartbeat_request_t *req) { int rv; + ogs_assert(node); ogs_assert(xact); + ogs_assert(req); + + if (req->recovery_time_stamp.presence == 0) { + ogs_error("No Recovery Time Stamp"); + return false; + } + + if (node->remote_recovery == 0 || + node->remote_recovery == req->recovery_time_stamp.u32) { + } else if (node->remote_recovery < req->recovery_time_stamp.u32) { + ogs_error("Remote PFCP restarted [%u<%u] in Heartbeat REQ", + node->remote_recovery, req->recovery_time_stamp.u32); + node->restoration_required = true; + } else if (node->remote_recovery > req->recovery_time_stamp.u32) { + ogs_error("Invalid Recovery Time Stamp [%u>%u] in Heartbeat REQ", + node->remote_recovery, req->recovery_time_stamp.u32); + } + + node->remote_recovery = req->recovery_time_stamp.u32; rv = ogs_pfcp_send_heartbeat_response(xact); if (rv != OGS_OK) { @@ -39,9 +59,30 @@ bool ogs_pfcp_handle_heartbeat_response( ogs_pfcp_node_t *node, ogs_pfcp_xact_t *xact, ogs_pfcp_heartbeat_response_t *rsp) { + ogs_assert(node); ogs_assert(xact); + ogs_assert(rsp); + ogs_pfcp_xact_commit(xact); + if (rsp->recovery_time_stamp.presence == 0) { + ogs_error("No Recovery Time Stamp"); + return false; + } + + if (node->remote_recovery == 0 || + node->remote_recovery == rsp->recovery_time_stamp.u32) { + } else if (node->remote_recovery < rsp->recovery_time_stamp.u32) { + ogs_error("Remote PFCP restarted [%u<%u] in Heartbeat RSP", + node->remote_recovery, rsp->recovery_time_stamp.u32); + node->restoration_required = true; + } else if (node->remote_recovery > rsp->recovery_time_stamp.u32) { + ogs_error("Invalid Recovery Time Stamp [%u>%u] in Heartbeat RSP", + node->remote_recovery, rsp->recovery_time_stamp.u32); + } + + node->remote_recovery = rsp->recovery_time_stamp.u32; + ogs_timer_start(node->t_no_heartbeat, ogs_app()->time.message.pfcp.no_heartbeat_duration); diff --git a/lib/pfcp/handler.h b/lib/pfcp/handler.h index f87b789b0..9ad357dca 100644 --- a/lib/pfcp/handler.h +++ b/lib/pfcp/handler.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * diff --git a/lib/pfcp/path.c b/lib/pfcp/path.c index 39ee6feaf..8719afde4 100644 --- a/lib/pfcp/path.c +++ b/lib/pfcp/path.c @@ -193,6 +193,15 @@ int ogs_pfcp_send_heartbeat_response(ogs_pfcp_xact_t *xact) rv = ogs_pfcp_xact_commit(xact); ogs_expect(rv == OGS_OK); + /* + * Force delete the PFCP transaction to check the PFCP recovery timestamp. + * + * Otherwise, duplicated request (lib/pfcp/xact.c:384) prevents the message + * from being passed to the state machine, so the PFCP recovery timestamp + * cannot be delivered in the handler routine. + */ + ogs_pfcp_xact_delete(xact); + return rv; } diff --git a/lib/pfcp/xact.c b/lib/pfcp/xact.c index 9632d43a1..61398f22a 100644 --- a/lib/pfcp/xact.c +++ b/lib/pfcp/xact.c @@ -39,7 +39,6 @@ static ogs_pfcp_xact_t *ogs_pfcp_xact_remote_create( ogs_pfcp_node_t *node, uint32_t sqn); static ogs_pfcp_xact_stage_t ogs_pfcp_xact_get_stage( uint8_t type, uint32_t xid); -static int ogs_pfcp_xact_delete(ogs_pfcp_xact_t *xact); static int ogs_pfcp_xact_update_rx(ogs_pfcp_xact_t *xact, uint8_t type); static void response_timeout(void *data); @@ -772,7 +771,7 @@ static ogs_pfcp_xact_stage_t ogs_pfcp_xact_get_stage(uint8_t type, uint32_t xid) return stage; } -static int ogs_pfcp_xact_delete(ogs_pfcp_xact_t *xact) +int ogs_pfcp_xact_delete(ogs_pfcp_xact_t *xact) { char buf[OGS_ADDRSTRLEN]; diff --git a/lib/pfcp/xact.h b/lib/pfcp/xact.h index bd03a9ab1..bc6a85803 100644 --- a/lib/pfcp/xact.h +++ b/lib/pfcp/xact.h @@ -140,6 +140,8 @@ int ogs_pfcp_xact_update_tx(ogs_pfcp_xact_t *xact, int ogs_pfcp_xact_commit(ogs_pfcp_xact_t *xact); void ogs_pfcp_xact_delayed_commit(ogs_pfcp_xact_t *xact, ogs_time_t duration); +int ogs_pfcp_xact_delete(ogs_pfcp_xact_t *xact); + int ogs_pfcp_xact_receive(ogs_pfcp_node_t *node, ogs_pfcp_header_t *h, ogs_pfcp_xact_t **xact); diff --git a/src/amf/context.c b/src/amf/context.c index 45aeaba08..7430248ef 100644 --- a/src/amf/context.c +++ b/src/amf/context.c @@ -1641,6 +1641,9 @@ void amf_ue_remove(amf_ue_t *amf_ue) ogs_timer_delete(amf_ue->implicit_deregistration.timer); /* Free SBI object memory */ + if (ogs_list_count(&amf_ue->sbi.xact_list)) + ogs_error("UE transaction [%d]", + ogs_list_count(&amf_ue->sbi.xact_list)); ogs_sbi_object_free(&amf_ue->sbi); amf_ue_deassociate(amf_ue); @@ -2079,6 +2082,9 @@ void amf_sess_remove(amf_sess_t *sess) ogs_list_remove(&sess->amf_ue->sess_list, sess); /* Free SBI object memory */ + if (ogs_list_count(&sess->sbi.xact_list)) + ogs_error("Session transaction [%d]", + ogs_list_count(&sess->sbi.xact_list)); ogs_sbi_object_free(&sess->sbi); if (sess->sm_context_ref) diff --git a/src/amf/context.h b/src/amf/context.h index a4203842f..e95db3d4d 100644 --- a/src/amf/context.h +++ b/src/amf/context.h @@ -490,6 +490,19 @@ typedef struct amf_sess_s { bool n1_released; bool n2_released; + /* + * To check if Reactivation Request has been used. + * + * During the PFCP recovery process, + * when a Reactivation Request is sent to PDU session release command, + * the UE simultaneously sends PDU session release complete and + * PDU session establishment request. + * + * In this case, old_gsm_type is PDU session release command and + * current_gsm_type is PDU session establishment request. + */ + uint8_t old_gsm_type, current_gsm_type; + struct { ogs_pkbuf_t *pdu_session_resource_setup_request; ogs_pkbuf_t *pdu_session_resource_modification_command; @@ -762,6 +775,7 @@ amf_sess_t *amf_sess_add(amf_ue_t *amf_ue, uint8_t psi); sbi_object = &sess->sbi; \ ogs_assert(sbi_object); \ \ + ogs_error("AMF_SESS_CLEAR"); \ if (ogs_list_count(&sbi_object->xact_list)) { \ ogs_error("SBI running [%d]", \ ogs_list_count(&sbi_object->xact_list)); \ diff --git a/src/amf/gmm-handler.c b/src/amf/gmm-handler.c index 9f03822e8..50b4c1e69 100644 --- a/src/amf/gmm-handler.c +++ b/src/amf/gmm-handler.c @@ -1099,6 +1099,20 @@ int gmm_handle_ul_nas_transport(amf_ue_t *amf_ue, } } + /* + * To check if Reactivation Request has been used. + * + * During the PFCP recovery process, + * when a Reactivation Request is sent to PDU session release command, + * the UE simultaneously sends PDU session release complete and + * PDU session establishment request. + * + * In this case, old_gsm_type is PDU session release command and + * current_gsm_type is PDU session establishment request. + */ + sess->old_gsm_type = sess->current_gsm_type; + sess->current_gsm_type = gsm_header->message_type; + if (sess->payload_container) ogs_pkbuf_free(sess->payload_container); diff --git a/src/amf/namf-handler.c b/src/amf/namf-handler.c index 5367e259f..77a7d4418 100644 --- a/src/amf/namf-handler.c +++ b/src/amf/namf-handler.c @@ -498,11 +498,15 @@ int amf_namf_callback_handle_sm_context_status( * If NOTIFICATION comes after the CLIENT response is received, * sync is done. So, the session context can be removed. */ + ogs_info("[%s:%d][%d:%d:%s] " + "/namf-callback/v1/{supi}/sm-context-status/{psi}", + amf_ue->supi, sess->psi, + sess->n1_released, sess->n2_released, + OpenAPI_resource_status_ToString(sess->resource_status)); + if (sess->n1_released == true && sess->n2_released == true && sess->resource_status == OpenAPI_resource_status_RELEASED) { - - ogs_debug("[%s:%d] SM context remove", amf_ue->supi, sess->psi); amf_nsmf_pdusession_handle_release_sm_context( sess, AMF_RELEASE_SM_CONTEXT_NO_STATE); } diff --git a/src/amf/nsmf-handler.c b/src/amf/nsmf-handler.c index 7d03c7dec..4a0d436a0 100644 --- a/src/amf/nsmf-handler.c +++ b/src/amf/nsmf-handler.c @@ -565,7 +565,7 @@ int amf_nsmf_pdusession_handle_update_sm_context( * 1. PDUSessionResourceReleaseResponse * 2. /nsmf-pdusession/v1/sm-contexts/{smContextRef}/modify */ - ogs_debug("[%s:%d] Receive Update SM context(N2-RELEASED)", + ogs_info("[%s:%d] Receive Update SM context(N2-RELEASED)", amf_ue->supi, sess->psi); sess->n2_released = true; @@ -577,7 +577,7 @@ int amf_nsmf_pdusession_handle_update_sm_context( * 2. /nsmf-pdusession/v1/sm-contexts/{smContextRef}/modify */ - ogs_debug("[%s:%d] Receive Update SM context(N1-RELEASED)", + ogs_info("[%s:%d] Receive Update SM context(N1-RELEASED)", amf_ue->supi, sess->psi); sess->n1_released = true; @@ -729,11 +729,15 @@ int amf_nsmf_pdusession_handle_update_sm_context( * Remove 'amf_sess_t' context to call * amf_nsmf_pdusession_handle_release_sm_context(). */ + ogs_info("[%s:%d:%d][%d:%d:%s] " + "/nsmf-pdusession/v1/sm-contexts/{smContextRef}/modify", + amf_ue->supi, sess->psi, state, + sess->n1_released, sess->n2_released, + OpenAPI_resource_status_ToString(sess->resource_status)); + if (sess->n1_released == true && sess->n2_released == true && sess->resource_status == OpenAPI_resource_status_RELEASED) { - - ogs_debug("[%s:%d] SM context remove", amf_ue->supi, sess->psi); amf_nsmf_pdusession_handle_release_sm_context( sess, AMF_RELEASE_SM_CONTEXT_NO_STATE); } @@ -844,7 +848,37 @@ int amf_nsmf_pdusession_handle_release_sm_context(amf_sess_t *sess, int state) amf_ue = sess->amf_ue; ogs_assert(amf_ue); - amf_sess_remove(sess); + /* + * To check if Reactivation Request has been used. + * + * During the PFCP recovery process, + * when a Reactivation Request is sent to PDU session release command, + * the UE simultaneously sends PDU session release complete and + * PDU session establishment request. + * + * In this case, old_gsm_type is PDU session release command and + * current_gsm_type is PDU session establishment request. + */ + if (sess->old_gsm_type == OGS_NAS_5GS_PDU_SESSION_RELEASE_COMPLETE && + sess->current_gsm_type == + OGS_NAS_5GS_PDU_SESSION_ESTABLISHMENT_REQUEST) { + ogs_error("[%s:%d] Do not remove Session due to Reactivation-requested", + amf_ue->supi, sess->psi); + + /* Initialize the context instead of using amf_sess_remove() */ + + sess->old_gsm_type = 0; + sess->current_gsm_type = 0; + + sess->n1_released = false; + sess->n2_released = false; + sess->resource_status = OpenAPI_resource_status_NULL; + + } else { + ogs_info("[%s:%d] Release SM Context [state:%d]", + amf_ue->supi, sess->psi, state); + amf_sess_remove(sess); + } if (state == AMF_RELEASE_SM_CONTEXT_REGISTRATION_ACCEPT) { /* diff --git a/src/pcf/context.c b/src/pcf/context.c index 7a6281df6..bbcfcde65 100644 --- a/src/pcf/context.c +++ b/src/pcf/context.c @@ -183,6 +183,9 @@ void pcf_ue_remove(pcf_ue_t *pcf_ue) ogs_fsm_fini(&pcf_ue->sm, &e); /* Free SBI object memory */ + if (ogs_list_count(&pcf_ue->sbi.xact_list)) + ogs_error("UE transaction [%d]", + ogs_list_count(&pcf_ue->sbi.xact_list)); ogs_sbi_object_free(&pcf_ue->sbi); pcf_sess_remove_all(pcf_ue); @@ -293,6 +296,9 @@ void pcf_sess_remove(pcf_sess_t *sess) ogs_fsm_fini(&sess->sm, &e); /* Free SBI object memory */ + if (ogs_list_count(&sess->sbi.xact_list)) + ogs_error("Session transaction [%d]", + ogs_list_count(&sess->sbi.xact_list)); ogs_sbi_object_free(&sess->sbi); pcf_app_remove_all(sess); diff --git a/src/sgwc/pfcp-sm.c b/src/sgwc/pfcp-sm.c index 9d101efa4..af099860e 100644 --- a/src/sgwc/pfcp-sm.c +++ b/src/sgwc/pfcp-sm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -124,6 +124,16 @@ void sgwc_pfcp_state_will_associate(ogs_fsm_t *s, sgwc_event_t *e) ogs_assert(xact); switch (message->h.type) { + case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_request(node, xact, + &message->pfcp_heartbeat_request)); + break; + case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_response(node, xact, + &message->pfcp_heartbeat_response)); + break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_pfcp_cp_handle_association_setup_request(node, xact, &message->pfcp_association_setup_request); @@ -174,6 +184,13 @@ void sgwc_pfcp_state_associated(ogs_fsm_t *s, sgwc_event_t *e) OGS_PORT(&node->addr)); ogs_timer_start(node->t_no_heartbeat, ogs_app()->time.message.pfcp.no_heartbeat_duration); + ogs_assert(OGS_OK == + ogs_pfcp_send_heartbeat_request(node, node_timeout)); + + if (node->restoration_required == true) { + node->restoration_required = false; + ogs_error("PFCP restoration"); + } break; case OGS_FSM_EXIT_SIG: ogs_info("PFCP de-associated [%s]:%d", @@ -200,12 +217,12 @@ void sgwc_pfcp_state_associated(ogs_fsm_t *s, sgwc_event_t *e) switch (message->h.type) { case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_request(node, xact, &message->pfcp_heartbeat_request)); break; case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_response(node, xact, &message->pfcp_heartbeat_response)); break; diff --git a/src/sgwu/context.c b/src/sgwu/context.c index b3ac1aebf..2f1fd4a60 100644 --- a/src/sgwu/context.c +++ b/src/sgwu/context.c @@ -189,7 +189,7 @@ int sgwu_sess_remove(sgwu_sess_t *sess) void sgwu_sess_remove_all(void) { - sgwu_sess_t *sess = NULL, *next = NULL;; + sgwu_sess_t *sess = NULL, *next = NULL; ogs_list_for_each_safe(&self.sess_list, next, sess) { sgwu_sess_remove(sess); diff --git a/src/sgwu/pfcp-sm.c b/src/sgwu/pfcp-sm.c index 2aa6c9843..c28385307 100644 --- a/src/sgwu/pfcp-sm.c +++ b/src/sgwu/pfcp-sm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -20,6 +20,7 @@ #include "pfcp-path.h" #include "sxa-handler.h" +static void pfcp_restoration(ogs_pfcp_node_t *node); static void node_timeout(ogs_pfcp_xact_t *xact, void *data); void sgwu_pfcp_state_initial(ogs_fsm_t *s, sgwu_event_t *e) @@ -120,6 +121,16 @@ void sgwu_pfcp_state_will_associate(ogs_fsm_t *s, sgwu_event_t *e) ogs_assert(xact); switch (message->h.type) { + case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_request(node, xact, + &message->pfcp_heartbeat_request)); + break; + case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_response(node, xact, + &message->pfcp_heartbeat_response)); + break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_pfcp_up_handle_association_setup_request(node, xact, &message->pfcp_association_setup_request); @@ -170,6 +181,14 @@ void sgwu_pfcp_state_associated(ogs_fsm_t *s, sgwu_event_t *e) OGS_PORT(&node->addr)); ogs_timer_start(node->t_no_heartbeat, ogs_app()->time.message.pfcp.no_heartbeat_duration); + ogs_assert(OGS_OK == + ogs_pfcp_send_heartbeat_request(node, node_timeout)); + + if (node->restoration_required == true) { + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } break; case OGS_FSM_EXIT_SIG: ogs_info("PFCP de-associated [%s]:%d", @@ -188,14 +207,60 @@ void sgwu_pfcp_state_associated(ogs_fsm_t *s, sgwu_event_t *e) switch (message->h.type) { case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_request(node, xact, &message->pfcp_heartbeat_request)); + if (node->restoration_required == true) { + if (node->t_association) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + OGS_FSM_TRAN(s, sgwu_pfcp_state_will_associate); + } else { + + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_response(node, xact, &message->pfcp_heartbeat_response)); + if (node->restoration_required == true) { + if (node->t_association) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + OGS_FSM_TRAN(s, sgwu_pfcp_state_will_associate); + } else { + + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_warn("PFCP[REQ] has already been associated [%s]:%d", @@ -288,6 +353,19 @@ void sgwu_pfcp_state_exception(ogs_fsm_t *s, sgwu_event_t *e) } } +static void pfcp_restoration(ogs_pfcp_node_t *node) +{ + sgwu_sess_t *sess = NULL, *next = NULL; + + ogs_list_for_each_safe(&sgwu_self()->sess_list, next, sess) { + if (node == sess->pfcp_node) { + ogs_info("DELETION: F-SEID[UP:0x%lx CP:0x%lx]", + (long)sess->sgwu_sxa_seid, (long)sess->sgwc_sxa_f_seid.seid); + sgwu_sess_remove(sess); + } + } +} + static void node_timeout(ogs_pfcp_xact_t *xact, void *data) { int rv; diff --git a/src/smf/pfcp-sm.c b/src/smf/pfcp-sm.c index 470e861d8..4dab934be 100644 --- a/src/smf/pfcp-sm.c +++ b/src/smf/pfcp-sm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -22,6 +22,7 @@ #include "n4-handler.h" +static void reselect_upf(ogs_pfcp_node_t *node); static void node_timeout(ogs_pfcp_xact_t *xact, void *data); void smf_pfcp_state_initial(ogs_fsm_t *s, smf_event_t *e) @@ -126,6 +127,16 @@ void smf_pfcp_state_will_associate(ogs_fsm_t *s, smf_event_t *e) ogs_assert(xact); switch (message->h.type) { + case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_request(node, xact, + &message->pfcp_heartbeat_request)); + break; + case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_response(node, xact, + &message->pfcp_heartbeat_response)); + break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_pfcp_cp_handle_association_setup_request(node, xact, &message->pfcp_association_setup_request); @@ -176,6 +187,14 @@ void smf_pfcp_state_associated(ogs_fsm_t *s, smf_event_t *e) OGS_PORT(&node->addr)); ogs_timer_start(node->t_no_heartbeat, ogs_app()->time.message.pfcp.no_heartbeat_duration); + ogs_assert(OGS_OK == + ogs_pfcp_send_heartbeat_request(node, node_timeout)); + + if (node->restoration_required == true) { + /* PFCP Restoration is being performed after PFCP association */ + node->restoration_required = false; + ogs_error("PFCP restoration"); + } break; case OGS_FSM_EXIT_SIG: ogs_info("PFCP de-associated [%s]:%d", @@ -204,14 +223,57 @@ void smf_pfcp_state_associated(ogs_fsm_t *s, smf_event_t *e) switch (message->h.type) { case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_request(node, xact, &message->pfcp_heartbeat_request)); + if (node->restoration_required == true) { + if (node->t_association) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + OGS_FSM_TRAN(s, smf_pfcp_state_will_associate); + } else { + + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_response(node, xact, &message->pfcp_heartbeat_response)); + if (node->restoration_required == true) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + if (node->t_association) { + OGS_FSM_TRAN(s, smf_pfcp_state_will_associate); + } else { + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_warn("PFCP[REQ] has already been associated [%s]:%d", @@ -347,34 +409,48 @@ void smf_pfcp_state_exception(ogs_fsm_t *s, smf_event_t *e) } } -static void node_timeout(ogs_pfcp_xact_t *xact, void *data) +static void reselect_upf(ogs_pfcp_node_t *node) { - int r, rv; - - smf_event_t *e = NULL; - uint8_t type; - ogs_pfcp_node_t *node = NULL; + int r; smf_ue_t *smf_ue = NULL, *next_ue = NULL;; + ogs_pfcp_node_t *iter = NULL; - ogs_assert(xact); - type = xact->seq[0].type; + ogs_assert(node); - switch (type) { - case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: - node = data; - ogs_assert(node); + if (node->restoration_required == true) { + ogs_error("UPF has already been restarted"); + return; + } - ogs_list_for_each_safe(&smf_self()->smf_ue_list, next_ue, smf_ue) { - smf_sess_t *sess = NULL, *next_sess = NULL;; - ogs_assert(smf_ue); + ogs_list_for_each(&ogs_pfcp_self()->pfcp_peer_list, iter) { + if (iter == node) + continue; + if (OGS_FSM_CHECK(&iter->sm, smf_pfcp_state_associated)) + break; + } - ogs_list_for_each_safe(&smf_ue->sess_list, next_sess, sess) { - ogs_assert(sess); + if (iter == NULL) { + ogs_error("No UPF avaiable"); + return; + } + + ogs_list_for_each_safe(&smf_self()->smf_ue_list, next_ue, smf_ue) { + smf_sess_t *sess = NULL, *next_sess = NULL;; + ogs_assert(smf_ue); + + ogs_list_for_each_safe(&smf_ue->sess_list, next_sess, sess) { + ogs_assert(sess); + if (sess->epc) { + ogs_error("[%s:%s] EPC restoration is not implemented", + smf_ue->imsi_bcd, sess->session.name); + } else { ogs_assert(sess->sm_context_ref); if (node == sess->pfcp_node) { smf_npcf_smpolicycontrol_param_t param; + ogs_info("[%s:%d] SMF-initiated Deletion", + smf_ue->supi, sess->psi); ogs_assert(sess->sm_context_ref); memset(¶m, 0, sizeof(param)); r = smf_sbi_discover_and_send( @@ -387,6 +463,35 @@ static void node_timeout(ogs_pfcp_xact_t *xact, void *data) } } } + } +} + +static void node_timeout(ogs_pfcp_xact_t *xact, void *data) +{ + int rv; + + smf_event_t *e = NULL; + uint8_t type; + + ogs_assert(xact); + type = xact->seq[0].type; + + switch (type) { + case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: + ogs_assert(data); + + + /* + * The code below is not secure. + * Session does not differentiate between EPC and 5GC. + * And, it does not check whether there are other PFCP Nodes. + * + * So, UPF redundancy will be implemented later. + * + * We plan to do this again after testing with restoration first + * in case peer PFCP restarts. + */ + reselect_upf(data); e = smf_event_new(SMF_EVT_N4_NO_HEARTBEAT); e->pfcp_node = data; diff --git a/src/upf/context.c b/src/upf/context.c index 92bdc1418..f0caba97e 100644 --- a/src/upf/context.c +++ b/src/upf/context.c @@ -237,7 +237,7 @@ int upf_sess_remove(upf_sess_t *sess) void upf_sess_remove_all(void) { - upf_sess_t *sess = NULL, *next = NULL;; + upf_sess_t *sess = NULL, *next = NULL; ogs_list_for_each_safe(&self.sess_list, next, sess) { upf_sess_remove(sess); diff --git a/src/upf/pfcp-sm.c b/src/upf/pfcp-sm.c index e850ce3cb..787ac2dca 100644 --- a/src/upf/pfcp-sm.c +++ b/src/upf/pfcp-sm.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2019-2023 by Sukchan Lee * * This file is part of Open5GS. * @@ -25,6 +25,7 @@ #include "pfcp-path.h" #include "n4-handler.h" +static void pfcp_restoration(ogs_pfcp_node_t *node); static void node_timeout(ogs_pfcp_xact_t *xact, void *data); void upf_pfcp_state_initial(ogs_fsm_t *s, upf_event_t *e) @@ -125,6 +126,16 @@ void upf_pfcp_state_will_associate(ogs_fsm_t *s, upf_event_t *e) ogs_assert(xact); switch (message->h.type) { + case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_request(node, xact, + &message->pfcp_heartbeat_request)); + break; + case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: + ogs_expect(true == + ogs_pfcp_handle_heartbeat_response(node, xact, + &message->pfcp_heartbeat_response)); + break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_pfcp_up_handle_association_setup_request(node, xact, &message->pfcp_association_setup_request); @@ -175,6 +186,14 @@ void upf_pfcp_state_associated(ogs_fsm_t *s, upf_event_t *e) OGS_PORT(&node->addr)); ogs_timer_start(node->t_no_heartbeat, ogs_app()->time.message.pfcp.no_heartbeat_duration); + ogs_assert(OGS_OK == + ogs_pfcp_send_heartbeat_request(node, node_timeout)); + + if (node->restoration_required == true) { + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } break; case OGS_FSM_EXIT_SIG: ogs_info("PFCP de-associated [%s]:%d", @@ -193,14 +212,59 @@ void upf_pfcp_state_associated(ogs_fsm_t *s, upf_event_t *e) switch (message->h.type) { case OGS_PFCP_HEARTBEAT_REQUEST_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_request(node, xact, &message->pfcp_heartbeat_request)); + if (node->restoration_required == true) { + if (node->t_association) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + OGS_FSM_TRAN(s, upf_pfcp_state_will_associate); + } else { + + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_HEARTBEAT_RESPONSE_TYPE: - ogs_assert(true == + ogs_expect(true == ogs_pfcp_handle_heartbeat_response(node, xact, &message->pfcp_heartbeat_response)); + if (node->restoration_required == true) { + /* + * node->t_association that the PFCP entity attempts an association. + * + * In this case, even if Remote PFCP entity is restarted, + * PFCP restoration must be performed after PFCP association. + * + * Otherwise, Session related PFCP cannot be initiated + * because the peer PFCP entity is in a de-associated state. + */ + if (node->t_association) { + OGS_FSM_TRAN(s, upf_pfcp_state_will_associate); + } else { + /* + * If the peer PFCP entity is performing the association, + * Restoration can be performed immediately. + */ + pfcp_restoration(node); + node->restoration_required = false; + ogs_error("PFCP restoration"); + } + } break; case OGS_PFCP_ASSOCIATION_SETUP_REQUEST_TYPE: ogs_warn("PFCP[REQ] has already been associated [%s]:%d", @@ -293,6 +357,23 @@ void upf_pfcp_state_exception(ogs_fsm_t *s, upf_event_t *e) } } +static void pfcp_restoration(ogs_pfcp_node_t *node) +{ + upf_sess_t *sess = NULL, *next = NULL; + char buf1[OGS_ADDRSTRLEN]; + char buf2[OGS_ADDRSTRLEN]; + + ogs_list_for_each_safe(&upf_self()->sess_list, next, sess) { + if (node == sess->pfcp_node) { + ogs_info("DELETION: F-SEID[UP:0x%lx CP:0x%lx] IPv4[%s] IPv6[%s]", + (long)sess->upf_n4_seid, (long)sess->smf_n4_f_seid.seid, + sess->ipv4 ? OGS_INET_NTOP(&sess->ipv4->addr, buf1) : "", + sess->ipv6 ? OGS_INET6_NTOP(&sess->ipv6->addr, buf2) : ""); + upf_sess_remove(sess); + } + } +} + static void node_timeout(ogs_pfcp_xact_t *xact, void *data) { int rv; diff --git a/tests/app/5gc-init.c b/tests/app/5gc-init.c index fcd6f1201..9589115d1 100644 --- a/tests/app/5gc-init.c +++ b/tests/app/5gc-init.c @@ -82,7 +82,7 @@ int app_initialize(const char *const argv[]) * * If freeDiameter is not used, it uses a delay of less than 4 seconds. */ - ogs_msleep(500); + ogs_msleep(300); return OGS_OK;; } diff --git a/tests/handover/5gc-n2-test.c b/tests/handover/5gc-n2-test.c index 6c73f4118..e560c8353 100644 --- a/tests/handover/5gc-n2-test.c +++ b/tests/handover/5gc-n2-test.c @@ -806,6 +806,9 @@ static void direct_complete_func(abts_case *tc, void *data) rv = testgnb_ngap_send(ngap2, sendbuf); ABTS_INT_EQUAL(tc, OGS_OK, rv); + /* Waiting for N4 */ + ogs_msleep(100); + /* Receive End Mark */ recvbuf = test_gtpu_read(gtpu1); ABTS_PTR_NOTNULL(tc, recvbuf); @@ -923,6 +926,9 @@ static void direct_complete_func(abts_case *tc, void *data) rv = testgnb_ngap_send(ngap1, sendbuf); ABTS_INT_EQUAL(tc, OGS_OK, rv); + /* Waiting for N4 */ + ogs_msleep(100); + /* Receive End Mark */ recvbuf = test_gtpu_read(gtpu2); ABTS_PTR_NOTNULL(tc, recvbuf); @@ -1838,6 +1844,9 @@ static void indirect_complete_func(abts_case *tc, void *data) rv = testgnb_ngap_send(ngap2, sendbuf); ABTS_INT_EQUAL(tc, OGS_OK, rv); + /* Waiting for N4 */ + ogs_msleep(100); + /* Receive End Mark */ recvbuf = test_gtpu_read(gtpu1); ABTS_PTR_NOTNULL(tc, recvbuf); @@ -1983,6 +1992,9 @@ static void indirect_complete_func(abts_case *tc, void *data) rv = testgnb_ngap_send(ngap1, sendbuf); ABTS_INT_EQUAL(tc, OGS_OK, rv); + /* Waiting for N4 */ + ogs_msleep(100); + /* Receive End Mark */ recvbuf = test_gtpu_read(gtpu2); ABTS_PTR_NOTNULL(tc, recvbuf); @@ -2488,6 +2500,9 @@ static void indirect_cancel_func(abts_case *tc, void *data) rv = testgnb_ngap_send(ngap2, sendbuf); ABTS_INT_EQUAL(tc, OGS_OK, rv); + /* Waiting for N4 */ + ogs_msleep(100); + /* Receive End Mark */ recvbuf = test_gtpu_read(gtpu1); ABTS_PTR_NOTNULL(tc, recvbuf);