Skip to content

Xn Handover Between Two OCUDU gNBs

Xn Handover Between Two OCUDU gNBs

Two OCUDU gNBs on the same 5G core, joined by an Xn interface, so a UE can be handed over directly between them. The RAN-to-RAN signalling goes over Xn instead of through the AMF. This page covers the ready-to-adapt intra-frequency Xn-handover pair, both ways to trigger a handover, and the serving/neighbour measurements (RSRP, RSRQ, SINR) that are also exported as KPIs over O1.

This feature lives on the xnap_handover branch of OCUDU-RAN; the source document is docs/XN_HANDOVER.md. Related repositories: OCUDU-RAN (the RAN) and OCUDU-O1-Adaptor (the O1 adapter).

How It Works

Xn handover architecture. Two OCUDU gNBs on a shared SD-Core: source gNB-1 (gnb_id 411, PCI 1, NCI 0x66C000) and target gNB-2 (gnb_id 412, PCI 2, NCI 0x670000), each with a CU-CP mobility layer (mobility_manager, cell_meas_manager, inter_cu_handover source/target routines) over the full CU-CP / CU-UP / DU stack. The gNBs are joined by an Xn-C SCTP link and both attach to the same AMF over N2/NGAP; the AMF performs a Path Switch to repoint the downlink GTP-U path after handover. An O1 adapter (NETCONF, WebSocket, PM metrics) connects to the SMO (ONAP Non-RT RIC with a Mobility rApp) and the Near-RT RIC connects over E2. A 5G UE is handed over from Radio Unit 1 to Radio Unit 2. The panel lists the three paths: trigger and decision via handle_handover(), inter-CU handover over Xn with Path Switch, and inter-CU handover over N2/NGAP when there is no Xn peer.
System architecture: two OCUDU gNBs joined by Xn-C over a shared SD-Core, with the CU-CP mobility layer, the AMF Path Switch, and the O1/E2 links to the SMO and RICs.
  • Xn interface. Each gNB's CU-CP opens an SCTP Xn-C link to the peer (cu_cp.xnap.gateways). On startup they exchange XnSetup and learn each other's gNB-ID and served cells.
  • Neighbour relations. cu_cp.mobility.cells lists each gNB's own serving cell and the foreign (peer) cell. For the foreign cell, gnb_id_bit_length and nr_cell_id let the CU-CP compute the peer's gNB-ID and route a handover to the correct Xn peer.
  • Decision and routing. When a handover is requested (manually, or from measurements), the mobility manager looks up the target cell's gNB-ID. If an Xn link to that gNB exists it runs an Xn handover; otherwise it falls back to an N2 (AMF-mediated) handover.
  • Xn handover flow. The source sends HandoverRequest, the target admits and sets up F1/E1 bearers and returns HandoverRequestAcknowledge, the source sends the RRC reconfiguration (Handover Command) to the UE and transfers PDCP SN status over Xn, the target completes and issues an NGAP Path Switch to the AMF to repoint the downlink GTP-U path, and the source UE context is released over Xn.
  • Measurements and KPIs. The UE's RRC MeasurementReports land in the CU-CP cell measurement manager, which can trigger A3 handovers and snapshots each UE's serving and neighbour RSRP/RSRQ/SINR (SSB and CSI-RS) into the CU-CP metrics as cu-cp.meas_reports. That block streams out over the metrics WebSocket and is picked up by the O1 adapter.

Code entry points: lib/xnap/, lib/cu_cp/mobility_manager/, and lib/cu_cp/cell_meas_manager/.

Handover Message Sequence

The message exchange between the source gNB, target gNB, AMF, and UPF, in three phases.

Handover preparation

  1. UE to source gNB: Measurement Control and Reports.
  2. Source gNB: Handover Decision.
  3. Source gNB to target gNB: Handover Request.
  4. Target gNB: Admission Control.
  5. Target gNB to source gNB: Handover Request Acknowledge.

