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:
@@ -34,9 +34,24 @@
|
||||
020002
|
||||
030001
|
||||
030002
|
||||
040002
|
||||
000004
|
||||
020001
|
||||
040002
|
||||
000004
|
||||
020001
|
||||
040002
|
||||
000004
|
||||
020001
|
||||
040002
|
||||
000004
|
||||
020001
|
||||
040002
|
||||
000004
|
||||
020001
|
||||
040001
|
||||
000004
|
||||
020002
|
||||
020001
|
||||
100001
|
||||
222222
|
||||
</TestCaseRequestedList>
|
||||
@@ -82,7 +97,7 @@
|
||||
<testCase id="000004">
|
||||
<class>IdleSleep</class>
|
||||
<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 id="020001">
|
||||
@@ -92,8 +107,8 @@
|
||||
<nodes>cacofonix</nodes>
|
||||
<svr_id>rfsim5g_ext_dn</svr_id>
|
||||
<svr_node>cacofonix</svr_node>
|
||||
<ping_args> -c 20 -i0.2</ping_args>
|
||||
<ping_packetloss_threshold>5</ping_packetloss_threshold>
|
||||
<ping_args> -c 20 -i0.5</ping_args>
|
||||
<ping_packetloss_threshold>80</ping_packetloss_threshold>
|
||||
</testCase>
|
||||
|
||||
<testCase id="020002">
|
||||
@@ -139,13 +154,19 @@
|
||||
<command_fail>yes</command_fail>
|
||||
</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">
|
||||
<class>Undeploy_Object</class>
|
||||
<always_exec>true</always_exec>
|
||||
<desc>Undeploy all OAI 5G stack</desc>
|
||||
<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_serverId>0</eNB_serverId>
|
||||
</testCase>
|
||||
|
||||
@@ -25,6 +25,8 @@ extern "C" {
|
||||
#include "common/config/config_userapi.h"
|
||||
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);
|
||||
#include "../config_userapi.h"
|
||||
#include "../config_common.h"
|
||||
}
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
@@ -98,6 +100,9 @@ void SetDefault(configmodule_interface_t *cfg, paramdef_t *param)
|
||||
param->uptr = (uint32_t *)param->defintarrayval;
|
||||
}
|
||||
break;
|
||||
case TYPE_IPV4ADDR:
|
||||
config_setdefault_ipv4addr(cfg, param, param->defstrval);
|
||||
break;
|
||||
default:
|
||||
AssertFatal(false, "Unhandled type %d", param->type);
|
||||
}
|
||||
@@ -186,6 +191,16 @@ void SetNonDefault(configmodule_interface_t *cfg, const YAML::Node &node, paramd
|
||||
}
|
||||
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:
|
||||
AssertFatal(false, "Unhandled type %d", param->type);
|
||||
}
|
||||
|
||||
@@ -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_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_ipv4.yaml test_ipv4.yaml COPYONLY)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
logInit();
|
||||
|
||||
@@ -102,12 +102,21 @@ int force_crnti_ra(char *buf, int debug, telnet_printfunc_t prnt)
|
||||
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 */
|
||||
static telnetshell_cmddef_t cicmds[] = {
|
||||
{"sync_state", "[UE_ID(int,opt)]", get_sync_state},
|
||||
{"force_rlf", "", force_rlf},
|
||||
{"force_RRC_IDLE", "", force_RRC_IDLE},
|
||||
{"force_crnti_ra", "", force_crnti_ra},
|
||||
{"deregistration", "", force_deregistration},
|
||||
{"", "", NULL},
|
||||
};
|
||||
|
||||
|
||||
+19
-8
@@ -168,10 +168,14 @@ static bool change_interface_state(int sock_fd, const char *ifn, if_action_t if_
|
||||
|
||||
struct ifreq ifr = {0};
|
||||
strncpy(ifr.ifr_name, ifn, sizeof(ifr.ifr_name));
|
||||
|
||||
/* get flags of this interface: see netdevice(7) */
|
||||
bool success = ioctl(sock_fd, SIOCGIFFLAGS, (caddr_t)&ifr) == 0;
|
||||
if (!success)
|
||||
goto fail_interface_state;
|
||||
if (!success && if_action == INTERFACE_DOWN && errno == ENODEV) {
|
||||
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) {
|
||||
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;
|
||||
if (!success)
|
||||
goto fail_interface_state;
|
||||
LOG_D(OIP, "Interface %s successfully set %s\n", ifn, action);
|
||||
|
||||
return true;
|
||||
|
||||
fail_interface_state:
|
||||
@@ -201,10 +207,6 @@ bool tun_config(const char* ifname, const char *ipv4, const char *ipv6)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!change_interface_state(sock_fd, ifname, INTERFACE_DOWN)) {
|
||||
close(sock_fd);
|
||||
return false;
|
||||
}
|
||||
bool success = true;
|
||||
if (ipv4 != NULL)
|
||||
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);
|
||||
}
|
||||
|
||||
void tun_destroy(const char *dev, int fd) {
|
||||
if (change_interface_state(fd, dev, INTERFACE_DOWN) == 0) {
|
||||
void tun_destroy(const char *dev)
|
||||
{
|
||||
// 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);
|
||||
} else {
|
||||
LOG_E(UTIL, "Could not bring interface %s down.\n", dev);
|
||||
|
||||
@@ -84,8 +84,7 @@ int tun_alloc(const char *dev);
|
||||
/*!
|
||||
* \brief This function destroys the TUN 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_*/
|
||||
|
||||
@@ -2562,6 +2562,9 @@ void nr_rrc_going_to_IDLE(NR_UE_RRC_INST_t *rrc,
|
||||
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++) {
|
||||
if (rrc->Srb[i] != RB_NOT_PRESENT) {
|
||||
rrc->Srb[i] = RB_NOT_PRESENT;
|
||||
|
||||
@@ -121,8 +121,21 @@ static void *sdap_tun_read_thread(void *arg)
|
||||
while (!entity->stop_thread) {
|
||||
len = read(entity->pdusession_sock, &rx_buf, NL_MAX_PAYLOAD);
|
||||
if (len == -1) {
|
||||
LOG_E(PDCP, "could not read(): errno %d %s\n", errno, strerror(errno));
|
||||
return NULL;
|
||||
if (errno == EINTR)
|
||||
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);
|
||||
@@ -194,6 +207,10 @@ void remove_ip_if(nr_sdap_entity_t *entity)
|
||||
DevAssert(entity != NULL);
|
||||
// Stop the read thread
|
||||
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);
|
||||
AssertFatal(ret == 0, "pthread_join() failed, errno: %d, %s\n", errno, strerror(errno));
|
||||
// Bring down the IP interface
|
||||
@@ -205,5 +222,5 @@ void remove_ip_if(nr_sdap_entity_t *entity)
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
|
||||
LOG_D(SDAP, "RX Entity offset : %d\n", 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
|
||||
PRIVATE log_headers
|
||||
)
|
||||
|
||||
target_include_directories(fgs_5gmm_ies_lib PRIVATE ../../)
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "OctetString.h"
|
||||
#include "fgs_nas_utils.h"
|
||||
|
||||
#ifndef FGS_MOBILE_IDENTITY_H_
|
||||
#define FGS_MOBILE_IDENTITY_H_
|
||||
@@ -129,15 +129,22 @@ typedef struct {
|
||||
uint8_t macaddr;
|
||||
} 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
|
||||
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;
|
||||
Suci5GSMobileIdentity_t suci;
|
||||
Guti5GSMobileIdentity_t guti;
|
||||
|
||||
@@ -5,6 +5,7 @@ add_library(fgs_5gmm_lib OBJECT
|
||||
fgmm_service_reject.c
|
||||
RegistrationRequest.c
|
||||
RegistrationAccept.c
|
||||
fgmm_identity_request.c
|
||||
FGSIdentityResponse.c
|
||||
FGSAuthenticationResponse.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
@@ -50,6 +50,7 @@
|
||||
#include "SORTransparentContainer.h"
|
||||
#include "FGSIdentityResponse.h"
|
||||
#include "fgmm_authentication_request.h"
|
||||
#include "fgmm_identity_request.h"
|
||||
#include "T.h"
|
||||
#include "aka_functions.h"
|
||||
#include "assertions.h"
|
||||
@@ -69,6 +70,7 @@
|
||||
#include "fgs_nas_utils.h"
|
||||
#include "fgmm_service_accept.h"
|
||||
#include "fgmm_service_reject.h"
|
||||
#include "ds/byte_array.h"
|
||||
|
||||
#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) \
|
||||
TYPE_DEF(NAS_SECURITY_NO_SECURITY_CONTEXT, 0) \
|
||||
TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 1) \
|
||||
TYPE_DEF(NAS_SECURITY_UNPROTECTED, 2) \
|
||||
TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 3) \
|
||||
TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 4) \
|
||||
TYPE_DEF(NAS_SECURITY_UNPROTECTED, 1) \
|
||||
TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 2) \
|
||||
TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 3) \
|
||||
TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 4) \
|
||||
TYPE_DEF(NAS_SECURITY_BAD_INPUT, 5)
|
||||
|
||||
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";
|
||||
}
|
||||
|
||||
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)
|
||||
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:
|
||||
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;
|
||||
case INTEGRITY_PROTECTED:
|
||||
case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX:
|
||||
case INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX:
|
||||
/* only accept "integrity protected and ciphered" messages */
|
||||
if (pdu_buffer[6] == 0)
|
||||
LOG_E(NAS, "Received nas_count_dl = %d\n", pdu_buffer[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]);
|
||||
if (buffer.buf[6] == 0)
|
||||
LOG_E(NAS, "Received nas_count_dl = %d\n", buffer.buf[6]);
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
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 */
|
||||
// Sequence number
|
||||
int nas_sqn = pdu_buffer[6];
|
||||
int nas_sqn = buffer.buf[6];
|
||||
int target_sqn = nas->security.nas_count_dl & 0xff;
|
||||
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.bearer = 1; /* todo: don't hardcode */
|
||||
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 */
|
||||
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);
|
||||
|
||||
uint8_t *received_mac = pdu_buffer + 2;
|
||||
uint8_t *received_mac = buffer.buf + 2;
|
||||
|
||||
if (memcmp(received_mac, computed_mac, NAS_INTEGRITY_SIZE) != 0)
|
||||
return NAS_SECURITY_INTEGRITY_FAILED;
|
||||
|
||||
/* 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];
|
||||
stream_cipher.context = nas->security_container->ciphering_context;
|
||||
stream_cipher.count = nas->security.nas_count_dl;
|
||||
stream_cipher.bearer = 1; /* todo: don't hardcode */
|
||||
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 */
|
||||
stream_cipher.blength = (payload_len) << 3;
|
||||
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++;
|
||||
|
||||
@@ -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_kseaf(kausf, kseaf, 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:");
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
derive_ue_keys(buf, nas);
|
||||
@@ -1227,7 +1297,7 @@ static void generateDeregistrationRequest(nr_ue_nas_t *nas, as_nas_info_t *initi
|
||||
|
||||
// Plain 5GMM
|
||||
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.access_type = TGPP_ACCESS;
|
||||
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)
|
||||
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).nasMsg.length);
|
||||
|
||||
uint8_t *pdu_buffer = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data;
|
||||
int pdu_length = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length;
|
||||
|
||||
security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length);
|
||||
if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) {
|
||||
byte_array_t ba = {.buf = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data, .len = 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);
|
||||
if (security_state > NAS_SECURITY_INTEGRITY_PASSED) {
|
||||
LOG_E(NAS, "NAS integrity failed, discard incoming message: security state is %s\n", security_state_info[security_state].text);
|
||||
break;
|
||||
}
|
||||
|
||||
int msg_type = get_msg_type(pdu_buffer, pdu_length);
|
||||
|
||||
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) {
|
||||
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(pdu_buffer);
|
||||
free_byte_array(ba);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1744,40 +1811,31 @@ void *nas_nrue(void *args_p)
|
||||
} break;
|
||||
|
||||
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};
|
||||
|
||||
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 msg_type = get_msg_type(pdu_buffer, pdu_length);
|
||||
|
||||
security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length);
|
||||
/* special cases accepted without protection */
|
||||
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) {
|
||||
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, buffer, msg_type);
|
||||
if (security_state > NAS_SECURITY_INTEGRITY_PASSED) {
|
||||
LOG_E(NAS, "NAS integrity failed, discard incoming message\n");
|
||||
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) {
|
||||
case FGS_IDENTITY_REQUEST:
|
||||
generateIdentityResponse(&initialNasMsg, *(pdu_buffer + 3), nas->uicc);
|
||||
handle_identity_request(&initialNasMsg, nas, buffer);
|
||||
break;
|
||||
case FGS_AUTHENTICATION_REQUEST: {
|
||||
byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length};
|
||||
case FGS_AUTHENTICATION_REQUEST:
|
||||
handle_fgmm_authentication_request(nas, &initialNasMsg, &buffer);
|
||||
break;
|
||||
}
|
||||
case FGS_SECURITY_MODE_COMMAND:
|
||||
handle_security_mode_command(nas, &initialNasMsg, pdu_buffer, pdu_length);
|
||||
break;
|
||||
@@ -1803,7 +1861,6 @@ void *nas_nrue(void *args_p)
|
||||
exit(1);
|
||||
break;
|
||||
case FGS_SERVICE_ACCEPT: {
|
||||
byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length};
|
||||
handle_service_accept(nas, &buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user