Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion doc/5Gnas.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ Key 5GS NAS messages:

## OAI Implementation Status

The following table lists implemented NAS messages and whether there is an encoder or decoder function, and if a corresponding unit test exists.
The following table lists NAS messages with dedicated `encode_*` / `decode_*` codecs under
`openair3/NAS/NR_UE/5GS/` (`5GMM/MSG`, `5GSM/MSG`). Unit test entries refer to
[`nas_lib_test.c`](../openair3/NAS/NR_UE/5GS/tests/nas_lib_test.c), most are encode/decode round-trips.

| Type | Message | Encoding | Decoding | Unit test |
|-------|-------------------------------------------|----------|----------|------------|
| 5GMM | Service Request | yes | yes | yes |
| 5GMM | Service Accept | yes | yes | yes |
| 5GMM | Service Reject | yes | yes | yes |
| 5GMM | Authentication Failure | yes | yes | yes |
| 5GMM | Authentication Reject | yes | yes | yes |
| 5GMM | Security Mode Reject | yes | yes | yes |
| 5GMM | Identity Request | no | yes | no |
| 5GMM | Authentication Response | yes | no | no |
| 5GMM | Identity Response | yes | no | no |
| 5GMM | Security Mode Complete | yes | no | no |
Expand All @@ -38,6 +44,23 @@ The following table lists implemented NAS messages and whether there is an encod
| 5GSM | PDU Session Establishment Request | yes | no | no |
| 5GSM | PDU Session Establishment Accept | no | yes | no |

### Runtime-handled messages

These network-originated messages are handled in [`nr_nas_msg.c`](../openair3/NAS/NR_UE/nr_nas_msg.c):

* Authentication Request
* Security Mode Command
* Downlink NAS Transport
* Deregistration Accept (UE originating)
* Registration Reject
* PDU Session Establishment Reject

## Integration testing

End-to-end attach can be tested with the [NR UE NAS simulator](../tests/nr-ue-nas-simulator/README.md),
which runs the OAI UE NAS stack and gNB NGAP against the AMF, forwarding NAS PDUs without PHY/MAC/RLC.
Use `nas_lib_test` for isolated codec round-trips.

### Code Structure

[openair3/NAS/NR_UE/nr_nas_msg.c](../openair3/NAS/NR_UE/nr_nas_msg.c):
Expand Down
55 changes: 46 additions & 9 deletions openair3/NAS/NR_UE/nr_nas_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,38 @@ static int fill_imeisv(FGSMobileIdentity *mi, const uicc_t *uicc)
return 19;
}

/** @brief Fill requested 5GS Mobile Identity (TS 24.501, 9.11.3.4) according to identity type.
*
* @param mobile_identity Pointer to the mobile identity structure to fill
* @param nas Pointer to the UE NAS context
* @param identitytype Type of identity to fill
* @return Encoded identity size in bytes, 0 on failure
*/
static int nas_fill_5gs_mobile_identity(FGSMobileIdentity *mobile_identity, const nr_ue_nas_t *nas, uint8_t identitytype)
{
switch (identitytype) {
case FGS_MOBILE_IDENTITY_SUCI:
return fill_suci(mobile_identity, nas->uicc);
case FGS_MOBILE_IDENTITY_5G_GUTI:
if (!nas->guti) {
LOG_W(NAS, "Cannot build Identity Response with 5G-GUTI: UE has no valid GUTI\n");
return 0;
}
return fill_guti(mobile_identity, nas->guti);
case FGS_MOBILE_IDENTITY_IMEISV:
return fill_imeisv(mobile_identity, nas->uicc);
case FGS_MOBILE_IDENTITY_5GS_TMSI:
if (!nas->guti) {
LOG_W(NAS, "Cannot build Identity Response with 5G-S-TMSI: UE has no valid GUTI\n");
return 0;
}
return fill_fgstmsi(&mobile_identity->stmsi, nas->guti);
default:
LOG_W(NAS, "Cannot build Identity Response: requested identity type %d is unsupported\n", identitytype);
return 0;
}
}

void transferRES(uint8_t ck[16], uint8_t ik[16], uint8_t *input, uint8_t rand[16], uint8_t *output, plmn_id_t *plmn_id)
{
uint8_t S[100] = {0};
Expand Down Expand Up @@ -1116,7 +1148,8 @@ void generateServiceRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas)
}
}