Handover execution

  1. Source gNB to UE: RAN handover initiation. The UE detaches from the old cell and synchronizes to the target cell.
  2. Source gNB to target gNB: Early Status Transfer, then SN Status Transfer. The target buffers user data from the source gNB.
  3. UE to target gNB: RAN handover completion, followed by Handover Success and SN Status Transfer back to the source.

Handover completion

  1. Target gNB to AMF: Path Switch Request.
  2. AMF to UPF: Path Switch in the UPF(s). The UPF sends an End Marker to the source gNB and user data to the target gNB.
  3. AMF to target gNB: Path Switch Request Acknowledge.
  4. Source gNB to target gNB: UE Context Release.

User data then continues via the target gNB.

Configuration

Two example configs (Liteon RU, TDD n78, 100 MHz) live in the configs/ directory:

ConfiggNB
gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.ymlgNB1: gnb_id 411, pci 1, cell 0x66C000
gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.ymlgNB2: gnb_id 412, pci 2, cell 0x670000

Environment-specific values are placeholders you must fill in:

PlaceholderMeaning
x.x.x.x (amf)AMF address and this gNB's N2 bind. Both gNBs point at the same core.
x.x.x.x (xnap)bind_addrs is this gNB's Xn IP; peer_addrs is the other gNB's Xn IP.
x.x.x.x (cu_up.ngu)This gNB's N3 IP. ext_addr is advertised to the UPF and must be reachable (used by Path Switch after handover).
xx:xx:xx:xx:xx:xxRU / DU MAC addresses (ru_ofh.cells).
0000:51:xx.xNIC PCIe addresses; must match the -a <pcie> entries in hal.eal_args.

Key blocks (already wired up in both files):

  • cu_cp.xnap.gateways: the Xn-C SCTP link to the peer gNB.
  • cu_cp.mobility.cells: own serving cell plus the foreign (peer) cell, with gnb_id_bit_length / pci / plmn / tac / ssb_arfcn so the CU-CP can resolve the peer gNB-ID.
  • cu_cp.mobility.report_configs: report_cfg_id 1 (periodical) and report_cfg_id 2 (A3 event-triggered).
  • cell_cfg.ssb.offset_to_point_a + k_ssb + pdcch.common.coreset0_index: manual SSB placement, pinned to the same values on both gNBs so the two cells land at an identical SSB position (required for intra-frequency neighbour measurement).
  • pcap.xnap_enable: true: captures Xn so you can verify handover signalling in Wireshark.

Notes:

  • Manual SSB placement is all-or-nothing: set offset_to_point_a + k_ssb + coreset0_index together, or none (to auto-derive). Both cells here pin the same trio.
  • Each ru_ofh.cells[].network_interface PCIe address must also appear in hal.eal_args as a -a <pcie> entry.

How to Run

Build (on each server)

git clone https://github.com/TOSSI-Foundation/OCUDU-RAN
cd OCUDU-RAN
mkdir build && cd build
cmake -DENABLE_DPDK=ON ..        # DPDK is needed for the Liteon OFH (split 7.2) RU
make -j$(nproc)

Bring up the two gNBs

  1. Start your 5G core (AMF/UPF). Both gNBs register to the same AMF.
  2. gNB1 (server A): sudo ./apps/gnb/gnb -c gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.yml
  3. gNB2 (server B): sudo ./apps/gnb/gnb -c gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.yml
  4. Confirm the Xn (SCTP) link comes up and XnSetup succeeds between the two gNBs.
  5. Attach a UE to gNB1 (or gNB2). Note its C-RNTI (from the gNB console metrics or logs); you need it for a manual handover.

sudo is required for DPDK and real-time scheduling.

Server and network topology of the two-gNB testbed. Host A runs the SD-Core 5G Core; Host B runs OCUDU gNB-1 and Host C runs OCUDU gNB-2; Host D runs the SMO (ONAP / O-RAN SC). Each gNB drives a Liteon Radio Unit over front-haul. The 5G Core reaches both gNBs over NGAP, the SMO reaches both gNBs over O1, and a Fibrolan grandmaster fed by GNSS provides timing to the radio units. The legend marks the front-haul, NGAP, and O1 links.
Physical topology: SD-Core on Host A, the two gNBs on Hosts B and C, the SMO on Host D, two Liteon RUs on front-haul, and a GNSS-disciplined Fibrolan grandmaster for timing.

