sbi: avoid duplicate NF status subscriptions and clean up local entries on DELETE

This patch addresses a potential subscription_data pool exhaustion
issue observed during repeated NF re-registration with the NRF.

Two improvements are introduced:

1) Prevent duplicate NF status subscriptions
   Before sending a new NF status subscription request, the code now
   checks whether an equivalent subscription already exists in the
   local subscription_data list. If a matching subscription (based on
   req_nf_instance_id and subscr_cond) is found and it is not already
   marked with DELETE_SENT, the new subscription request is skipped.

   This prevents repeated subscription creation during re-registration
   loops.

2) Ensure local cleanup after DELETE response
   When handling HTTP DELETE responses for NF status subscriptions,
   the local subscription_data entry is now removed regardless of the
   response status. Previously, the entry was only removed on
   HTTP 204 (No Content), which could leave stale entries in the local
   list when the NRF returned other statuses (e.g., 404).

   Keeping stale entries could lead to unbounded growth of
   subscription_data and eventual pool exhaustion.

Additionally, successful DELETE operations are logged to improve
debugging visibility.

This change affects all NF state machines that handle subscription
DELETE responses (AMF, AUSF, BSF, NSSF, PCF, SCP, SEPP, SMF, UDM, UDR,
and AF test code).

Issues: #4207
This commit is contained in:
Sukchan Lee 2026-03-13 16:46:00 +09:00
parent 88116fd1c6
commit 783b1dc26f
12 changed files with 134 additions and 49 deletions

View file

@ -275,6 +275,52 @@ void ogs_sbi_nf_state_will_register(ogs_fsm_t *s, ogs_event_t *e)
}
}
static bool nf_status_subscription_exists(
const char *req_nf_instance_id,
OpenAPI_nf_type_e nf_type,
const char *service_name)
{
ogs_sbi_subscription_data_t *s = NULL;
bool same_nf_type = false;
bool same_service_name = false;
bool nf_type_present = false;
bool service_name_present = false;
ogs_assert(req_nf_instance_id);
nf_type_present = (nf_type != OpenAPI_nf_type_NULL);
service_name_present = (service_name != NULL);
ogs_list_for_each(&ogs_sbi_self()->subscription_data_list, s) {
if (!s->req_nf_instance_id)
continue;
if (strcmp(s->req_nf_instance_id, req_nf_instance_id) != 0)
continue;
if (s->flags & OGS_SBI_SUBSCRIPTION_DELETE_SENT)
continue;
same_nf_type = false;
same_service_name = false;
if (nf_type_present &&
s->subscr_cond.nf_type != OpenAPI_nf_type_NULL &&
s->subscr_cond.nf_type == nf_type)
same_nf_type = true;
if (service_name_present &&
s->subscr_cond.service_name &&
strcmp(s->subscr_cond.service_name, service_name) == 0)
same_service_name = true;
if (same_nf_type || same_service_name)
return true;
}
return false;
}
void ogs_sbi_nf_state_registered(ogs_fsm_t *s, ogs_event_t *e)
{
ogs_sbi_nf_instance_t *nf_instance = NULL;
@ -308,6 +354,15 @@ void ogs_sbi_nf_state_registered(ogs_fsm_t *s, ogs_event_t *e)
ogs_list_for_each(
&ogs_sbi_self()->subscription_spec_list, subscription_spec) {
if (nf_status_subscription_exists(
ogs_sbi_self()->nf_instance->id,
subscription_spec->subscr_cond.nf_type,
subscription_spec->subscr_cond.service_name)) {
ogs_warn("[%s] NF status subscription already exists, skip",
ogs_sbi_self()->nf_instance->id);
continue;
}
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
ogs_sbi_self()->nf_instance->id,

View file

@ -417,14 +417,17 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (sbi_message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
sbi_message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -274,15 +274,17 @@ void ausf_state_operational(ogs_fsm_t *s, ausf_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -293,14 +293,17 @@ void bsf_state_operational(ogs_fsm_t *s, bsf_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -280,15 +280,17 @@ void nssf_state_operational(ogs_fsm_t *s, nssf_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -440,15 +440,17 @@ void pcf_state_operational(ogs_fsm_t *s, pcf_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -226,14 +226,17 @@ void scp_state_operational(ogs_fsm_t *s, scp_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -330,14 +330,17 @@ void sepp_state_operational(ogs_fsm_t *s, sepp_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -903,14 +903,17 @@ void smf_state_operational(ogs_fsm_t *s, smf_event_t *e)
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (sbi_message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
sbi_message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -364,15 +364,17 @@ void udm_state_operational(ogs_fsm_t *s, udm_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT

View file

@ -284,17 +284,20 @@ void udr_state_operational(ogs_fsm_t *s, udr_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status ==
OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT
ogs_error("[%s] Invalid HTTP method [%s]",
subscription_data->id, message.h.method);

View file

@ -262,16 +262,20 @@ void af_state_operational(ogs_fsm_t *s, af_event_t *e)
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT) {
ogs_sbi_subscription_data_remove(subscription_data);
} else {
if (message.res_status == OGS_SBI_HTTP_STATUS_NO_CONTENT)
ogs_info("[%s] Subscription deleted",
subscription_data->id ?
subscription_data->id : "Unknown");
else
ogs_error("[%s] HTTP response error [%d]",
subscription_data->id ?
subscription_data->id : "Unknown",
message.res_status);
}
ogs_sbi_subscription_data_remove(subscription_data);
break;
DEFAULT
ogs_error("Invalid HTTP method [%s]", message.h.method);
ogs_assert_if_reached();