Add NGAP Handover Failure message

This message is sent from the Target NG-RAN to the AMF.

The commit introduces:
* NGAP encoding function
* N2 callback for the target gNB
* handle the failure in RRC, inform NGAP
* send NGAP message to AMF via SCTP

Note: UE context in NGAP is fetched from the amf_ue_ngap_id

Co-authored-by: batuhan duyuler <batuhan.duyuler@firecell.io>
This commit is contained in:
Guido Casati
2025-09-18 11:34:30 +02:00
co-authored by batuhan duyuler
parent 6e4f1b7b31
commit 92abefb815
11 changed files with 122 additions and 1 deletions
+1
View File
@@ -72,6 +72,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_RESP , MESSAGE_PRIORITY_MED, ngap_pdu
MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp) MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp)
MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp) MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp)
MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required)
MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure)
/* NGAP -> RRC messages */ /* NGAP -> RRC messages */
MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas )
+10
View File
@@ -66,6 +66,8 @@
#define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req #define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req
#define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind #define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind
#define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required #define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required
#define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure
#define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req
#define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command
#define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp #define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp
@@ -548,6 +550,14 @@ typedef struct {
source_to_target_transparent_container_t *source2target; source_to_target_transparent_container_t *source2target;
} ngap_handover_required_t; } ngap_handover_required_t;
/* 3GPP TS 38.413 9.2.3.6 */
typedef struct {
// AMF UE NGAP ID (M)
uint64_t amf_ue_ngap_id;
// Cause (M)
ngap_cause_t cause;
} ngap_handover_failure_t;
typedef struct ngap_ue_cap_info_ind_s { typedef struct ngap_ue_cap_info_ind_s {
uint32_t gNB_ue_ngap_id; uint32_t gNB_ue_ngap_id;
byte_array_t ue_radio_cap; byte_array_t ue_radio_cap;
+9 -1
View File
@@ -1063,7 +1063,15 @@ int rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_REQ(MessageDef *msg_p, instance_t in
} }
} }
//----------------------------------------------------------------------------- /** @brief Sends the NG Handover Failure from the Target NG-RAN to the AMF */
void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg)
{
LOG_I(NR_RRC, "Send NG Handover Failure message (amf_ue_ngap_id %ld) with cause %d \n ", msg->amf_ue_ngap_id, msg->cause.value);
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_FAILURE);
NGAP_HANDOVER_FAILURE(msg_p) = *msg;
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
}
/* /*
* Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF. * Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF.
* The gNB should remove all pdu session, NG context, and other context of the UE. * The gNB should remove all pdu session, NG context, and other context of the UE.
+2
View File
@@ -94,4 +94,6 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc,
const nr_neighbour_cell_t *neighbourCellConfiguration, const nr_neighbour_cell_t *neighbourCellConfiguration,
const byte_array_t hoPrepInfo); const byte_array_t hoPrepInfo);
void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg);
#endif #endif
+13
View File
@@ -406,6 +406,19 @@ static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST
return hoPrepInfo; return hoPrepInfo;
} }
/** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF
* that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded).
* This message represents an Unsuccessful Outcome of the Handover Resource Allocation */
void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg)
{
LOG_I(NR_RRC, "Triggering N2 Handover Failure\n");
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, msg);
LOG_I(NR_RRC, "Send UE Context Release for gnb_ue_id %d\n", gnb_ue_id);
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, gnb_ue_id);
rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(rrc->module_id, ue_context_p, msg->cause);
return;
}
/** @brief Trigger N2 handover on source gNB: /** @brief Trigger N2 handover on source gNB:
* 1) Prepare RRC Container with HandoverPreparationInformation message * 1) Prepare RRC Container with HandoverPreparationInformation message
* 2) send NGAP Handover Required message */ * 2) send NGAP Handover Required message */
+2
View File
@@ -53,6 +53,7 @@ typedef struct nr_ho_source_cu {
/* acknowledgement of handover request. buf+len is the RRC Reconfiguration */ /* acknowledgement of handover request. buf+len is the RRC Reconfiguration */
typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue);
typedef struct nr_ho_target_cu { typedef struct nr_ho_target_cu {
/// pointer to the (target) DU structure /// pointer to the (target) DU structure
const nr_rrc_du_container_t *du; const nr_rrc_du_container_t *du;
@@ -78,6 +79,7 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type);
void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du);
void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue);
void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg);
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
gNB_RRC_UE_t *ue, gNB_RRC_UE_t *ue,
+44
View File
@@ -334,6 +334,46 @@ void ngap_gNB_init(void) {
itti_mark_task_ready(TASK_NGAP); itti_mark_task_ready(TASK_NGAP);
} }
static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_failure_t *msg)
{
ngap_gNB_ue_context_t *ue_context_p = NULL;
uint8_t *buffer = NULL;
uint32_t length;
ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance);
DevAssert(msg != NULL);
DevAssert(ngap_gNB_instance_p != NULL);
NGAP_NGAP_PDU_t *pdu = encode_ng_handover_failure(msg);
if (!pdu) {
NGAP_ERROR("Failed to encode NG Handover Failure\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return -1;
}
if (ngap_gNB_encode_pdu(pdu, &buffer, &length) < 0) {
NGAP_ERROR("Failed to encode HANDOVER FAILURE MESSAGE\n");
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return -1;
}
ngap_gNB_amf_data_t *amf = ngap_gNB_get_AMF_from_instance(ngap_gNB_instance_p);
if ((ue_context_p = ngap_get_ue_context_from_amf_ue_ngap_id(msg->amf_ue_ngap_id)) == NULL) {
//The context for this gNB ue ngap id doesn't exist in the map of gNB UEs
NGAP_WARN("Failed to find UE context associated with amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id);
/*In this case there is no ue context since HO REQ might be failed at preprocessing stage itself.
* so sctp message send to AMF based on its assoc_id */
ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, 0);
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu);
return -1;
}
ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, amf->nextstream);
ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, &pdu);
return 0;
}
void *ngap_gNB_process_itti_msg(void *notUsed) { void *ngap_gNB_process_itti_msg(void *notUsed) {
MessageDef *received_msg = NULL; MessageDef *received_msg = NULL;
int result; int result;
@@ -415,6 +455,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) {
} }
break; break;
case NGAP_HANDOVER_FAILURE:
ngap_gNB_handover_failure(instance, &NGAP_HANDOVER_FAILURE(received_msg));
break;
default: default:
NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg));
break; break;
@@ -191,3 +191,36 @@ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg
return pdu; return pdu;
} }
NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg)
{
NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu));
/* Prepare the NGAP message to encode */
pdu->present = NGAP_NGAP_PDU_PR_unsuccessfulOutcome;
asn1cCalloc(pdu->choice.unsuccessfulOutcome, head);
head->procedureCode = NGAP_ProcedureCode_id_HandoverResourceAllocation;
head->criticality = NGAP_Criticality_reject;
head->value.present = NGAP_UnsuccessfulOutcome__value_PR_HandoverFailure;
NGAP_HandoverFailure_t *out = &head->value.choice.HandoverFailure;
// AMF_UE_NGAP_ID (M)
{
asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie);
ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID;
ie->criticality = NGAP_Criticality_reject;
ie->value.present = NGAP_HandoverFailureIEs__value_PR_AMF_UE_NGAP_ID;
asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id);
}
// Cause (M)
{
asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie);
ie->id = NGAP_ProtocolIE_ID_id_Cause;
ie->criticality = NGAP_Criticality_ignore;
ie->value.present = NGAP_HandoverFailureIEs__value_PR_Cause;
encode_ngap_cause(&ie->value.choice.Cause, &msg->cause);
}
return pdu;
}
@@ -27,5 +27,6 @@
#include "ngap_messages_types.h" #include "ngap_messages_types.h"
NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg);
NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg);
#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */
+6
View File
@@ -76,6 +76,12 @@ struct ngap_gNB_ue_context_s *ngap_get_ue_context(uint32_t gNB_ue_ngap_id)
return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp); return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp);
} }
ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id)
{
ngap_gNB_ue_context_t temp = {.amf_ue_ngap_id = amf_ue_ngap_id};
return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp);
}
struct ngap_gNB_ue_context_s *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id) struct ngap_gNB_ue_context_s *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id)
{ {
struct ngap_gNB_ue_context_s *tmp = ngap_get_ue_context(gNB_ue_ngap_id); struct ngap_gNB_ue_context_s *tmp = ngap_get_ue_context(gNB_ue_ngap_id);
+1
View File
@@ -81,6 +81,7 @@ typedef struct ngap_gNB_ue_context_s {
void ngap_store_ue_context(const ngap_gNB_ue_context_t *ue_desc_p); void ngap_store_ue_context(const ngap_gNB_ue_context_t *ue_desc_p);
ngap_gNB_ue_context_t *ngap_get_ue_context(uint32_t gNB_ue_ngap_id); ngap_gNB_ue_context_t *ngap_get_ue_context(uint32_t gNB_ue_ngap_id);
ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id);
ngap_gNB_ue_context_t *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id); ngap_gNB_ue_context_t *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id);
#endif /* NGAP_GNB_UE_CONTEXT_H_ */ #endif /* NGAP_GNB_UE_CONTEXT_H_ */