diff --git a/lib/sbi/context.c b/lib/sbi/context.c index 69c1d6378..bd4349755 100644 --- a/lib/sbi/context.c +++ b/lib/sbi/context.c @@ -2805,6 +2805,92 @@ void ogs_sbi_subscription_data_remove_all_by_nf_instance_id( } } +/* + * Send DELETE requests to NRF for all subscriptions belonging + * to the given NF instance before re-registration. + * + * This prevents subscription accumulation during repeated + * re-registration loops (e.g., heartbeat flapping). + * + * IMPORTANT: + * Local subscription_data MUST NOT be removed here. + * Cleanup is performed asynchronously in the unsubscribe + * response handler once NRF confirms deletion. + */ +void ogs_sbi_subscription_data_delete_and_remove_all_by_nf_instance_id( + const char *nf_instance_id) +{ + ogs_sbi_subscription_data_t *subscription_data = NULL; + + ogs_assert(nf_instance_id); + + ogs_list_for_each( + &ogs_sbi_self()->subscription_data_list, subscription_data) { + + if (!subscription_data->id) { + ogs_error("Skip subscription delete: id is NULL"); + continue; + } + + if (!subscription_data->req_nf_instance_id) { + ogs_error("Skip subscription delete: req_nf_instance_id is NULL"); + continue; + } + + if (!subscription_data->resource_uri) { + ogs_error("Skip subscription delete: resource_uri is NULL"); + continue; + } + + if (strcmp(subscription_data->req_nf_instance_id, + nf_instance_id) != 0) { + ogs_error("Skip subscription delete: nf_instance_id mismatch " + "[target:%s, current:%s]", + subscription_data->req_nf_instance_id, nf_instance_id); + continue; + } + + /* + * Prevent duplicate DELETE transmissions. + * (Simple guard using existing state field or flag placeholder) + */ + if (subscription_data->flags & OGS_SBI_SUBSCRIPTION_DELETE_SENT) { + ogs_debug("[%s] Skip subscription delete: DELETE already sent", + subscription_data->id); + continue; + } + + subscription_data->flags |= OGS_SBI_SUBSCRIPTION_DELETE_SENT; + + /* + * If we have a subscription resource identifier, + * send DELETE to NRF to cleanup remote subscription state. + * + * Typical resource: + * /nnrf-nfm/v1/subscriptions/{subscriptionId} + */ + ogs_info("[%s] Sending NRF subscription DELETE before " + "NF re-registration", subscription_data->id); + + + /* Build DELETE request */ + ogs_nnrf_nfm_send_nf_status_unsubscribe(subscription_data); + + /* + * NOTE: + * Do NOT remove subscription_data here. + * + * Local cleanup is performed in the unsubscribe + * response handler once NRF confirms deletion. + * + * Removing here could lead to: + * - Use-after-free + * - Double free + * - Dangling transaction context + */ + } +} + void ogs_sbi_subscription_data_remove_all(void) { ogs_sbi_subscription_data_t *subscription_data = NULL; diff --git a/lib/sbi/context.h b/lib/sbi/context.h index 3b1ea946a..c4fbfe29a 100644 --- a/lib/sbi/context.h +++ b/lib/sbi/context.h @@ -335,6 +335,9 @@ typedef struct ogs_sbi_subscription_data_s { ogs_timer_t *t_validity; /* check validation */ ogs_timer_t *t_patch; /* for sending PATCH */ +#define OGS_SBI_SUBSCRIPTION_DELETE_SENT (1 << 0) + uint32_t flags; /* Subscription lifecycle flags */ + char *id; /* SubscriptionId */ char *req_nf_instance_id; /* reqNfInstanceId */ OpenAPI_nf_type_e req_nf_type; /* reqNfType */ @@ -640,6 +643,8 @@ void ogs_sbi_subscription_data_remove( ogs_sbi_subscription_data_t *subscription_data); void ogs_sbi_subscription_data_remove_all_by_nf_instance_id( char *nf_instance_id); +void ogs_sbi_subscription_data_delete_and_remove_all_by_nf_instance_id( + const char *nf_instance_id); void ogs_sbi_subscription_data_remove_all(void); ogs_sbi_subscription_data_t *ogs_sbi_subscription_data_find(char *id); diff --git a/lib/sbi/nf-sm.c b/lib/sbi/nf-sm.c index b457e1804..4a4821bf5 100644 --- a/lib/sbi/nf-sm.c +++ b/lib/sbi/nf-sm.c @@ -432,6 +432,20 @@ void ogs_sbi_nf_state_registered(ogs_fsm_t *s, ogs_event_t *e) NF_INSTANCE_ID(ogs_sbi_self()->nf_instance), OpenAPI_nf_type_ToString( NF_INSTANCE_TYPE(ogs_sbi_self()->nf_instance))); + + + /* + * In case of NF re-registration due to heartbeat loss, clear any + * local subscription bookkeeping tied to the current NF instance id. + * + * This prevents unbounded growth of subscription_data entries when + * re-registration and re-subscription loops happen (e.g., docker-compose + * timing/race), which could otherwise exhaust subscription_data_pool. + */ + if (ogs_sbi_self()->nf_instance && ogs_sbi_self()->nf_instance->id) + ogs_sbi_subscription_data_delete_and_remove_all_by_nf_instance_id( + ogs_sbi_self()->nf_instance->id); + OGS_FSM_TRAN(s, &ogs_sbi_nf_state_will_register); break; diff --git a/src/nrf/nnrf-handler.c b/src/nrf/nnrf-handler.c index 5bd912538..29a6362fb 100644 --- a/src/nrf/nnrf-handler.c +++ b/src/nrf/nnrf-handler.c @@ -842,6 +842,13 @@ bool nrf_nnrf_handle_nf_status_unsubscribe( return false; } + ogs_info("[%s] Removing local subscription data after NRF unsubscribe " + "[duration:%lld,validity:%d.%06d]", + subscription_data->id, + (long long)subscription_data->validity_duration, + (int)ogs_time_sec(subscription_data->validity_duration), + (int)ogs_time_usec(subscription_data->validity_duration)); + ogs_assert(subscription_data->id); ogs_sbi_subscription_data_remove(subscription_data);