Merge remote-tracking branch 'origin/add-ci-ue-rrc-idle-test' into integration_2025_w22 (!3277)

Add CI test to force RRC IDLE and new connection setup with 5G-S-TMSI

This MR is a follow up to !2725 (merged)..

The primary goal is to introduce a CI test that enforces the following behavior:
- Forces the UE to enter RRC_IDLE state.
- Validates the UE's re-connection procedure using the 5G-S-TMSI as the UE
  identity.
This commit is contained in:
Jaroslava Fiedlerova
2025-05-27 19:30:08 +02:00
17 changed files with 336 additions and 86 deletions
@@ -34,9 +34,24 @@
020002 020002
030001 030001
030002 030002
040002
000004
020001
040002
000004
020001
040002
000004
020001
040002
000004
020001
040002
000004
020001
040001 040001
000004 000004
020002 020001
100001 100001
222222 222222
</TestCaseRequestedList> </TestCaseRequestedList>
@@ -82,7 +97,7 @@
<testCase id="000004"> <testCase id="000004">
<class>IdleSleep</class> <class>IdleSleep</class>
<desc>Sleep</desc> <desc>Sleep</desc>
<idle_sleep_time_in_sec>5</idle_sleep_time_in_sec> <idle_sleep_time_in_sec>1</idle_sleep_time_in_sec>
</testCase> </testCase>
<testCase id="020001"> <testCase id="020001">
@@ -92,8 +107,8 @@
<nodes>cacofonix</nodes> <nodes>cacofonix</nodes>
<svr_id>rfsim5g_ext_dn</svr_id> <svr_id>rfsim5g_ext_dn</svr_id>
<svr_node>cacofonix</svr_node> <svr_node>cacofonix</svr_node>
<ping_args> -c 20 -i0.2</ping_args> <ping_args> -c 20 -i0.5</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold> <ping_packetloss_threshold>80</ping_packetloss_threshold>
</testCase> </testCase>
<testCase id="020002"> <testCase id="020002">
@@ -139,13 +154,19 @@
<command_fail>yes</command_fail> <command_fail>yes</command_fail>
</testCase> </testCase>
<testCase id="040002">
<class>Custom_Command</class>
<desc>Force RRC_IDLE (UE ID 0)</desc>
<node>cacofonix</node>
<command>echo ciUE force_RRC_IDLE | nc 192.168.71.150 8091</command>
<command_fail>yes</command_fail>
</testCase>
<testCase id="100001"> <testCase id="100001">
<class>Undeploy_Object</class> <class>Undeploy_Object</class>
<always_exec>true</always_exec> <always_exec>true</always_exec>
<desc>Undeploy all OAI 5G stack</desc> <desc>Undeploy all OAI 5G stack</desc>
<yaml_path>ci-scripts/yaml_files/5g_rfsimulator_24prb</yaml_path> <yaml_path>ci-scripts/yaml_files/5g_rfsimulator_24prb</yaml_path>
<d_retx_th>1,0,0,0</d_retx_th>
<u_retx_th>1,0,0,0</u_retx_th>
<eNB_instance>0</eNB_instance> <eNB_instance>0</eNB_instance>
<eNB_serverId>0</eNB_serverId> <eNB_serverId>0</eNB_serverId>
</testCase> </testCase>
+15
View File
@@ -25,6 +25,8 @@ extern "C" {
#include "common/config/config_userapi.h" #include "common/config/config_userapi.h"
void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree); void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree);
void config_check_valptr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int elt_sz, int nb_elt); void config_check_valptr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int elt_sz, int nb_elt);
#include "../config_userapi.h"
#include "../config_common.h"
} }
#include <cstring> #include <cstring>
#include <string> #include <string>
@@ -98,6 +100,9 @@ void SetDefault(configmodule_interface_t *cfg, paramdef_t *param)
param->uptr = (uint32_t *)param->defintarrayval; param->uptr = (uint32_t *)param->defintarrayval;
} }
break; break;
case TYPE_IPV4ADDR:
config_setdefault_ipv4addr(cfg, param, param->defstrval);
break;
default: default:
AssertFatal(false, "Unhandled type %d", param->type); AssertFatal(false, "Unhandled type %d", param->type);
} }
@@ -186,6 +191,16 @@ void SetNonDefault(configmodule_interface_t *cfg, const YAML::Node &node, paramd
} }
break; break;
} }
case TYPE_IPV4ADDR: {
std::string ipv4addr = node[optname].as<std::string>();
char* ipv4addr_ptr = strdup(ipv4addr.c_str());
int rst = config_assign_ipv4addr(cfg, param, ipv4addr_ptr);
free(ipv4addr_ptr);
if (rst < 0) {
fprintf(stderr,"[LIBCONFIG] %s not valid for %s \n", ipv4addr.c_str(), optname.c_str());
}
break;
}
default: default:
AssertFatal(false, "Unhandled type %d", param->type); AssertFatal(false, "Unhandled type %d", param->type);
} }
+1
View File
@@ -10,3 +10,4 @@ configure_file(test_string.yaml test_string.yaml COPYONLY)
configure_file(test_list_of_mappings.yml test_list_of_mappings.yml COPYONLY) configure_file(test_list_of_mappings.yml test_list_of_mappings.yml COPYONLY)
configure_file(test_int_array.yaml test_int_array.yaml COPYONLY) configure_file(test_int_array.yaml test_int_array.yaml COPYONLY)
configure_file(test_read_mapping_as_list.yaml test_read_mapping_as_list.yaml COPYONLY) configure_file(test_read_mapping_as_list.yaml test_read_mapping_as_list.yaml COPYONLY)
configure_file(test_ipv4.yaml test_ipv4.yaml COPYONLY)
+4
View File
@@ -0,0 +1,4 @@
test:
ipv4_1: 0.0.0.0
ipv4_2: 192.168.0.1
#ipv4_3: check default
@@ -391,6 +391,36 @@ TEST(yaml_config, test_read_mapping_as_list) {
end_configmodule(cfg); end_configmodule(cfg);
} }
TEST(yaml_config, test_read_ipv4) {
configmodule_interface_t *cfg = static_cast<configmodule_interface_t*>(calloc(1, sizeof(*cfg)));
cfg->cfgP[0] = strdup("test_ipv4.yaml");
EXPECT_EQ(config_yaml_init(cfg), 0);
paramdef_t p = {};
p.type = TYPE_IPV4ADDR;
strncpy(p.optname, "ipv4_1", sizeof(p.optname) - 1);
uint32_t addr = 0;
p.uptr = &addr;
p.defstrval = nullptr;
char prefix[] = "test";
config_yaml_get(cfg, &p, 1, prefix);
printf("%x\n", addr);
strncpy(p.optname, "ipv4_2", sizeof(p.optname) - 1);
config_yaml_get(cfg, &p, 1, prefix);
printf("%x\n", addr);
p.defstrval = strdup("10.0.0.1");
strncpy(p.optname, "ipv4_3", sizeof(p.optname) - 1);
config_yaml_get(cfg, &p, 1, prefix);
printf("%x\n", addr);
config_yaml_end(cfg);
free(cfg->cfgP[0]);
end_configmodule(cfg);
free(p.defstrval);
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
logInit(); logInit();
+9
View File
@@ -102,12 +102,21 @@ int force_crnti_ra(char *buf, int debug, telnet_printfunc_t prnt)
return 0; return 0;
} }
static int force_deregistration(char *buf, int debug, telnet_printfunc_t prnt)
{
MessageDef *msg = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_DEREGISTRATION_REQ);
NAS_DEREGISTRATION_REQ(msg).cause = AS_DETACH;
itti_send_msg_to_task(TASK_NAS_NRUE, 0, msg);
return 0;
}
/* Telnet shell command definitions */ /* Telnet shell command definitions */
static telnetshell_cmddef_t cicmds[] = { static telnetshell_cmddef_t cicmds[] = {
{"sync_state", "[UE_ID(int,opt)]", get_sync_state}, {"sync_state", "[UE_ID(int,opt)]", get_sync_state},
{"force_rlf", "", force_rlf}, {"force_rlf", "", force_rlf},
{"force_RRC_IDLE", "", force_RRC_IDLE}, {"force_RRC_IDLE", "", force_RRC_IDLE},
{"force_crnti_ra", "", force_crnti_ra}, {"force_crnti_ra", "", force_crnti_ra},
{"deregistration", "", force_deregistration},
{"", "", NULL}, {"", "", NULL},
}; };
+19 -8
View File
@@ -168,10 +168,14 @@ static bool change_interface_state(int sock_fd, const char *ifn, if_action_t if_
struct ifreq ifr = {0}; struct ifreq ifr = {0};
strncpy(ifr.ifr_name, ifn, sizeof(ifr.ifr_name)); strncpy(ifr.ifr_name, ifn, sizeof(ifr.ifr_name));
/* get flags of this interface: see netdevice(7) */ /* get flags of this interface: see netdevice(7) */
bool success = ioctl(sock_fd, SIOCGIFFLAGS, (caddr_t)&ifr) == 0; bool success = ioctl(sock_fd, SIOCGIFFLAGS, (caddr_t)&ifr) == 0;
if (!success) if (!success && if_action == INTERFACE_DOWN && errno == ENODEV) {
goto fail_interface_state; LOG_W(OIP, "trying to remove non-existant device %s\n", ifn);
return true;
}
LOG_D(OIP, "Got flags for %s: 0x%x (action: %s)\n", ifn, ifr.ifr_flags, action);
if (if_action == INTERFACE_UP) { if (if_action == INTERFACE_UP) {
ifr.ifr_flags |= IFF_UP | IFF_NOARP | IFF_POINTOPOINT; ifr.ifr_flags |= IFF_UP | IFF_NOARP | IFF_POINTOPOINT;
@@ -183,6 +187,8 @@ static bool change_interface_state(int sock_fd, const char *ifn, if_action_t if_
success = ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t)&ifr) == 0; success = ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t)&ifr) == 0;
if (!success) if (!success)
goto fail_interface_state; goto fail_interface_state;
LOG_D(OIP, "Interface %s successfully set %s\n", ifn, action);
return true; return true;
fail_interface_state: fail_interface_state:
@@ -201,10 +207,6 @@ bool tun_config(const char* ifname, const char *ipv4, const char *ipv6)
return false; return false;
} }
if (!change_interface_state(sock_fd, ifname, INTERFACE_DOWN)) {
close(sock_fd);
return false;
}
bool success = true; bool success = true;
if (ipv4 != NULL) if (ipv4 != NULL)
success = setInterfaceParameter(sock_fd, ifname, AF_INET, ipv4, SIOCSIFADDR); success = setInterfaceParameter(sock_fd, ifname, AF_INET, ipv4, SIOCSIFADDR);
@@ -272,8 +274,17 @@ int tun_generate_ue_ifname(char *ifname, int instance_id, int pdu_session_id)
return snprintf(ifname, IFNAMSIZ, "%s%d%s", "oaitun_ue", instance_id + 1, pdu_session_id == -1 ? "" : pdu_session_string); return snprintf(ifname, IFNAMSIZ, "%s%d%s", "oaitun_ue", instance_id + 1, pdu_session_id == -1 ? "" : pdu_session_string);
} }
void tun_destroy(const char *dev, int fd) { void tun_destroy(const char *dev)
if (change_interface_state(fd, dev, INTERFACE_DOWN) == 0) { {
// Use a new socket for ioctl operations
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
LOG_E(UTIL, "Failed to create socket for interface teardown: %d, %s\n", errno, strerror(errno));
return;
}
bool success = change_interface_state(fd, dev, INTERFACE_DOWN);
if (success) {
LOG_I(UTIL, "Interface %s is now down.\n", dev); LOG_I(UTIL, "Interface %s is now down.\n", dev);
} else { } else {
LOG_E(UTIL, "Could not bring interface %s down.\n", dev); LOG_E(UTIL, "Could not bring interface %s down.\n", dev);
+1 -2
View File
@@ -84,8 +84,7 @@ int tun_alloc(const char *dev);
/*! /*!
* \brief This function destroys the TUN interface * \brief This function destroys the TUN interface
* \param[in] dev name of the interface * \param[in] dev name of the interface
* \param[in] fd file descriptor of the allocated interface
*/ */
void tun_destroy(const char *dev, int fd); void tun_destroy(const char *dev);
#endif /*TUN_IF_H_*/ #endif /*TUN_IF_H_*/
+3
View File
@@ -2562,6 +2562,9 @@ void nr_rrc_going_to_IDLE(NR_UE_RRC_INST_t *rrc,
nr_pdcp_release_drb(rrc->ue_id, i); nr_pdcp_release_drb(rrc->ue_id, i);
} }
} }
// stop TUN threads and clean up SDAP entities
nr_sdap_delete_ue_entities(rrc->ue_id);
for (int i = 1; i < NR_NUM_SRB; i++) { for (int i = 1; i < NR_NUM_SRB; i++) {
if (rrc->Srb[i] != RB_NOT_PRESENT) { if (rrc->Srb[i] != RB_NOT_PRESENT) {
rrc->Srb[i] = RB_NOT_PRESENT; rrc->Srb[i] = RB_NOT_PRESENT;
+20 -3
View File
@@ -121,8 +121,21 @@ static void *sdap_tun_read_thread(void *arg)
while (!entity->stop_thread) { while (!entity->stop_thread) {
len = read(entity->pdusession_sock, &rx_buf, NL_MAX_PAYLOAD); len = read(entity->pdusession_sock, &rx_buf, NL_MAX_PAYLOAD);
if (len == -1) { if (len == -1) {
LOG_E(PDCP, "could not read(): errno %d %s\n", errno, strerror(errno)); if (errno == EINTR)
return NULL; continue; // interrupted system call
if (errno == EBADF || errno == EINVAL) {
LOG_I(SDAP, "Socket closed, exiting TUN read thread for UE %ld, PDU session %d\n", entity->ue_id, entity->pdusession_id);
break;
}
LOG_E(PDCP, "read() failed: errno %d (%s)\n", errno, strerror(errno));
break;
}
if (len == 0) {
LOG_W(SDAP, "TUN socket returned EOF - exiting thread\n");
break;
} }
LOG_D(SDAP, "read data of size %d\n", len); LOG_D(SDAP, "read data of size %d\n", len);
@@ -194,6 +207,10 @@ void remove_ip_if(nr_sdap_entity_t *entity)
DevAssert(entity != NULL); DevAssert(entity != NULL);
// Stop the read thread // Stop the read thread
entity->stop_thread = true; entity->stop_thread = true;
// Close the socket: read() will get EBADF and exit
close(entity->pdusession_sock);
int ret = pthread_join(entity->pdusession_thread, NULL); int ret = pthread_join(entity->pdusession_thread, NULL);
AssertFatal(ret == 0, "pthread_join() failed, errno: %d, %s\n", errno, strerror(errno)); AssertFatal(ret == 0, "pthread_join() failed, errno: %d, %s\n", errno, strerror(errno));
// Bring down the IP interface // Bring down the IP interface
@@ -205,5 +222,5 @@ void remove_ip_if(nr_sdap_entity_t *entity)
} else { } else {
tun_generate_ue_ifname(ifname, entity->ue_id, entity->pdusession_id != default_pdu ? entity->pdusession_id : -1); tun_generate_ue_ifname(ifname, entity->ue_id, entity->pdusession_id != default_pdu ? entity->pdusession_id : -1);
} }
tun_destroy(ifname, entity->pdusession_sock); tun_destroy(ifname);
} }
+1 -1
View File
@@ -329,7 +329,7 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
LOG_D(SDAP, "RX Entity offset : %d\n", offset); LOG_D(SDAP, "RX Entity offset : %d\n", offset);
if (len != size-offset) if (len != size-offset)
LOG_E(SDAP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__); LOG_E(SDAP, "write failed to fd %d! errno = %s\n", entity->pdusession_sock, strerror(errno));
} }
} }
@@ -11,3 +11,5 @@ target_link_libraries(fgs_5gmm_ies_lib
PUBLIC nas_common_ies_lib PUBLIC nas_common_ies_lib
PRIVATE log_headers PRIVATE log_headers
) )
target_include_directories(fgs_5gmm_ies_lib PRIVATE ../../)
@@ -30,8 +30,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include "OctetString.h" #include "OctetString.h"
#include "fgs_nas_utils.h"
#ifndef FGS_MOBILE_IDENTITY_H_ #ifndef FGS_MOBILE_IDENTITY_H_
#define FGS_MOBILE_IDENTITY_H_ #define FGS_MOBILE_IDENTITY_H_
@@ -129,15 +129,22 @@ typedef struct {
uint8_t macaddr; uint8_t macaddr;
} Macaddr5GSMobileIdentity_t; } Macaddr5GSMobileIdentity_t;
#define FOREACH_IDENTITY_TYPE(TYPE_DEF) \
TYPE_DEF(FGS_MOBILE_IDENTITY_NOIDENTITY, 0) \
TYPE_DEF(FGS_MOBILE_IDENTITY_SUCI, 1) \
TYPE_DEF(FGS_MOBILE_IDENTITY_5G_GUTI, 2) \
TYPE_DEF(FGS_MOBILE_IDENTITY_IMEI, 3) \
TYPE_DEF(FGS_MOBILE_IDENTITY_5GS_TMSI, 4) \
TYPE_DEF(FGS_MOBILE_IDENTITY_IMEISV, 5) \
TYPE_DEF(FGS_MOBILE_IDENTITY_MAC_ADDR, 6) \
TYPE_DEF(FGS_MOBILE_IDENTITY_EUI64, 7)
typedef enum { FOREACH_IDENTITY_TYPE(TO_ENUM) } fgs_identity_type_t;
static const text_info_t fgs_identity_type_text[] = {FOREACH_IDENTITY_TYPE(TO_TEXT)};
// clang-format off // clang-format off
typedef union FGSMobileIdentity_tag { typedef union FGSMobileIdentity_tag {
#define FGS_MOBILE_IDENTITY_NOIDENTITY 0b000
#define FGS_MOBILE_IDENTITY_SUCI 0b001
#define FGS_MOBILE_IDENTITY_5G_GUTI 0b010
#define FGS_MOBILE_IDENTITY_IMEI 0b011
#define FGS_MOBILE_IDENTITY_5GS_TMSI 0b100
#define FGS_MOBILE_IDENTITY_IMEISV 0b101
#define FGS_MOBILE_IDENTITY_MAC_ADDR 0b110
NoIdentity5GSMobileIdentity_t noidentity; NoIdentity5GSMobileIdentity_t noidentity;
Suci5GSMobileIdentity_t suci; Suci5GSMobileIdentity_t suci;
Guti5GSMobileIdentity_t guti; Guti5GSMobileIdentity_t guti;
@@ -5,6 +5,7 @@ add_library(fgs_5gmm_lib OBJECT
fgmm_service_reject.c fgmm_service_reject.c
RegistrationRequest.c RegistrationRequest.c
RegistrationAccept.c RegistrationAccept.c
fgmm_identity_request.c
FGSIdentityResponse.c FGSIdentityResponse.c
FGSAuthenticationResponse.c FGSAuthenticationResponse.c
FGSNASSecurityModeComplete.c FGSNASSecurityModeComplete.c
@@ -0,0 +1,37 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include "fgmm_identity_request.h"
#include <string.h>
#include "FGSMobileIdentity.h"
int decode_fgs_identity_request(fgs_identity_request_msg_t *msg, const byte_array_t *buffer)
{
if (buffer->len < 1) {
PRINT_NAS_ERROR("Failed to decode NAS Identity Request: invalid buffer length %ld\n", buffer->len);
return -1;
}
// Bits 3-1 (least significant 3 bits)
msg->fgsmobileidentity = buffer->buf[0] & 0x07;
return 1;
}
@@ -0,0 +1,36 @@
/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#ifndef FGS_IDENTITY_REQUEST_H
#define FGS_IDENTITY_REQUEST_H
#include <stdint.h>
#include <stdbool.h>
#include "common/utils/ds/byte_array.h"
#include "FGSMobileIdentity.h"
typedef struct {
fgs_identity_type_t fgsmobileidentity;
} fgs_identity_request_msg_t;
int decode_fgs_identity_request(fgs_identity_request_msg_t *msg, const byte_array_t *buffer);
#endif /* FGS_IDENTITY_REQUEST_H */
+115 -58
View File
@@ -50,6 +50,7 @@
#include "SORTransparentContainer.h" #include "SORTransparentContainer.h"
#include "FGSIdentityResponse.h" #include "FGSIdentityResponse.h"
#include "fgmm_authentication_request.h" #include "fgmm_authentication_request.h"
#include "fgmm_identity_request.h"
#include "T.h" #include "T.h"
#include "aka_functions.h" #include "aka_functions.h"
#include "assertions.h" #include "assertions.h"
@@ -69,6 +70,7 @@
#include "fgs_nas_utils.h" #include "fgs_nas_utils.h"
#include "fgmm_service_accept.h" #include "fgmm_service_accept.h"
#include "fgmm_service_reject.h" #include "fgmm_service_reject.h"
#include "ds/byte_array.h"
#define MAX_NAS_UE 4 #define MAX_NAS_UE 4
@@ -76,10 +78,10 @@ static nr_ue_nas_t nr_ue_nas[MAX_NAS_UE] = {0};
#define FOREACH_STATE(TYPE_DEF) \ #define FOREACH_STATE(TYPE_DEF) \
TYPE_DEF(NAS_SECURITY_NO_SECURITY_CONTEXT, 0) \ TYPE_DEF(NAS_SECURITY_NO_SECURITY_CONTEXT, 0) \
TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 1) \ TYPE_DEF(NAS_SECURITY_UNPROTECTED, 1) \
TYPE_DEF(NAS_SECURITY_UNPROTECTED, 2) \ TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 2) \
TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 3) \ TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 3) \
TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 4) \ TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 4) \
TYPE_DEF(NAS_SECURITY_BAD_INPUT, 5) TYPE_DEF(NAS_SECURITY_BAD_INPUT, 5)
const char *nr_release_cause_desc[] = {"RRC_CONNECTION_FAILURE", "RRC_RESUME_FAILURE", "OTHER"}; const char *nr_release_cause_desc[] = {"RRC_CONNECTION_FAILURE", "RRC_RESUME_FAILURE", "OTHER"};
@@ -129,34 +131,69 @@ static const char *print_info(uint8_t id, const text_info_t *array, uint8_t arra
return "N/A"; return "N/A";
} }
static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_buffer, int pdu_length) /** @brief Check whether a message belongs to the list of messages that
* are allowed to be integrity unprotected (4.4.4.2 3GPP TS 24.501) */
static bool unprotected_allowed(byte_array_t buffer, fgs_nas_msg_t msg_type)
{
switch (msg_type) {
case FGS_IDENTITY_REQUEST: // check on SUCI done in the handler
case FGS_AUTHENTICATION_REQUEST:
case FGS_AUTHENTICATION_RESULT:
case FGS_AUTHENTICATION_REJECT:
case FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING: // for non switch off: deregistration type IE set to NORMAL_DEREGISTRATION
return true;
case FGS_REGISTRATION_REJECT:
case FGS_SERVICE_REJECT:
// unprotected if the 5GMM cause is not #76
return buffer.buf[4] != Not_authorized_for_this_CAG_or_authorized_for_CAG_cells_only;
default:
return false;
}
}
static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t buffer, fgs_nas_msg_t msg_type)
{ {
if (nas->security_container == NULL) if (nas->security_container == NULL)
return NAS_SECURITY_NO_SECURITY_CONTEXT; return NAS_SECURITY_NO_SECURITY_CONTEXT;
/* header is 7 bytes, require at least one byte of payload */
if (pdu_length < 8)
return NAS_SECURITY_BAD_INPUT;
switch (pdu_buffer[1]) { if (buffer.len < sizeof(fgmm_msg_header_t)) {
LOG_E(NAS, "Invalid buffer length = %ld: must hold at least a plain 5GMM header\n", buffer.len);
return NAS_SECURITY_BAD_INPUT;
}
int security_type = buffer.buf[1];
LOG_D(NAS, "Security type is: %s\n", print_info(security_type, security_header_type_s, sizeofArray(security_header_type_s)));
switch (security_type) {
case PLAIN_5GS_MSG: case PLAIN_5GS_MSG:
return NAS_SECURITY_UNPROTECTED; return unprotected_allowed(buffer, msg_type) ? NAS_SECURITY_UNPROTECTED : NAS_SECURITY_BAD_INPUT;
break;
case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX:
stream_security_container_delete(nas->security_container);
nas->security_container = NULL;
nas->security.nas_count_dl = 0;
return NAS_SECURITY_NO_SECURITY_CONTEXT;
break; break;
case INTEGRITY_PROTECTED: case INTEGRITY_PROTECTED:
case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX:
case INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX: case INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX:
/* only accept "integrity protected and ciphered" messages */ /* only accept "integrity protected and ciphered" messages */
if (pdu_buffer[6] == 0) if (buffer.buf[6] == 0)
LOG_E(NAS, "Received nas_count_dl = %d\n", pdu_buffer[6]); LOG_E(NAS, "Received nas_count_dl = %d\n", buffer.buf[6]);
LOG_E(NAS, "todo: unhandled security type %s (pdu_buffer[1] = %d)\n", security_header_type_s[pdu_buffer[1]].text, pdu_buffer[1]); LOG_E(NAS, "todo: unhandled security type %s (buffer.buf[1] = %d)\n", security_header_type_s[buffer.buf[1]].text, buffer.buf[1]);
return NAS_SECURITY_BAD_INPUT; return NAS_SECURITY_BAD_INPUT;
break; break;
default: default:
break; break;
} }
/* header is 7 bytes, require at least one byte of payload */
if (buffer.len < 8) {
LOG_E(NAS, "Invalid buffer length = %ld\n", buffer.len);
return NAS_SECURITY_BAD_INPUT;
}
/* synchronize NAS SQN, based on 24.501 4.4.3.1 */ /* synchronize NAS SQN, based on 24.501 4.4.3.1 */
// Sequence number // Sequence number
int nas_sqn = pdu_buffer[6]; int nas_sqn = buffer.buf[6];
int target_sqn = nas->security.nas_count_dl & 0xff; int target_sqn = nas->security.nas_count_dl & 0xff;
if (nas_sqn != target_sqn) { if (nas_sqn != target_sqn) {
if (nas_sqn < target_sqn) if (nas_sqn < target_sqn)
@@ -178,28 +215,28 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b
stream_cipher.count = nas->security.nas_count_dl; stream_cipher.count = nas->security.nas_count_dl;
stream_cipher.bearer = 1; /* todo: don't hardcode */ stream_cipher.bearer = 1; /* todo: don't hardcode */
stream_cipher.direction = 1; stream_cipher.direction = 1;
stream_cipher.message = pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1; stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1;
/* length in bits */ /* length in bits */
stream_cipher.blength = (pdu_length - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1) << 3; stream_cipher.blength = (buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1) << 3;
stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, computed_mac); stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, computed_mac);
uint8_t *received_mac = pdu_buffer + 2; uint8_t *received_mac = buffer.buf + 2;
if (memcmp(received_mac, computed_mac, NAS_INTEGRITY_SIZE) != 0) if (memcmp(received_mac, computed_mac, NAS_INTEGRITY_SIZE) != 0)
return NAS_SECURITY_INTEGRITY_FAILED; return NAS_SECURITY_INTEGRITY_FAILED;
/* decipher */ /* decipher */
uint8_t payload_len = pdu_length - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; uint8_t payload_len = buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH;
uint8_t buf[payload_len]; uint8_t buf[payload_len];
stream_cipher.context = nas->security_container->ciphering_context; stream_cipher.context = nas->security_container->ciphering_context;
stream_cipher.count = nas->security.nas_count_dl; stream_cipher.count = nas->security.nas_count_dl;
stream_cipher.bearer = 1; /* todo: don't hardcode */ stream_cipher.bearer = 1; /* todo: don't hardcode */
stream_cipher.direction = 1; stream_cipher.direction = 1;
stream_cipher.message = pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH;
/* length in bits */ /* length in bits */
stream_cipher.blength = (payload_len) << 3; stream_cipher.blength = (payload_len) << 3;
stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, buf); stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, buf);
memcpy(pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH, buf, payload_len); memcpy(buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH, buf, payload_len);
nas->security.nas_count_dl++; nas->security.nas_count_dl++;
@@ -473,7 +510,7 @@ static void derive_ue_keys(uint8_t *buf, nr_ue_nas_t *nas)
derive_kausf(ck, ik, sqn, kausf, nas->uicc); derive_kausf(ck, ik, sqn, kausf, nas->uicc);
derive_kseaf(kausf, kseaf, nas->uicc); derive_kseaf(kausf, kseaf, nas->uicc);
derive_kamf(kseaf, kamf, 0x0000, nas->uicc); derive_kamf(kseaf, kamf, 0x0000, nas->uicc);
derive_kgnb(kamf, 0, kgnb); derive_kgnb(kamf, nas->security.nas_count_ul, kgnb);
printf("kausf:"); printf("kausf:");
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
@@ -792,7 +829,7 @@ void generateServiceRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas)
} }
} }
void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype, uicc_t *uicc) static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t identitytype, uicc_t *uicc)
{ {
int size = sizeof(fgmm_msg_header_t); int size = sizeof(fgmm_msg_header_t);
fgmm_nas_message_plain_t plain = {0}; fgmm_nas_message_plain_t plain = {0};
@@ -813,6 +850,39 @@ void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype
initialNasMsg->length = mm_msg_encode(&plain, initialNasMsg->nas_data, size); initialNasMsg->length = mm_msg_encode(&plain, initialNasMsg->nas_data, size);
} }
static void handle_identity_request(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas, const byte_array_t buffer)
{
fgmm_msg_header_t mm_header = {0};
fgs_identity_request_msg_t msg = {0};
// Decode plain NAS header
int decoded = decode_5gmm_msg_header(&mm_header, buffer.buf, buffer.len);
if (decoded < 0) {
LOG_E(NAS, "Failed to decode NAS Identity Request header\n");
return;
}
byte_array_t payload = {.buf = &buffer.buf[decoded], .len = buffer.len - decoded};
// Decode identity request payload
if (decode_fgs_identity_request(&msg, &payload) < 0) {
LOG_E(NAS, "Failed to decode Identity Request message\n");
return;
}
LOG_I(NAS,
"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");
return;
}
generateIdentityResponse(initialNasMsg, msg.fgsmobileidentity, nas->uicc);
}
static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf) static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf)
{ {
derive_ue_keys(buf, nas); derive_ue_keys(buf, nas);
@@ -1227,7 +1297,7 @@ static void generateDeregistrationRequest(nr_ue_nas_t *nas, as_nas_info_t *initi
// Plain 5GMM // Plain 5GMM
fgs_deregistration_request_ue_originating_msg *mm_msg = &sp_msg.plain.mm_msg.fgs_deregistration_request_ue_originating; fgs_deregistration_request_ue_originating_msg *mm_msg = &sp_msg.plain.mm_msg.fgs_deregistration_request_ue_originating;
mm_msg->deregistrationtype.switchoff = NORMAL_DEREGISTRATION; mm_msg->deregistrationtype.switchoff = NORMAL_DEREGISTRATION; // note: in case this is changed to SWITCH_OFF, handle in unprotected_allowed
mm_msg->deregistrationtype.reregistration_required = REREGISTRATION_NOT_REQUIRED; mm_msg->deregistrationtype.reregistration_required = REREGISTRATION_NOT_REQUIRED;
mm_msg->deregistrationtype.access_type = TGPP_ACCESS; mm_msg->deregistrationtype.access_type = TGPP_ACCESS;
mm_msg->naskeysetidentifier.naskeysetidentifier = 1; mm_msg->naskeysetidentifier.naskeysetidentifier = 1;
@@ -1381,7 +1451,7 @@ static void generatePduSessionEstablishRequest(nr_ue_nas_t *nas, as_nas_info_t *
} }
} }
static uint8_t get_msg_type(uint8_t *pdu_buffer, uint32_t length) static fgs_nas_msg_t get_msg_type(uint8_t *pdu_buffer, uint32_t length)
{ {
if (pdu_buffer == NULL) if (pdu_buffer == NULL)
goto error; goto error;
@@ -1676,25 +1746,22 @@ void *nas_nrue(void *args_p)
NAS_CONN_ESTABLI_CNF(msg_p).errCode, NAS_CONN_ESTABLI_CNF(msg_p).errCode,
NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length);
uint8_t *pdu_buffer = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data; byte_array_t ba = {.buf = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data, .len = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length};
int pdu_length = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length; fgs_nas_msg_t msg_type = get_msg_type(ba.buf, ba.len);
security_state_t security_state = nas_security_rx_process(nas, ba, msg_type);
security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length); if (security_state > NAS_SECURITY_INTEGRITY_PASSED) {
if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) {
LOG_E(NAS, "NAS integrity failed, discard incoming message: security state is %s\n", security_state_info[security_state].text); LOG_E(NAS, "NAS integrity failed, discard incoming message: security state is %s\n", security_state_info[security_state].text);
break; break;
} }
int msg_type = get_msg_type(pdu_buffer, pdu_length);
if (msg_type == FGS_REGISTRATION_ACCEPT) { if (msg_type == FGS_REGISTRATION_ACCEPT) {
handle_registration_accept(nas, pdu_buffer, pdu_length); handle_registration_accept(nas, ba.buf, ba.len);
} else if (msg_type == FGS_PDU_SESSION_ESTABLISHMENT_ACC) { } else if (msg_type == FGS_PDU_SESSION_ESTABLISHMENT_ACC) {
handle_pdu_session_accept(pdu_buffer, pdu_length, nas->UE_id); handle_pdu_session_accept(ba.buf, ba.len, nas->UE_id);
} }
// Free NAS buffer memory after use (coming from RRC) // Free NAS buffer memory after use (coming from RRC)
free(pdu_buffer); free_byte_array(ba);
break; break;
} }
@@ -1744,40 +1811,31 @@ void *nas_nrue(void *args_p)
} break; } break;
case NAS_DOWNLINK_DATA_IND: { case NAS_DOWNLINK_DATA_IND: {
LOG_I(NAS,
"[UE %ld] Received %s: length %u , buffer %p\n",
nas->UE_id,
ITTI_MSG_NAME(msg_p),
NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length,
NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data);
as_nas_info_t initialNasMsg = {0}; as_nas_info_t initialNasMsg = {0};
uint8_t *pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data; uint8_t *pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data;
int pdu_length = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length; int pdu_length = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length;
int msg_type = get_msg_type(pdu_buffer, pdu_length); byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length};
fgs_nas_msg_t msg_type = get_msg_type(pdu_buffer, pdu_length);
security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length); security_state_t security_state = nas_security_rx_process(nas, buffer, msg_type);
/* special cases accepted without protection */ if (security_state > NAS_SECURITY_INTEGRITY_PASSED) {
if (security_state == NAS_SECURITY_UNPROTECTED) {
/* for the moment, only FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING is accepted */
if (msg_type == FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING)
security_state = NAS_SECURITY_INTEGRITY_PASSED;
}
if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) {
LOG_E(NAS, "NAS integrity failed, discard incoming message\n"); LOG_E(NAS, "NAS integrity failed, discard incoming message\n");
break; break;
} }
LOG_I(NAS,
"[UE %ld] Received %s type %s with length %u\n",
nas->UE_id,
ITTI_MSG_NAME(msg_p),
print_info(msg_type, message_text_info, sizeofArray(message_text_info)),
NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length);
switch (msg_type) { switch (msg_type) {
case FGS_IDENTITY_REQUEST: case FGS_IDENTITY_REQUEST:
generateIdentityResponse(&initialNasMsg, *(pdu_buffer + 3), nas->uicc); handle_identity_request(&initialNasMsg, nas, buffer);
break; break;
case FGS_AUTHENTICATION_REQUEST: { case FGS_AUTHENTICATION_REQUEST:
byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length};
handle_fgmm_authentication_request(nas, &initialNasMsg, &buffer); handle_fgmm_authentication_request(nas, &initialNasMsg, &buffer);
break; break;
}
case FGS_SECURITY_MODE_COMMAND: case FGS_SECURITY_MODE_COMMAND:
handle_security_mode_command(nas, &initialNasMsg, pdu_buffer, pdu_length); handle_security_mode_command(nas, &initialNasMsg, pdu_buffer, pdu_length);
break; break;
@@ -1803,7 +1861,6 @@ void *nas_nrue(void *args_p)
exit(1); exit(1);
break; break;
case FGS_SERVICE_ACCEPT: { case FGS_SERVICE_ACCEPT: {
byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length};
handle_service_accept(nas, &buffer); handle_service_accept(nas, &buffer);
break; break;
} }