Testbed

The two cells are deployed in an office lab as an intra-frequency pair so a mobile UE walking between them triggers a handover.

Photograph of the end-to-end testbed in an office lab. A Liteon radio for gNB 1 is mounted on a stand in one bay and a radio for gNB 2 is mounted on a pillar in the adjacent bay, with a person holding the mobile UE (a phone) standing between the two coverage areas.
End-to-end testbed: gNB 1 and gNB 2 radios in adjacent lab bays with a mobile UE moving between their coverage.
Floor plan showing antenna placement and coverage of the two Liteon radios. Gnb 1 radio points at azimuth 170 degrees and Gnb 2 radio points at azimuth 30 degrees; their beam lobes overlap in the middle of the room, which is where the handover occurs as the UE moves through the overlap.
Antenna placement: Gnb 1 at azimuth 170 degrees and Gnb 2 at azimuth 30 degrees, with overlapping coverage in the middle where the handover happens.

Triggering a Handover: Two Ways

Both paths drive the same Xn handover machinery; they differ only in what decides to hand over.

gNB-triggered (manual): the ho command

This is what the shipped configs are set for (cu_cp.mobility.trigger_handover_from_measurements: false). Type the command on the serving gNB's console (stdin):

ho <serving_pci> <rnti> <target_pci> <target_plmn> <target_tac>
  • serving_pci: PCI the UE is currently on (gNB1 = 1, gNB2 = 2).
  • rnti: the UE's C-RNTI, in hex (e.g. 4601).
  • target_pci / target_plmn / target_tac: the destination cell (must be a configured Xn neighbour).

For example, to move a UE (rnti 0x4601) from gNB1 to gNB2, typed on gNB1's console:

ho 1 4601 2 00101 1

The manual ho works regardless of the trigger_handover_from_measurements flag. (Related console commands: cho ... for conditional handover with up to 8 candidates, release ... for RRC release / redirect.)

A3 event-triggered (measurement-based, automatic)

The network hands over on its own when the UE reports a neighbour is better than the serving cell (3GPP A3 event). To enable:

  1. Set cu_cp.mobility.trigger_handover_from_measurements: true.
  2. Keep the A3 report config (report_cfg_id: 2, event_triggered_report_type: a3) and make sure each neighbour relation references it (report_configs: [2]); the shipped configs already do.
  3. Tune the A3 thresholds (recommended, to avoid ping-pong):
    • meas_trigger_quantity_offset_db: how much better (dB) the neighbour must be (e.g. 3).
    • hysteresis_db: extra margin (e.g. 0 to 2).
    • time_to_trigger_ms: how long the condition must hold (e.g. 100).

The shipped configs set these to 0 (fire as soon as neighbour is greater than or equal to serving). Fine for a lab, too twitchy for real deployments.

Flow: the UE sends an RRC MeasurementReport when the A3 condition holds; the CU-CP cell measurement manager picks the strongest neighbour and, because trigger_handover_from_measurements is true, triggers the Xn handover to that neighbour's gNB automatically (no ho command needed).

Measurement-Report KPIs over O1

The same serving/neighbour measurements are exported as PM KPIs:

  • RAN side. The CU-CP emits a cu-cp.meas_reports block (per UE: serving / best-neighbour / neighbour cells, each with SSB and CSI-RS RSRP (dBm), RSRQ (dB), SINR (dB)) in its metrics JSON over the metrics WebSocket. Enable the WebSocket by turning on remote_control in the gNB config (enabled: true, bind_addr, port).
  • O1 adapter side (OCUDU-O1-Adaptor): connects to that WebSocket and flattens the block into PM metrics named cu-cp.meas.ue<ue>.<role>.pci<pci>.<quantity> (role = serving / best_neigh / neigh), pushing them to the SMO alongside the other PM KPIs.

Run the adapter (see the O1-Adaptor repo for full options):

python3 src/o1_adapter --profile gnb --netconf_host <host> --netconf_username <user> --netconf_password <pass>

References