static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t identitytype, uicc_t *uicc)
/** @brief Build Identity Response according to requested identity type (8.2.22 of 3GPP TS 24.501) */
static void generateIdentityResponse(const nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t identitytype)
{
int size = sizeof(fgmm_msg_header_t);
fgmm_nas_message_plain_t plain = {0};
Expand All @@ -1125,11 +1158,13 @@ static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t
plain.header = set_mm_header(FGS_IDENTITY_RESPONSE, PLAIN_5GS_MSG);
size += sizeof(plain.header);

// set identity response
/** Build Identity Response according to requested identity type: if identity
* is unavailable in UE context (e.g., missing 5G-GUTI), return */
fgmm_identity_response_msg *mm_msg = &plain.mm_msg.fgs_identity_response;
if (identitytype == FGS_MOBILE_IDENTITY_SUCI) {
size += fill_suci(&mm_msg->fgsmobileidentity, uicc);
}
int identity_size = nas_fill_5gs_mobile_identity(&mm_msg->fgsmobileidentity, nas, identitytype);
if (identity_size <= 0)
return;
size += identity_size;

// encode the message
initialNasMsg->nas_data = malloc_or_fail(size * sizeof(*initialNasMsg->nas_data));
Expand Down Expand Up @@ -1161,13 +1196,15 @@ static void handle_identity_request(as_nas_info_t *initialNasMsg, nr_ue_nas_t *n
"Received IDENTITY REQUEST for identity type: %s\n",
print_info(msg.fgsmobileidentity, fgs_identity_type_text, sizeofArray(fgs_identity_type_text)));

if (mm_header.message_type == NAS_SECURITY_UNPROTECTED && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) {
// see 3GPP TS 24.501 4.4.4.2
LOG_E(NAS, "Only SUCI mobile identity is expected in a security-unprotected request\n");
if (mm_header.security_header_type == PLAIN_5GS_MSG && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) {
// TS 24.501, 4.4.4.2: unprotected Identity Request is processable only when the requested identity is SUCI.
LOG_E(NAS, "Drop unprotected Identity Request: SUCI required (TS 24.501 4.4.4.2), got %d\n", msg.fgsmobileidentity);
return;
}

generateIdentityResponse(initialNasMsg, msg.fgsmobileidentity, nas->uicc);
generateIdentityResponse(nas, initialNasMsg, msg.fgsmobileidentity);
if (initialNasMsg->length <= 0)
LOG_W(NAS, "Dropped Identity Response: unable to build Identity Response for identity type %d\n", msg.fgsmobileidentity);
}

static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf)
Expand Down
18 changes: 17 additions & 1 deletion tests/nr-ue-nas-simulator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,23 @@ You can build the tester as follows:

Start the tester with the dedicated configuration file:

openairinterface5g/build$ LD_LIBRARY_PATH=. ./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test -O ../tests/nr-ue-nas-simulator/test.conf
openairinterface5g/build$ ./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test -O ../tests/nr-ue-nas-simulator/test.conf

# Testing NAS procedures

## Registration with unknown 5G-GUTI

Optional `--identity-guti` or `identity-guti = 1` in the config file:
Registration Request is sent with an unknown 5G-GUTI so the AMF sends Identity
Request (SUCI) per TS 23.502 step 6, the simulator then continues the normal
attach flow. From `cmake_targets/ran_build/build`:

```bash
./tests/nr-ue-nas-simulator/nr-ue-nas-simulator-test \
-O ../tests/nr-ue-nas-simulator/test.conf --identity-guti
```

Look for `Unknown-GUTI test:` then `IDENTITY REQUEST` / `IDENTITY RESPONSE` in NAS logs.

# Limitations
- The tester is limited to a fixed flow: Initial Attach -> PDU Session -> Deregistration.
Expand Down
71 changes: 63 additions & 8 deletions tests/nr-ue-nas-simulator/nr-ue-nas-simulator.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
#include "openair2/GNB_APP/gnb_paramdef.h"
#include "openair3/SCTP/sctp_default_values.h"
#include "openair3/NAS/NR_UE/nr_nas_msg.h"
#include "openair3/UTILS/conversions.h"
#include "executables/nr-uesoftmodem.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
#include "SIMULATION/TOOLS/sim.h"
#include <stdlib.h>

