Add NGAP Handover Request decoding and RRC handler

This commit introduces the NG Mobility Management Procedure known as
Handover Resource Allocation. The inbound message is sent by the AMF
and is known as Handover Request.

Major changes:

(0) Add Handover Resource Allocation initiating message decoding
    * add case for NGAP initiating message and unsuccessful message decoding
(1) handle Handover Request on the target NG-RAN
(2) decode NG Handover Request message
(3) process payload, e.g. create UE context,
    store IDs, set UP security and trigger bearer setup

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 92abefb815
commit 461cfb7911
16 changed files with 525 additions and 104 deletions
+1
View File
@@ -83,6 +83,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_REQ , MESSAGE_PRIORITY_MED, ngap_pd
MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_REQ , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_req_t , ngap_pdusession_modify_req )
MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdusession_release_command_t , ngap_pdusession_release_command)
MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command)
MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request)
/* NGAP <-> RRC messages (can be initiated either by MME or gNB) */
MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req)
+59 -9
View File
@@ -67,6 +67,7 @@
#define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind
#define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required
#define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure
#define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request
#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
@@ -229,6 +230,15 @@ typedef struct ngap_mobility_restriction_s{
plmn_id_t serving_plmn;
}ngap_mobility_restriction_t;
/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
typedef struct {
uint8_t nb_qos;
pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE];
pdu_session_type_t pdu_session_type;
// UPF endpoint of the NG-U (N3) transport bearer
gtpu_tunnel_t n3_incoming;
} pdusession_transfer_t;
typedef enum pdusession_qosflow_mapping_ind_e{
QOSFLOW_MAPPING_INDICATION_UL = 0,
QOSFLOW_MAPPING_INDICATION_DL = 1,
@@ -558,6 +568,55 @@ typedef struct {
ngap_cause_t cause;
} ngap_handover_failure_t;
typedef struct {
// Next-Hop NH
uint8_t next_hop[SECURITY_KEY_LENGTH];
// Next Hop Chaining Count
uint8_t next_hop_chain_count;
} ngap_security_context_t;
/* Handover Request (3GPP TS 38.413 9.2.3.4)
PDU Session Resource Setup Item */
typedef struct {
// PDU Session ID
uint8_t pdusession_id;
pdu_session_type_t pdu_session_type;
// S-NSSAI
nssai_t nssai;
// Handover Required Transfer
pdusession_transfer_t pdusessionTransfer;
} ho_request_pdusession_t;
/* 3GPP TS 38.413 9.2.3.4 */
typedef struct {
// AMF UE NGAP ID
uint64_t amf_ue_ngap_id;
// Handover Type
ho_type_t ho_type;
// Cause
ngap_cause_t cause;
// UE Aggregate Maximum Bit Rate
ngap_ambr_t ue_ambr;
// UE Security Capabilities
ngap_security_capabilities_t security_capabilities;
// Security Context
ngap_security_context_t security_context;
// PDU Session Resource Setup List
uint8_t nb_of_pdusessions;
ho_request_pdusession_t pduSessionResourceSetupList[NGAP_MAX_PDU_SESSION];
// Allowed NSSAI
uint8_t nb_allowed_nssais;
nssai_t allowed_nssai[8];
// Source to Target Transparent Container contents
uint64_t nr_cell_id;
byte_array_t ue_ho_prep_info;
byte_array_t ue_cap;
// Mobility Restriction List
ngap_mobility_restriction_t *mobility_restriction;
// GUAMI
nr_guami_t guami;
} ngap_handover_request_t;
typedef struct ngap_ue_cap_info_ind_s {
uint32_t gNB_ue_ngap_id;
byte_array_t ue_radio_cap;
@@ -628,15 +687,6 @@ typedef struct ngap_downlink_nas_s {
byte_array_t nas_pdu;
} ngap_downlink_nas_t;
/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */
typedef struct {
uint8_t nb_qos;
pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE];
pdu_session_type_t pdu_session_type;
// UPF endpoint of the NG-U (N3) transport bearer
gtpu_tunnel_t n3_incoming;
} pdusession_transfer_t;
/* PDU Session Resource Setup/Modify Request Item */
typedef struct {
int pdusession_id;
+1
View File
@@ -127,5 +127,6 @@ f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qo
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration);
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish);
void activate_srb(gNB_RRC_UE_t *UE, int srb_id);
#endif
+6 -1
View File
@@ -354,7 +354,7 @@ void freeSRBlist(NR_SRB_ToAddModList_t *l)
ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l);
}
static void activate_srb(gNB_RRC_UE_t *UE, int srb_id)
void activate_srb(gNB_RRC_UE_t *UE, int srb_id)
{
AssertFatal(srb_id == 1 || srb_id == 2, "handling only SRB 1 or 2\n");
if (UE->Srb[srb_id].Active == 1) {
@@ -2944,6 +2944,11 @@ void *rrc_gnb_task(void *args_p) {
rrc_gNB_process_PAGING_IND(msg_p, instance);
break;
case NGAP_HANDOVER_REQUEST:
rrc_gNB_process_Handover_Request(RC.nrrrc[instance], instance, &NGAP_HANDOVER_REQUEST(msg_p));
rrc_gNB_free_Handover_Request(&NGAP_HANDOVER_REQUEST(msg_p)); // Free transfered NG message
break;
default:
LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p);
break;
+95 -6
View File
@@ -83,6 +83,8 @@
#include "rrc_messages_types.h"
#include "s1ap_messages_types.h"
#include "uper_encoder.h"
#include "rrc_gNB_mobility.h"
#include "rrc_gNB_du.h"
#ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
@@ -163,18 +165,24 @@ static nr_guami_t get_guami(const uint32_t amf_Id, const plmn_id_t plmn)
return guami;
}
/** @brief Copy NGAP PDU Session Transfer item to RRC pdusession_t struct */
static void cp_pdusession_transfer_to_pdusession(pdusession_t *dst, const pdusession_transfer_t *src)
{
dst->nb_qos = src->nb_qos;
for (uint8_t i = 0; i < src->nb_qos && i < QOSFLOW_MAX_VALUE; ++i) {
dst->qos[i] = src->qos[i];
}
dst->pdu_session_type = src->pdu_session_type;
dst->n3_incoming = src->n3_incoming;
}
/** @brief Copy NGAP PDU Session Resource item to RRC pdusession_t struct */
static void cp_pdusession_resource_item_to_pdusession(pdusession_t *dst, const pdusession_resource_item_t *src)
{
dst->pdusession_id = src->pdusession_id;
dst->nas_pdu = src->nas_pdu;
dst->nb_qos = src->pdusessionTransfer.nb_qos;
for (uint8_t i = 0; i < src->pdusessionTransfer.nb_qos && i < QOSFLOW_MAX_VALUE; ++i) {
dst->qos[i] = src->pdusessionTransfer.qos[i];
}
dst->pdu_session_type = src->pdusessionTransfer.pdu_session_type;
dst->n3_incoming = src->pdusessionTransfer.n3_incoming;
dst->nssai = src->nssai;
cp_pdusession_transfer_to_pdusession(dst, &src->pdusessionTransfer);
}
/**
@@ -1072,6 +1080,87 @@ void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure
itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p);
}
/** @brief Process NG Handover Request message (8.4.2.2 3GPP TS 38.413) */
int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg)
{
LOG_I(NR_RRC, "Received Handover Request (on PCI=%lu) \n", msg->nr_cell_id);
struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id);
if (du == NULL) {
/* Cell Not Found! Return HO Request Failure*/
LOG_E(RRC, "Failed to process Handover Request: no DU found with PCI=%lu \n", msg->nr_cell_id);
ngap_handover_failure_t fail = {
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
.cause.value = NGAP_CAUSE_RADIO_NETWORK_RADIO_RESOURCES_NOT_AVAILABLE,
};
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
return -1;
}
// Create UE context
sctp_assoc_t curr_assoc_id = du->assoc_id;
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(curr_assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX);
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
// allocate context for target
if (UE->ho_context != NULL) {
LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", UE->rrc_ue_id);
return -1;
}
UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET);
// Store IDs in UE context
UE->amf_ue_ngap_id = msg->amf_ue_ngap_id;
UE->ue_guami = msg->guami;
UE->ho_context->target->ue_ho_prep_info = copy_byte_array(msg->ue_ho_prep_info);
// store the received UE Security Capabilities in the UE context
FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer);
UE->ue_cap_buffer = copy_byte_array(msg->ue_cap);
/* store the received Security Context in the UE context
and take it into use as defined in TS 33.501 */
set_UE_security_algos(rrc, UE, &msg->security_capabilities);
UE->nh_ncc = msg->security_context.next_hop_chain_count;
memcpy(UE->nh, msg->security_context.next_hop, SECURITY_KEY_LENGTH);
// Reset KgNB
memset(UE->kgnb, 0, SECURITY_KEY_LENGTH);
// Derive KgNB*
const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info;
uint32_t ssb_arfcn = get_ssb_arfcn(du);
nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, UE->nh, UE->kgnb);
UE->as_security_active = true;
// Activate SRBs
activate_srb(UE, SRB1);
activate_srb(UE, SRB2);
nr_rrc_pdcp_config_security(UE, false);
// Copy PDU Session Resource Setup item to RRC struct and do PDU Session Resource Setup procedure
pdusession_t to_setup = {
.nssai = msg->pduSessionResourceSetupList->nssai,
.pdu_session_type = msg->pduSessionResourceSetupList->pdu_session_type ,
.pdusession_id = msg->pduSessionResourceSetupList->pdusession_id,
};
cp_pdusession_transfer_to_pdusession(&to_setup, &msg->pduSessionResourceSetupList->pdusessionTransfer);
if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, &to_setup, msg->ue_ambr.br_dl)) {
LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n");
ngap_handover_failure_t fail = {
.amf_ue_ngap_id = msg->amf_ue_ngap_id,
.cause.type = NGAP_CAUSE_RADIO_NETWORK,
.cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM,
};
rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail);
}
return 0;
}
void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg)
{
free_byte_array(msg->ue_cap);
free_byte_array(msg->ue_ho_prep_info);
free(msg->mobility_restriction);
}
/*
* 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.
+3
View File
@@ -96,4 +96,7 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc,
void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg);
int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg);
void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg);
#endif
+2
View File
@@ -57,6 +57,8 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type)
static void free_ho_ctx(nr_handover_context_t *ho_ctx)
{
free(ho_ctx->source);
if (ho_ctx->target)
FREE_AND_ZERO_BYTE_ARRAY(ho_ctx->target->ue_ho_prep_info);
free(ho_ctx->target);
free(ho_ctx);
}
+2
View File
@@ -63,6 +63,8 @@ typedef struct nr_ho_target_cu {
uint32_t du_ue_id;
/// new (target) RNTI (as for du_ue_id)
rnti_t new_rnti;
/// Handover Preparation Buffer
byte_array_t ue_ho_prep_info;
/// function pointer to announce handover request acknowledgment
ho_req_ack_t ho_req_ack;
/// function pointer to announce handover success