[PFCP] SGWU/UPF Restoration (#2223)

TS23.007 17.4.1
19A PFCP based restart procedures

After a PFCP entity has restarted, it shall immediately update all local Recovery Time Stamps and shall clear all remote
Recovery Time Stamps. When peer PFCP entities information is available, i.e. when the PFCP Association is still alive,
the restarted PFCP entity shall send its updated Recovery Time Stamps in a Heartbeat Request message to the peer
PFCP entities before initiating any PFCP session signalling.
This commit is contained in:
Sukchan Lee 2023-04-04 21:22:03 +09:00 committed by GitHub
parent 939b311b2d
commit b9a3157467
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 481 additions and 63 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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 */

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*

View File

@ -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;
}

View File

@ -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];

View File

@ -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);

View File

@ -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)

View File

@ -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)); \

View File

@ -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);

View File

@ -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);
}

View File

@ -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) {
/*

View File

@ -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);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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;

View File

@ -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);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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;

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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(&param, 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;

View File

@ -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);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* 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;

View File

@ -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;;
}

View File

@ -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);