RAN_CONTEXT_t RC;
THREAD_STRUCT thread_struct;
Expand Down Expand Up @@ -75,16 +78,55 @@ int nr_rlc_get_available_tx_space(const rnti_t rntiP, const logical_chan_id_t ch

configmodule_interface_t *uniqCfg = NULL;

static int identity_guti = 0;

static paramdef_t nas_sim_params[] = {
BOOLPARAM("identity-guti",
"Registration with random 5G-GUTI to trigger AMF Identity Request (SUCI)\n",
PARAMFLAG_BOOL,
&identity_guti,
0),
};

/** Seed a 5G-GUTI unknown to the AMF (TS 23.502 step 6: AMF should request SUCI) */
static void seed_unknown_guti(nr_ue_nas_t *nas, const plmn_id_t *plmn)
{
nas->guti = calloc_or_fail(1, sizeof(*nas->guti));
Guti5GSMobileIdentity_t *guti = nas->guti;
guti->typeofidentity = FGS_MOBILE_IDENTITY_5G_GUTI;
guti->mccdigit1 = MCC_HUNDREDS(plmn->mcc);
guti->mccdigit2 = MCC_MNC_DECIMAL(plmn->mcc);
guti->mccdigit3 = MCC_MNC_DIGIT(plmn->mcc);
guti->mncdigit1 = MNC_HUNDREDS(plmn->mnc, plmn->mnc_digit_length);
guti->mncdigit2 = MCC_MNC_DECIMAL(plmn->mnc);
guti->mncdigit3 = MCC_MNC_DIGIT(plmn->mnc);
/* OAI CN5G served GUAMI */
guti->amfregionid = 0x01;
guti->amfsetid = 0x001;
guti->amfpointer = 0x01;
fill_random(&guti->tmsi, sizeof(guti->tmsi));
LOG_I(NAS,
"Unknown-GUTI test: Registration Request will use 5G-GUTI MCC=%u%u%u MNC=%u%u AMFRI=%u AMFSI=%u AMFPT=%u TMSI=0x%08x\n",
guti->mccdigit1,
guti->mccdigit2,
guti->mccdigit3,
guti->mncdigit1,
guti->mncdigit2,
guti->amfregionid,
guti->amfsetid,
guti->amfpointer,
guti->tmsi);
}

// Emulate registration request to register UE in the AMF
void send_initial_ue_message(instance_t instance)
{
MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_NAS_FIRST_REQ);

NGAP_NAS_FIRST_REQ(msg_p).gNB_ue_ngap_id = 1; // Simulated UE ID

NGAP_NAS_FIRST_REQ(msg_p).plmn.mcc = mcc;
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc = mnc;
NGAP_NAS_FIRST_REQ(msg_p).plmn.mnc_digit_length = mnc_len;
plmn_id_t plmn = {.mcc = mcc, .mnc = mnc, .mnc_digit_length = mnc_len};
NGAP_NAS_FIRST_REQ(msg_p).plmn = plmn;

NGAP_NAS_FIRST_REQ(msg_p).nr_cell_id = gnb_id;

Expand All @@ -95,16 +137,27 @@ void send_initial_ue_message(instance_t instance)
// serving network name (snn)
// Ideally snn should be filled from SIB1, but we operate at NAS/RRC level and we dont decode SIB1
nr_ue_nas->sn_id = calloc(1, sizeof(plmn_id_t));
nr_ue_nas->sn_id->mcc = mcc;
nr_ue_nas->sn_id->mnc = mnc;
nr_ue_nas->sn_id->mnc_digit_length = mnc_len;
*nr_ue_nas->sn_id = plmn;
if (identity_guti) {
seed_unknown_guti(nr_ue_nas, &plmn);

ngap_ue_identity_t *ue_identity = &NGAP_NAS_FIRST_REQ(msg_p).ue_identity;
const Guti5GSMobileIdentity_t *guti = nr_ue_nas->guti;

ue_identity->presenceMask = NGAP_UE_IDENTITIES_FiveG_s_tmsi | NGAP_UE_IDENTITIES_guami;
ue_identity->s_tmsi.amf_set_id = guti->amfsetid;
ue_identity->s_tmsi.amf_pointer = guti->amfpointer;
ue_identity->s_tmsi.m_tmsi = guti->tmsi;
ue_identity->guami.plmn = plmn;
ue_identity->guami.amf_region_id = guti->amfregionid;
ue_identity->guami.amf_set_id = guti->amfsetid;
ue_identity->guami.amf_pointer = guti->amfpointer;
}
generateRegistrationRequest(&initialNasMsg, nr_ue_nas, false);

NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.buf = initialNasMsg.nas_data;
NGAP_NAS_FIRST_REQ(msg_p).nas_pdu.len = initialNasMsg.length;

NGAP_NAS_FIRST_REQ(msg_p).ue_identity.presenceMask = 0;

itti_send_msg_to_task(TASK_NGAP, instance, msg_p);
}

Expand Down Expand Up @@ -349,6 +402,8 @@ int main(int argc, char **argv)
if ((uniqCfg = load_configmodule(argc, argv, CONFIG_ENABLECMDLINEONLY)) == NULL) {
exit_fun("[SOFTMODEM] Error, configuration module init failed\n");
}
config_get(uniqCfg, nas_sim_params, sizeofArray(nas_sim_params), NULL);

logInit();

nas_init_nrue(NB_UE_INST);
Expand Down
Loading