diff --git a/ci-scripts/xml_files/container_5g_rfsim_24prb.xml b/ci-scripts/xml_files/container_5g_rfsim_24prb.xml index f4ee341c94..5a59880b2f 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_24prb.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_24prb.xml @@ -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 @@ -82,7 +97,7 @@ IdleSleep Sleep - 5 + 1 @@ -92,8 +107,8 @@ cacofonix rfsim5g_ext_dn cacofonix - -c 20 -i0.2 - 5 + -c 20 -i0.5 + 80 @@ -139,13 +154,19 @@ yes + + Custom_Command + Force RRC_IDLE (UE ID 0) + cacofonix + echo ciUE force_RRC_IDLE | nc 192.168.71.150 8091 + yes + + Undeploy_Object true Undeploy all OAI 5G stack ci-scripts/yaml_files/5g_rfsimulator_24prb - 1,0,0,0 - 1,0,0,0 0 0 diff --git a/common/config/yaml/config_yaml.cpp b/common/config/yaml/config_yaml.cpp index 108dee1ac2..138163ce0e 100644 --- a/common/config/yaml/config_yaml.cpp +++ b/common/config/yaml/config_yaml.cpp @@ -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 #include @@ -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(); + 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); } diff --git a/common/config/yaml/tests/CMakeLists.txt b/common/config/yaml/tests/CMakeLists.txt index 6290f2fc15..d33fa57658 100644 --- a/common/config/yaml/tests/CMakeLists.txt +++ b/common/config/yaml/tests/CMakeLists.txt @@ -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) diff --git a/common/config/yaml/tests/test_ipv4.yaml b/common/config/yaml/tests/test_ipv4.yaml new file mode 100644 index 0000000000..c7d1b4866f --- /dev/null +++ b/common/config/yaml/tests/test_ipv4.yaml @@ -0,0 +1,4 @@ +test: + ipv4_1: 0.0.0.0 + ipv4_2: 192.168.0.1 + #ipv4_3: check default diff --git a/common/config/yaml/tests/test_yaml_config.cpp b/common/config/yaml/tests/test_yaml_config.cpp index deaa52cd1a..384a1f406c 100644 --- a/common/config/yaml/tests/test_yaml_config.cpp +++ b/common/config/yaml/tests/test_yaml_config.cpp @@ -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(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(); diff --git a/common/utils/telnetsrv/telnetsrv_ciUE.c b/common/utils/telnetsrv/telnetsrv_ciUE.c index 721cb0e5cd..90e044ed86 100644 --- a/common/utils/telnetsrv/telnetsrv_ciUE.c +++ b/common/utils/telnetsrv/telnetsrv_ciUE.c @@ -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}, }; diff --git a/common/utils/tun_if.c b/common/utils/tun_if.c index 87bba5a513..4bb1f7ec58 100644 --- a/common/utils/tun_if.c +++ b/common/utils/tun_if.c @@ -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); diff --git a/common/utils/tun_if.h b/common/utils/tun_if.h index e73b4be94e..8e1463624f 100644 --- a/common/utils/tun_if.h +++ b/common/utils/tun_if.h @@ -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_*/ diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index b36c2d676d..4abc712507 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -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; diff --git a/openair2/SDAP/nr_sdap/nr_sdap.c b/openair2/SDAP/nr_sdap/nr_sdap.c index 44b8fa2cb0..f6602c7597 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap.c +++ b/openair2/SDAP/nr_sdap/nr_sdap.c @@ -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); } diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c index d93a508c33..7f2f3dd07c 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap_entity.c +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c @@ -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)); } } diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt index 40bfcbabcd..a4bb516b89 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt @@ -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 ../../) diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h index b7e66019cc..4d6d4d7eed 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h @@ -30,8 +30,8 @@ #include #include #include - #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; diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt index 3b51ec748c..bf47e478b9 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt @@ -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 diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c new file mode 100644 index 0000000000..b41035f6ea --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.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 +#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; +} diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h new file mode 100644 index 0000000000..104f133ed1 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h @@ -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 +#include +#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 */ diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index b1378c3360..d152a5d4a3 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -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; }