Unverified Commit 34aca71c authored by Ali Güngör's avatar Ali Güngör Committed by GitHub

Merge pull request #262 from aligungr/dev

v3.1.2
parents a39a01d9 724167da
......@@ -2,19 +2,24 @@
<a href="https://github.com/aligungr/UERANSIM"><img src="/.github/logo.png" width="75" title="UERANSIM"></a>
</p>
<p align="center">
<img src="https://img.shields.io/badge/UERANSIM-v3.1.1-blue" />
<img src="https://img.shields.io/badge/UERANSIM-v3.1.2-blue" />
<img src="https://img.shields.io/badge/3GPP-R15-orange" />
<img src="https://img.shields.io/badge/License-GPL--3.0-green"/>
</p>
**UERANSIM** <small>(pronounced "ju-i ræn sɪm")</small>, is the open-source state-of-the-art 5G UE and RAN (gNodeB) implementation. It can be considered as a 5G mobile phone and a base station in basic terms. The project can be used for testing 5G Core Network and studying 5G System.
**UERANSIM** <small>(pronounced "ju-i ræn sɪm")</small>, is the open-source state-of-the-art 5G UE and RAN (gNodeB)
implementation. It can be considered as a 5G mobile phone and a base station in basic terms. The project can be used for
testing 5G Core Network and studying 5G System.
## Current Status
Our UE and gNodeB are functional and ready to use. You can connect them to your 5G core network right now and start using it.
Our UE and gNodeB are functional and ready to use. You can connect them to your 5G core network right now and start
using it.
In terms of 3GPP coverage, fundamental control plane features are done. However, some of them are in progress.
At the same time, 5G-NR radio interface is under development but not complete yet. Currently we utilize the radio interface over a simulated environment.
At the same time, 5G-NR radio interface is under development but not complete yet. Currently we utilize the radio
interface over a simulated environment.
<p align="center">
<img src="https://img.shields.io/badge/5G%20Radio%20Interface-in%20progress-orange" alt="OS Linux"/>
......@@ -23,20 +28,28 @@ At the same time, 5G-NR radio interface is under development but not complete ye
</p>
## Documentation
You can find the documentation on [UERANSIM Wiki](https://github.com/aligungr/UERANSIM/wiki).
And, since the project is rapidly developing, please make sure that you have always the [latest](https://github.com/aligungr/UERANSIM/releases) UERANSIM.
And, since the project is rapidly developing, please make sure that you have always
the [latest](https://github.com/aligungr/UERANSIM/releases) UERANSIM.
## Contributing
Implementing UE and RAN is not an easy task and is very time-consuming. We are always open to public contributions and pull requests.
Implementing UE and RAN is not an easy task and is very time-consuming. We are always open to public contributions and
pull requests.
## Supporting
UERANSIM is the first and currently only open source project that implements 5G-SA UE and RAN. Commercial alternatives of this software cost hundreds of thousands of dollars. You can support this free and open source software by:
UERANSIM is the first and currently only open source project that implements 5G-SA UE and RAN. Commercial alternatives
of this software cost hundreds of thousands of dollars. You can support this free and open source software by:
- Donating on [Open Collective](https://opencollective.com/UERANSIM)
- Starring our GitHub repository,
- Creating pull requests, submitting bugs, suggesting new features or documentation updates.
## License
Copyright (c) 2021 ALİ GÜNGÖR. All source code and related files including documentation and wiki pages are licensed under [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), see [LICENSE](https://github.com/aligungr/UERANSIM/blob/master/LICENSE) for more details.
Copyright (c) 2021 ALİ GÜNGÖR. All source code and related files including documentation and wiki pages are licensed
under [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html),
see [LICENSE](https://github.com/aligungr/UERANSIM/blob/master/LICENSE) for more details.
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)
supi: 'imsi-286010000000001'
# Mobile Country Code value
# Mobile Country Code value of HPLMN
mcc: '286'
# Mobile Network Code value (2 or 3 digits)
# Mobile Network Code value of HPLMN (2 or 3 digits)
mnc: '93'
# Permanent subscription key
......@@ -30,16 +30,23 @@ sessions:
sst: 1
sd: 1
# List of requested S-NSSAIs by this UE
slices:
# Configured NSSAI for this UE by HPLMN
configured-nssai:
- sst: 1
sd: 1
# Supported encryption and integrity algorithms by this UE
# Default Configured NSSAI for this UE
default-nssai:
- sst: 1
sd: 1
# Supported encryption algorithms by this UE
integrity:
IA1: true
IA2: true
IA3: true
# Supported integrity algorithms by this UE
ciphering:
EA1: true
EA2: true
......
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)
supi: 'imsi-208930000000003'
# Mobile Country Code value
# Mobile Country Code value of HPLMN
mcc: '208'
# Mobile Network Code value (2 or 3 digits)
# Mobile Network Code value of HPLMN (2 or 3 digits)
mnc: '93'
# Permanent subscription key
......@@ -10,7 +10,7 @@ key: '8baf473f2f8fd09487cccbd7097c6862'
# Operator code (OP or OPC) of the UE
op: '8e27b6af0e692e750f32667a3b14605d'
# This value specifies the OP type and it can be either 'OP' or 'OPC'
opType: 'OP'
opType: 'OPC'
# Authentication Management Field (AMF) value
amf: '8000'
# IMEI number of the device. It is used if no SUPI is provided
......@@ -30,16 +30,23 @@ sessions:
sst: 0x01
sd: 0x010203
# List of requested S-NSSAIs by this UE
slices:
# Configured NSSAI for this UE by HPLMN
configured-nssai:
- sst: 0x01
sd: 0x010203
# Supported encryption and integrity algorithms by this UE
# Default Configured NSSAI for this UE
default-nssai:
- sst: 1
sd: 1
# Supported encryption algorithms by this UE
integrity:
IA1: true
IA2: true
IA3: true
# Supported integrity algorithms by this UE
ciphering:
EA1: true
EA2: true
......
# IMSI number of the UE. IMSI = [MCC|MNC|MSISDN] (In total 15 or 16 digits)
supi: 'imsi-901700000000003'
# Mobile Country Code value
supi: 'imsi-901700000000001'
# Mobile Country Code value of HPLMN
mcc: '901'
# Mobile Network Code value (2 or 3 digits)
# Mobile Network Code value of HPLMN (2 or 3 digits)
mnc: '70'
# Permanent subscription key
......@@ -10,7 +10,7 @@ key: '465B5CE8B199B49FAA5F0A2EE238A6BC'
# Operator code (OP or OPC) of the UE
op: 'E8ED289DEBA952E4283B54E88E6183CA'
# This value specifies the OP type and it can be either 'OP' or 'OPC'
opType: 'OP'
opType: 'OPC'
# Authentication Management Field (AMF) value
amf: '8000'
# IMEI number of the device. It is used if no SUPI is provided
......@@ -30,16 +30,23 @@ sessions:
sst: 1
sd: 1
# List of requested S-NSSAIs by this UE
slices:
# Configured NSSAI for this UE by HPLMN
configured-nssai:
- sst: 1
sd: 1
# Supported encryption and integrity algorithms by this UE
# Default Configured NSSAI for this UE
default-nssai:
- sst: 1
sd: 1
# Supported encryption algorithms by this UE
integrity:
IA1: true
IA2: true
IA3: true
# Supported integrity algorithms by this UE
ciphering:
EA1: true
EA2: true
......
......@@ -65,11 +65,11 @@ static nr::gnb::GnbConfig *ReadConfigYaml()
for (auto &nssai : yaml::GetSequence(config, "slices"))
{
SliceSupport s{};
SingleSlice s{};
s.sst = yaml::GetInt32(nssai, "sst", 1, 0xFF);
if (yaml::HasField(nssai, "sd"))
s.sd = octet3{yaml::GetInt32(nssai, "sd", 1, 0xFFFFFF)};
result->nssais.push_back(s);
result->nssai.slices.push_back(s);
}
return result;
......
......@@ -106,7 +106,7 @@ void GnbMrTask::onLoop()
break;
}
case NwGnbRrcToMr::AN_RELEASE: {
m_rlsEntity->localReleaseConnection(w->ueId, rls::ECause::RRC_RELEASE);
m_rlsEntity->localReleaseConnection(w->ueId, rls::ECause::RRC_NORMAL_RELEASE);
break;
}
}
......
......@@ -23,12 +23,12 @@
#include <asn/ngap/ASN_NGAP_InitiatingMessage.h>
#include <asn/ngap/ASN_NGAP_NGAP-PDU.h>
#include <asn/ngap/ASN_NGAP_NGSetupRequest.h>
#include <asn/ngap/ASN_NGAP_OverloadStartNSSAIItem.h>
#include <asn/ngap/ASN_NGAP_PLMNSupportItem.h>
#include <asn/ngap/ASN_NGAP_ProtocolIE-Field.h>
#include <asn/ngap/ASN_NGAP_ServedGUAMIItem.h>
#include <asn/ngap/ASN_NGAP_SliceSupportItem.h>
#include <asn/ngap/ASN_NGAP_SupportedTAItem.h>
#include <asn/ngap/ASN_NGAP_OverloadStartNSSAIItem.h>
namespace nr::gnb
{
......@@ -67,7 +67,7 @@ static void AssignDefaultAmfConfigs(NgapAmfContext *amf, T *msg)
auto plmnSupport = new PlmnSupport();
ngap_utils::PlmnFromAsn_Ref(item.pLMNIdentity, plmnSupport->plmn);
asn::ForeachItem(item.sliceSupportList, [plmnSupport](ASN_NGAP_SliceSupportItem &ssItem) {
plmnSupport->sliceSupportList.push_back(ngap_utils::SliceSupportFromAsn_Unique(ssItem));
plmnSupport->sliceSupportList.slices.push_back(ngap_utils::SliceSupportFromAsn(ssItem));
});
amf->plmnSupportList.push_back(plmnSupport);
});
......@@ -139,7 +139,7 @@ void NgapTask::sendNgSetupRequest(int amfId)
auto *broadcastPlmn = asn::New<ASN_NGAP_BroadcastPLMNItem>();
asn::SetOctetString3(broadcastPlmn->pLMNIdentity, ngap_utils::PlmnToOctet3(m_base->config->plmn));
for (auto &nssai : m_base->config->nssais)
for (auto &nssai : m_base->config->nssai.slices)
{
auto *item = asn::New<ASN_NGAP_SliceSupportItem>();
asn::SetOctetString1(item->s_NSSAI.sST, static_cast<uint8_t>(nssai.sst));
......
......@@ -192,10 +192,9 @@ void NgapTask::receiveSessionResourceSetupRequest(int amfId, ASN_NGAP_PDUSession
sendNgapUeAssociated(ue->ctxId, respPdu);
if (failedList.empty())
m_logger->info("PDU session resource is established for UE[%d] count[%d]", ue->ctxId, successList.size());
m_logger->info("PDU session resource is setup for UE[%d] count[%d]", ue->ctxId, successList.size());
else if (successList.empty())
m_logger->err("PDU session resource establishment was failed for UE[%d] count[%d]", ue->ctxId,
failedList.size());
m_logger->err("PDU session resource setup was failed for UE[%d] count[%d]", ue->ctxId, failedList.size());
else
m_logger->err("PDU session establishment is partially successful for UE[%d], success[%d], failed[%d]",
successList.size(), failedList.size());
......
......@@ -86,13 +86,13 @@ void GuamiFromAsn_Ref(const ASN_NGAP_GUAMI_t &guami, Guami &target)
PlmnFromAsn_Ref(guami.pLMNIdentity, target.plmn);
}
std::unique_ptr<SliceSupport> SliceSupportFromAsn_Unique(ASN_NGAP_SliceSupportItem &supportItem)
SingleSlice SliceSupportFromAsn(ASN_NGAP_SliceSupportItem &supportItem)
{
auto s = std::make_unique<SliceSupport>();
s->sst = asn::GetOctet1(supportItem.s_NSSAI.sST);
s->sd = std::nullopt;
SingleSlice s{};
s.sst = asn::GetOctet1(supportItem.s_NSSAI.sST);
s.sd = std::nullopt;
if (supportItem.s_NSSAI.sD)
s->sd = asn::GetOctet3(*supportItem.s_NSSAI.sD);
s.sd = asn::GetOctet3(*supportItem.s_NSSAI.sD);
return s;
}
......
......@@ -35,7 +35,7 @@ void GuamiFromAsn_Ref(const ASN_NGAP_GUAMI_t &guami, Guami &target);
void ToCauseAsn_Ref(NgapCause source, ASN_NGAP_Cause_t &target);
void ToPlmnAsn_Ref(const Plmn &source, ASN_NGAP_PLMNIdentity_t &target);
std::unique_ptr<SliceSupport> SliceSupportFromAsn_Unique(ASN_NGAP_SliceSupportItem &supportItem);
SingleSlice SliceSupportFromAsn(ASN_NGAP_SliceSupportItem &supportItem);
NgapIdPair FindNgapIdPairFromAsnNgapIds(const ASN_NGAP_UE_NGAP_IDs &ngapIDs);
......
......@@ -26,7 +26,7 @@ Json ToJson(const GnbConfig &v)
{"nci", v.nci},
{"plmn", ToJson(v.plmn)},
{"tac", v.tac},
{"nssai", ToJson(v.nssais)},
{"nssai", ToJson(v.nssai)},
{"ngap-ip", v.ngapIp},
{"gtp-ip", v.gtpIp},
{"paging-drx", ToJson(v.pagingDrx)},
......
......@@ -289,7 +289,7 @@ struct GnbConfig
int gnbIdLength{}; // 22..32 bit
Plmn plmn{};
int tac{};
std::vector<SliceSupport> nssais{};
NetworkSlice nssai{};
std::vector<GnbAmfConfig> amfConfigs{};
std::string portalIp{};
std::string ngapIp{};
......
......@@ -150,8 +150,8 @@ struct IENasKeySetIdentifier : InformationElement1
struct IENetworkSlicingIndication : InformationElement1
{
ENetworkSlicingSubscriptionChangeIndication nssci{};
EDefaultConfiguredNssaiIndication dcni{};
ENetworkSlicingSubscriptionChangeIndication nssci{}; // This is spare if dir is UE->NW
EDefaultConfiguredNssaiIndication dcni{}; // This is spare if dir is NW->UE
IENetworkSlicingIndication() = default;
IENetworkSlicingIndication(ENetworkSlicingSubscriptionChangeIndication nssci,
......
......@@ -8,6 +8,7 @@
#include "timer.hpp"
#include <sstream>
#include <utils/common.hpp>
namespace nas
......@@ -96,16 +97,16 @@ bool NasTimer::performTick()
if (running)
{
long currentMs = utils::CurrentTimeMillis();
long deltaSec = (currentMs - startMillis) / 1000;
long deltaSec = (currentMs - startMillis) / 1000LL;
long remainingSec = interval - deltaSec;
if (currentMs - _lastDebugPrintMs > 10 * 1000)
if (currentMs - _lastDebugPrintMs > 10LL * 1000LL)
{
_lastDebugPrintMs = currentMs;
// Log.debug(Tag.TIMER, "NAS Timer %s int:%ss rem:%ss", timerCode, interval, remainingSec);
}
if (remainingSec < 0)
if (remainingSec <= 0LL)
{
stop(false);
expiryCount++;
......@@ -125,8 +126,8 @@ int NasTimer::getRemaining() const
if (!running)
return 0;
long elapsed = utils::CurrentTimeMillis() - startMillis;
return static_cast<int>(std::max(interval - elapsed, 0L));
int elapsed = static_cast<int>((utils::CurrentTimeMillis() - startMillis) / 1000LL);
return std::max(interval - elapsed, 0);
}
void NasTimer::resetExpiryCount()
......@@ -141,13 +142,13 @@ int NasTimer::getExpiryCount() const
Json ToJson(const NasTimer &v)
{
int interval = v.getInterval();
std::stringstream ss{};
if (v.isRunning())
ss << "rem[" << v.getRemaining() << "] int[" << v.getInterval() << "]";
else
ss << ".";
return Json::Obj({
{"interval", interval == INT32_MAX ? Json{"inf"} : interval},
{"remaining", v.getRemaining()},
{"running", v.isRunning()},
});
return ss.str();
}
} // namespace nas
......@@ -13,7 +13,7 @@
namespace nas::utils
{
IESNssai SNssaiFrom(const SliceSupport &v)
IESNssai SNssaiFrom(const SingleSlice &v)
{
IESNssai r;
r.sst = v.sst;
......@@ -22,10 +22,10 @@ IESNssai SNssaiFrom(const SliceSupport &v)
return r;
}
IENssai NssaiFrom(const std::vector<SliceSupport> &v)
IENssai NssaiFrom(const NetworkSlice &v)
{
IENssai r;
for (auto &x : v)
for (auto &x : v.slices)
r.sNssais.push_back(SNssaiFrom(x));
return r;
}
......@@ -262,4 +262,31 @@ IEDnn DnnFromApn(const std::string &apn)
return dnn;
}
void AddToPlmnList(IEPlmnList &list, VPlmn item)
{
if (!std::any_of(list.plmns.begin(), list.plmns.end(), [&item](auto &i) { return DeepEqualsV(i, item); }))
list.plmns.push_back(item);
}
VPlmn PlmnFrom(const Plmn &plmn)
{
return VPlmn{plmn.mcc, plmn.mnc, plmn.isLongMnc};
}
NetworkSlice NssaiTo(const IENssai &v)
{
NetworkSlice nssai{};
for (auto &item : v.sNssais)
nssai.slices.push_back(SNssaiTo(item));
return nssai;
}
SingleSlice SNssaiTo(const IESNssai &v)
{
SingleSlice sNssai{};
sNssai.sst = v.sst;
sNssai.sd = v.sd;
return sNssai;
}
} // namespace nas::utils
......@@ -13,13 +13,19 @@
namespace nas::utils
{
IESNssai SNssaiFrom(const SliceSupport &v);
IENssai NssaiFrom(const std::vector<SliceSupport> &v);
IEDnn DnnFromApn(const std::string& apn);
IESNssai SNssaiFrom(const SingleSlice &v);
IENssai NssaiFrom(const NetworkSlice &v);
IEDnn DnnFromApn(const std::string &apn);
VPlmn PlmnFrom(const Plmn &plmn);
NetworkSlice NssaiTo(const IENssai &v);
SingleSlice SNssaiTo(const IESNssai &v);
bool HasValue(const IEGprsTimer3 &v);
bool HasValue(const IEGprsTimer2 &v);
void AddToPlmnList(IEPlmnList &list, VPlmn item);
const char *EnumToString(ERegistrationType v);
const char *EnumToString(EMmCause v);
const char *EnumToString(ESmCause v);
......@@ -43,6 +49,15 @@ inline bool DeepEqualsIe(const T &a, const T &b)
return s1 == s2;
}
template <typename T>
inline bool DeepEqualsV(const T &a, const T &b)
{
OctetString s1{}, s2{};
T::Encode(a, s1);
T::Encode(b, s2);
return s1 == s2;
}
template <typename T>
inline T DeepCopyIe(const T &a)
{
......
......@@ -160,7 +160,7 @@ void VTime::Encode(const VTime &value, OctetString &stream)
VTime VTime::Decode(const OctetView &stream)
{
VTime time;
VTime time{};
time.year = stream.read();
time.month = stream.read();
time.day = stream.read();
......@@ -178,28 +178,21 @@ VTime::VTime(const octet &year, const octet &month, const octet &day, const octe
void VRejectedSNssai::Encode(const VRejectedSNssai &value, OctetString &stream)
{
int totalLength = 0;
if (value.sd.has_value() && !value.sst.has_value())
{
// error: "sst must not be null if sd is not null" (currently ignoring)
}
if (value.sst.has_value())
totalLength++;
int totalLength = 1;
if (value.sd.has_value())
totalLength += 3;
int octet = totalLength << 4 | static_cast<int>(value.cause);
stream.appendOctet(octet);
if (value.sst.has_value())
stream.appendOctet(value.sst.value());
stream.appendOctet(value.sst);
if (value.sd.has_value())
stream.appendOctet3(value.sd.value());
}
VRejectedSNssai VRejectedSNssai::Decode(const OctetView &stream)
{
VRejectedSNssai res;
VRejectedSNssai res{};
int octet = stream.readI();
res.cause = static_cast<ERejectedSNssaiCause>(octet & 0xF);
......@@ -212,8 +205,7 @@ VRejectedSNssai VRejectedSNssai::Decode(const OctetView &stream)
return res;
}
VRejectedSNssai::VRejectedSNssai(ERejectedSNssaiCause cause, const std::optional<octet> &sst,
const std::optional<octet3> &sd)
VRejectedSNssai::VRejectedSNssai(ERejectedSNssaiCause cause, octet sst, const std::optional<octet3> &sd)
: cause(cause), sst(sst), sd(sd)
{
}
......@@ -243,7 +235,7 @@ VPartialServiceAreaList VPartialServiceAreaList::Decode(const OctetView &stream)
{
auto octet = stream.peek();
VPartialServiceAreaList res;
VPartialServiceAreaList res{};
res.present = bits::BitRange8<5, 6>(octet);
switch (res.present)
{
......
......@@ -98,12 +98,12 @@ struct VTime
struct VRejectedSNssai
{
ERejectedSNssaiCause cause{};
std::optional<octet> sst{};
octet sst{};
std::optional<octet3> sd{};
VRejectedSNssai() = default;
VRejectedSNssai(ERejectedSNssaiCause cause, const std::optional<octet> &sst, const std::optional<octet3> &sd);
VRejectedSNssai(ERejectedSNssaiCause cause, octet sst, const std::optional<octet3> &sd);
static void Encode(const VRejectedSNssai &value, OctetString &stream);
static VRejectedSNssai Decode(const OctetView &stream);
......
......@@ -99,28 +99,42 @@ static nr::ue::UeConfig *ReadConfigYaml()
auto *result = new nr::ue::UeConfig();
auto config = YAML::LoadFile(g_options.configFile);
result->plmn.mcc = yaml::GetInt32(config, "mcc", 1, 999);
result->hplmn.mcc = yaml::GetInt32(config, "mcc", 1, 999);
yaml::GetString(config, "mcc", 3, 3);
result->plmn.mnc = yaml::GetInt32(config, "mnc", 0, 999);
result->plmn.isLongMnc = yaml::GetString(config, "mnc", 2, 3).size() == 3;
result->hplmn.mnc = yaml::GetInt32(config, "mnc", 0, 999);
result->hplmn.isLongMnc = yaml::GetString(config, "mnc", 2, 3).size() == 3;
for (auto &gnbSearchItem : yaml::GetSequence(config, "gnbSearchList"))
result->gnbSearchList.push_back(gnbSearchItem.as<std::string>());
for (auto &nssai : yaml::GetSequence(config, "slices"))
if (yaml::HasField(config, "default-nssai"))
{
SliceSupport s{};
s.sst = yaml::GetInt32(nssai, "sst", 1, 0xFF);
if (yaml::HasField(nssai, "sd"))
s.sd = octet3{yaml::GetInt32(nssai, "sd", 1, 0xFFFFFF)};
result->nssais.push_back(s);
for (auto &sNssai : yaml::GetSequence(config, "default-nssai"))
{
SingleSlice s{};
s.sst = yaml::GetInt32(sNssai, "sst", 1, 0xFF);
if (yaml::HasField(sNssai, "sd"))
s.sd = octet3{yaml::GetInt32(sNssai, "sd", 1, 0xFFFFFF)};
result->initials.defaultConfiguredNssai.slices.push_back(s);
}
}
if (yaml::HasField(config, "configured-nssai"))
{
for (auto &sNssai : yaml::GetSequence(config, "configured-nssai"))
{
SingleSlice s{};
s.sst = yaml::GetInt32(sNssai, "sst", 1, 0xFF);
if (yaml::HasField(sNssai, "sd"))
s.sd = octet3{yaml::GetInt32(sNssai, "sd", 1, 0xFFFFFF)};
result->initials.configuredNssai.slices.push_back(s);
}
}
result->key = OctetString::FromHex(yaml::GetString(config, "key", 32, 32));
result->opC = OctetString::FromHex(yaml::GetString(config, "op", 32, 32));
result->amf = OctetString::FromHex(yaml::GetString(config, "amf", 4, 4));
result->autoBehaviour = true;
result->configureRouting = !g_options.noRoutingConfigs;
// If we have multiple UEs in the same process, then log names should be separated.
......@@ -162,7 +176,7 @@ static nr::ue::UeConfig *ReadConfigYaml()
if (yaml::HasField(sess, "slice"))
{
auto slice = sess["slice"];
s.sNssai = SliceSupport{};
s.sNssai = SingleSlice{};
s.sNssai->sst = yaml::GetInt32(slice, "sst", 1, 0xFF);
if (yaml::HasField(slice, "sd"))
s.sNssai->sd = octet3{yaml::GetInt32(slice, "sd", 1, 0xFFFFFF)};
......@@ -275,7 +289,6 @@ static void IncrementNumber(std::string &s, int delta)
static nr::ue::UeConfig *GetConfigByUe(int ueIndex)
{
auto *c = new nr::ue::UeConfig();
c->autoBehaviour = g_refConfig->autoBehaviour;
c->key = g_refConfig->key.copy();
c->opC = g_refConfig->opC.copy();
c->opType = g_refConfig->opType;
......@@ -283,8 +296,8 @@ static nr::ue::UeConfig *GetConfigByUe(int ueIndex)
c->imei = g_refConfig->imei;
c->imeiSv = g_refConfig->imeiSv;
c->supi = g_refConfig->supi;
c->plmn = g_refConfig->plmn;
c->nssais = g_refConfig->nssais;
c->hplmn = g_refConfig->hplmn;
c->initials = g_refConfig->initials;
c->supportedAlgs = g_refConfig->supportedAlgs;
c->gnbSearchList = g_refConfig->gnbSearchList;
c->initSessions = g_refConfig->initSessions;
......
......@@ -109,9 +109,11 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg)
{"cm-state", ToJson(m_base->nasTask->mm->m_cmState)},
{"rm-state", ToJson(m_base->nasTask->mm->m_rmState)},
{"mm-state", ToJson(m_base->nasTask->mm->m_mmSubState)},
{"sim-inserted", m_base->nasTask->mm->m_validSim},
{"stored-suci", ToJson(m_base->nasTask->mm->m_storedSuci)},
{"stored-guti", ToJson(m_base->nasTask->mm->m_storedGuti)},
{"5u-state", ToJson(m_base->nasTask->mm->m_storage.m_uState)},
{"sim-inserted", m_base->nasTask->mm->m_storage.isSimValid()},
{"stored-suci", ToJson(m_base->nasTask->mm->m_storage.m_storedSuci)},
{"stored-guti", ToJson(m_base->nasTask->mm->m_storage.m_storedGuti)},
{"has-emergency", ::ToJson(m_base->nasTask->mm->hasEmergency())},
{"pdu-sessions", Json::Arr(std::move(pduSessions))},
});
sendResult(msg.address, json.dumpYaml());
......@@ -127,8 +129,8 @@ void UeCmdHandler::handleCmdImpl(NwUeCliCommand &msg)
}
case app::UeCliCommand::DE_REGISTER: {
m_base->nasTask->mm->sendDeregistration(msg.cmd->isSwitchOff ? nas::ESwitchOff::SWITCH_OFF
: nas::ESwitchOff::NORMAL_DE_REGISTRATION,
msg.cmd->dueToDisable5g);
: nas::ESwitchOff::NORMAL_DE_REGISTRATION,
msg.cmd->dueToDisable5g);
if (!msg.cmd->isSwitchOff)
sendResult(msg.address, "De-registration procedure triggered");
else
......
......@@ -17,6 +17,12 @@ namespace nr::ue
void NasMm::receiveAuthenticationRequest(const nas::AuthenticationRequest &msg)
{
if (!m_storage.isSimValid())
{
m_logger->warn("Authentication request is ignored. USIM is invalid");
return;
}
if (msg.eapMessage.has_value())
receiveAuthenticationRequestEap(msg);
else
......@@ -25,15 +31,16 @@ void NasMm::receiveAuthenticationRequest(const nas::AuthenticationRequest &msg)
void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &msg)
{
// TODO
m_logger->err("EAP AKA' not implemented yet. Use 5G AKA instead");
return;
auto ueRejectionTimers = [this]() {
m_timers->t3520.start();
m_timers->t3520.start();
m_timers->t3510.stop();
m_timers->t3517.stop();
m_timers->t3521.stop();
m_timers->t3510.stop();
m_timers->t3517.stop();
m_timers->t3521.stop();
};
m_timers->t3520.stop();
......@@ -45,9 +52,9 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
auto receivedAutn = receivedEap.attributes.getAutn();
auto receivedKdf = receivedEap.attributes.getKdf();
m_logger->debug("received at_rand: %s", receivedRand.toHexString().c_str());
m_logger->debug("received at_mac: %s", receivedMac.toHexString().c_str());
m_logger->debug("received at_autn: %s", receivedAutn.toHexString().c_str());
// m_logger->debug("received at_rand: %s", receivedRand.toHexString().c_str());
// m_logger->debug("received at_mac: %s", receivedMac.toHexString().c_str());
// m_logger->debug("received at_autn: %s", receivedAutn.toHexString().c_str());
// Derive keys
if (USE_SQN_HACK)
......@@ -61,19 +68,19 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
if (USE_SQN_HACK)
{
auto ak = calculateMilenage(OctetString::FromSpare(6), receivedRand).ak;
m_sqn = OctetString::Xor(receivedAutn.subCopy(0, 6), ak);
m_storage.m_sqn = OctetString::Xor(receivedAutn.subCopy(0, 6), ak);
}
auto milenage = calculateMilenage(m_sqn, receivedRand);
auto milenage = calculateMilenage(m_storage.m_sqn, receivedRand);
auto &res = milenage.res;
auto &ck = milenage.ck;
auto &ik = milenage.ik;
auto &milenageAk = milenage.ak;
auto &milenageMac = milenage.mac_a;
auto sqnXorAk = OctetString::Xor(m_sqn, milenageAk);
auto sqnXorAk = OctetString::Xor(m_storage.m_sqn, milenageAk);
auto ckPrimeIkPrime =
keys::CalculateCkPrimeIkPrime(ck, ik, keys::ConstructServingNetworkName(m_base->config->plmn), sqnXorAk);
keys::CalculateCkPrimeIkPrime(ck, ik, keys::ConstructServingNetworkName(m_storage.m_currentPlmn), sqnXorAk);
auto &ckPrime = ckPrimeIkPrime.first;
auto &ikPrime = ckPrimeIkPrime.second;
......@@ -86,17 +93,17 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
auto mk = keys::CalculateMk(ckPrime, ikPrime, m_base->config->supi.value());
auto kaut = mk.subCopy(16, 32);
m_logger->debug("ueData.sqn: %s", m_sqn.toHexString().c_str());
m_logger->debug("ueData.op(C): %s", m_base->config->opC.toHexString().c_str());
m_logger->debug("ueData.K: %s", m_base->config->key.toHexString().c_str());
m_logger->debug("calculated res: %s", res.toHexString().c_str());
m_logger->debug("calculated ck: %s", ck.toHexString().c_str());
m_logger->debug("calculated ik: %s", ik.toHexString().c_str());
m_logger->debug("calculated milenageAk: %s", milenageAk.toHexString().c_str());
m_logger->debug("calculated milenageMac: %s", milenageMac.toHexString().c_str());
m_logger->debug("calculated ckPrime: %s", ckPrime.toHexString().c_str());
m_logger->debug("calculated ikPrime: %s", ikPrime.toHexString().c_str());
m_logger->debug("calculated kaut: %s", kaut.toHexString().c_str());
// m_logger->debug("ueData.sqn: %s", m_storage.m_sqn.toHexString().c_str());
// m_logger->debug("ueData.op(C): %s", m_base->config->opC.toHexString().c_str());
// m_logger->debug("ueData.K: %s", m_base->config->key.toHexString().c_str());
// m_logger->debug("calculated res: %s", res.toHexString().c_str());
// m_logger->debug("calculated ck: %s", ck.toHexString().c_str());
// m_logger->debug("calculated ik: %s", ik.toHexString().c_str());
// m_logger->debug("calculated milenageAk: %s", milenageAk.toHexString().c_str());
// m_logger->debug("calculated milenageMac: %s", milenageMac.toHexString().c_str());
// m_logger->debug("calculated ckPrime: %s", ckPrime.toHexString().c_str());
// m_logger->debug("calculated ikPrime: %s", ikPrime.toHexString().c_str());
// m_logger->debug("calculated kaut: %s", kaut.toHexString().c_str());
// Control received KDF
if (!IGNORE_CONTROLS_FAILURES && receivedKdf != 1)
......@@ -106,7 +113,7 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
nas::AuthenticationReject resp;
resp.eapMessage = nas::IEEapMessage{};
resp.eapMessage->eap = std::make_unique<eap::EapAkaPrime>(eap::ECode::RESPONSE, receivedEap.id,
eap::ESubType::AKA_AUTHENTICATION_REJECT);
eap::ESubType::AKA_AUTHENTICATION_REJECT);
sendNasMessage(resp);
return;
......@@ -159,14 +166,14 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
if (expectedMac != receivedMac)
{
m_logger->err("AT_MAC failure in EAP AKA'. expected: %s received: %s", expectedMac.toHexString().c_str(),
receivedMac.toHexString().c_str());
receivedMac.toHexString().c_str());
if (!IGNORE_CONTROLS_FAILURES)
{
ueRejectionTimers();
auto eapResponse = std::make_unique<eap::EapAkaPrime>(eap::ECode::RESPONSE, receivedEap.id,
eap::ESubType::AKA_CLIENT_ERROR);
eap::ESubType::AKA_CLIENT_ERROR);
eapResponse->attributes.putClientErrorCode(0);
nas::AuthenticationReject response;
......@@ -182,25 +189,25 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
auto kAusf = keys::CalculateKAusfForEapAkaPrime(mk);
m_logger->debug("kAusf: %s", kAusf.toHexString().c_str());
m_nonCurrentNsCtx = NasSecurityContext{};
m_nonCurrentNsCtx->tsc = msg.ngKSI.tsc;
m_nonCurrentNsCtx->ngKsi = msg.ngKSI.ksi;
m_nonCurrentNsCtx->keys.rand = std::move(receivedRand);
m_nonCurrentNsCtx->keys.res = std::move(res);
m_nonCurrentNsCtx->keys.resStar = {};
m_nonCurrentNsCtx->keys.kAusf = std::move(kAusf);
m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy();
m_storage.m_nonCurrentNsCtx = std::make_unique<NasSecurityContext>();
m_storage.m_nonCurrentNsCtx->tsc = msg.ngKSI.tsc;
m_storage.m_nonCurrentNsCtx->ngKsi = msg.ngKSI.ksi;
m_storage.m_nonCurrentNsCtx->keys.rand = std::move(receivedRand);
m_storage.m_nonCurrentNsCtx->keys.res = std::move(res);
m_storage.m_nonCurrentNsCtx->keys.resStar = {};
m_storage.m_nonCurrentNsCtx->keys.kAusf = std::move(kAusf);
m_storage.m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy();
keys::DeriveKeysSeafAmf(*m_base->config, *m_nonCurrentNsCtx);
keys::DeriveKeysSeafAmf(*m_base->config, m_storage.m_currentPlmn, *m_storage.m_nonCurrentNsCtx);
m_logger->debug("kSeaf: %s", m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str());
m_logger->debug("kAmf: %s", m_nonCurrentNsCtx->keys.kAmf.toHexString().c_str());
// m_logger->debug("kSeaf: %s", m_storage.m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str());
// m_logger->debug("kAmf: %s", m_storage.m_nonCurrentNsCtx->keys.kAmf.toHexString().c_str());
// Send Response
{
auto *akaPrimeResponse =
new eap::EapAkaPrime(eap::ECode::RESPONSE, receivedEap.id, eap::ESubType::AKA_CHALLENGE);
akaPrimeResponse->attributes.putRes(m_nonCurrentNsCtx->keys.res);
akaPrimeResponse->attributes.putRes(m_storage.m_nonCurrentNsCtx->keys.res);
akaPrimeResponse->attributes.putMac(OctetString::FromSpare(16)); // Dummy mac for now
akaPrimeResponse->attributes.putKdf(1);
......@@ -221,9 +228,9 @@ void NasMm::receiveAuthenticationRequestEap(const nas::AuthenticationRequest &ms
void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest &msg)
{
auto sendFailure = [this](nas::EMmCause cause) {
nas::AuthenticationFailure resp;
resp.mmCause.value = cause;
sendNasMessage(resp);
nas::AuthenticationFailure resp{};
resp.mmCause.value = cause;
sendNasMessage(resp);
};
if (USE_SQN_HACK)
......@@ -243,53 +250,54 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest &
auto &rand = msg.authParamRAND->value;
auto &autn = msg.authParamAUTN->value;
m_logger->debug("Received rand[%s] autn[%s]", rand.toHexString().c_str(), autn.toHexString().c_str());
// m_logger->debug("Received rand[%s] autn[%s]", rand.toHexString().c_str(), autn.toHexString().c_str());
if (USE_SQN_HACK)
{
auto ak = calculateMilenage(OctetString::FromSpare(6), rand).ak;
m_sqn = OctetString::Xor(autn.subCopy(0, 6), ak);
m_storage.m_sqn = OctetString::Xor(autn.subCopy(0, 6), ak);
}
auto milenage = calculateMilenage(m_sqn, rand);
auto milenage = calculateMilenage(m_storage.m_sqn, rand);
auto &res = milenage.res;
auto &ck = milenage.ck;
auto &ik = milenage.ik;
auto ckIk = OctetString::Concat(ck, ik);
auto &milenageAk = milenage.ak;
auto &milenageMac = milenage.mac_a;
auto sqnXorAk = OctetString::Xor(m_sqn, milenageAk);
auto snn = keys::ConstructServingNetworkName(m_base->config->plmn);
auto sqnXorAk = OctetString::Xor(m_storage.m_sqn, milenageAk);
auto snn = keys::ConstructServingNetworkName(m_storage.m_currentPlmn);
m_logger->debug("Calculated res[%s] ck[%s] ik[%s] ak[%s] mac_a[%s]", res.toHexString().c_str(),
ck.toHexString().c_str(), ik.toHexString().c_str(), milenageAk.toHexString().c_str(),
milenageMac.toHexString().c_str());
m_logger->debug("Used snn[%s] sqn[%s]", snn.c_str(), m_sqn.toHexString().c_str());
// m_logger->debug("Calculated res[%s] ck[%s] ik[%s] ak[%s] mac_a[%s]", res.toHexString().c_str(),
// ck.toHexString().c_str(), ik.toHexString().c_str(), milenageAk.toHexString().c_str(),
// milenageMac.toHexString().c_str());
// m_logger->debug("Used snn[%s] sqn[%s]", snn.c_str(), m_storage.m_sqn.toHexString().c_str());
auto autnCheck = validateAutn(milenageAk, milenageMac, autn);
if (IGNORE_CONTROLS_FAILURES || autnCheck == EAutnValidationRes::OK)
{
// Create new partial native NAS security context and continue with key derivation
m_nonCurrentNsCtx = NasSecurityContext{};
m_nonCurrentNsCtx->tsc = msg.ngKSI.tsc;
m_nonCurrentNsCtx->ngKsi = msg.ngKSI.ksi;
m_nonCurrentNsCtx->keys.rand = rand.copy();
m_nonCurrentNsCtx->keys.resStar = keys::CalculateResStar(ckIk, snn, rand, res);
m_nonCurrentNsCtx->keys.res = std::move(res);
m_nonCurrentNsCtx->keys.kAusf = keys::CalculateKAusfFor5gAka(ck, ik, snn, sqnXorAk);
m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy();
keys::DeriveKeysSeafAmf(*m_base->config, *m_nonCurrentNsCtx);
m_logger->debug("Derived kSeaf[%s] kAusf[%s] kAmf[%s]", m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str(),
m_nonCurrentNsCtx->keys.kAusf.toHexString().c_str(),
m_nonCurrentNsCtx->keys.kAmf.toHexString().c_str());
m_storage.m_nonCurrentNsCtx = std::make_unique<NasSecurityContext>();
m_storage.m_nonCurrentNsCtx->tsc = msg.ngKSI.tsc;
m_storage.m_nonCurrentNsCtx->ngKsi = msg.ngKSI.ksi;
m_storage.m_nonCurrentNsCtx->keys.rand = rand.copy();
m_storage.m_nonCurrentNsCtx->keys.resStar = keys::CalculateResStar(ckIk, snn, rand, res);
m_storage.m_nonCurrentNsCtx->keys.res = std::move(res);
m_storage.m_nonCurrentNsCtx->keys.kAusf = keys::CalculateKAusfFor5gAka(ck, ik, snn, sqnXorAk);
m_storage.m_nonCurrentNsCtx->keys.abba = msg.abba.rawData.copy();
keys::DeriveKeysSeafAmf(*m_base->config, m_storage.m_currentPlmn, *m_storage.m_nonCurrentNsCtx);
// m_logger->debug("Derived kSeaf[%s] kAusf[%s] kAmf[%s]",
// m_storage.m_nonCurrentNsCtx->keys.kSeaf.toHexString().c_str(),
// m_storage.m_nonCurrentNsCtx->keys.kAusf.toHexString().c_str(),
// m_storage.m_nonCurrentNsCtx->keys.kAmf.toHexString().c_str());
// Send response
nas::AuthenticationResponse resp;
resp.authenticationResponseParameter = nas::IEAuthenticationResponseParameter{};
resp.authenticationResponseParameter->rawData = m_nonCurrentNsCtx->keys.resStar.copy();
resp.authenticationResponseParameter->rawData = m_storage.m_nonCurrentNsCtx->keys.resStar.copy();
sendNasMessage(resp);
}
else if (autnCheck == EAutnValidationRes::MAC_FAILURE)
......@@ -311,7 +319,7 @@ void NasMm::receiveAuthenticationRequest5gAka(const nas::AuthenticationRequest &
void NasMm::receiveAuthenticationResult(const nas::AuthenticationResult &msg)
{
if (msg.abba.has_value())
m_nonCurrentNsCtx->keys.abba = msg.abba->rawData.copy();
m_storage.m_nonCurrentNsCtx->keys.abba = msg.abba->rawData.copy();
if (msg.eapMessage.eap->code == eap::ECode::SUCCESS)
receiveEapSuccessMessage(*msg.eapMessage.eap);
......@@ -336,23 +344,31 @@ void NasMm::receiveAuthenticationReject(const nas::AuthenticationReject &msg)
{
m_logger->err("Authentication Reject received.");
if (msg.eapMessage.has_value())
if (msg.eapMessage.has_value() && msg.eapMessage->eap->code != eap::ECode::FAILURE)
{
if (msg.eapMessage->eap->code == eap::ECode::FAILURE)
{
m_storedGuti = {};
m_taiList = {};
m_lastVisitedRegisteredTai = {};
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
receiveEapFailureMessage(*msg.eapMessage->eap);
}
else
{
m_logger->warn("Network sent EAP with inconvenient type in AuthenticationReject, ignoring EAP IE.");
}
m_logger->warn("Network sent EAP with inconvenient type in AuthenticationReject, ignoring EAP IE.");
return;
}
// The UE shall set the update status to 5U3 ROAMING NOT ALLOWED,
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
// Delete the stored 5G-GUTI, TAI list, last visited registered TAI and ngKSI. The USIM shall be considered invalid
// until switching off the UE or the UICC containing the USIM is removed
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
m_storage.invalidateSim();
// The UE shall abort any 5GMM signalling procedure, stop any of the timers T3510, T3516, T3517, T3519 or T3521 (if
// they were running) ..
m_timers->t3510.stop();
m_timers->t3516.stop();
m_timers->t3517.stop();
m_timers->t3519.stop();
m_timers->t3521.stop();
// .. and enter state 5GMM-DEREGISTERED.
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
}
void NasMm::receiveEapSuccessMessage(const eap::Eap &eap)
......@@ -363,7 +379,7 @@ void NasMm::receiveEapSuccessMessage(const eap::Eap &eap)
void NasMm::receiveEapFailureMessage(const eap::Eap &eap)
{
m_logger->err("EAP failure received. Deleting non-current NAS security context");
m_nonCurrentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
}
void NasMm::receiveEapResponseMessage(const eap::Eap &eap)
......@@ -390,7 +406,7 @@ EAutnValidationRes NasMm::validateAutn(const OctetString &ak, const OctetString
if (receivedMAC != mac)
{
m_logger->err("AUTN validation MAC mismatch. expected: %s received: %s", mac.toHexString().c_str(),
receivedMAC.toHexString().c_str());
receivedMAC.toHexString().c_str());
return EAutnValidationRes::MAC_FAILURE;
}
......@@ -428,5 +444,4 @@ crypto::milenage::Milenage NasMm::calculateMilenage(const OctetString &sqn, cons
return crypto::milenage::Calculate(opc, m_base->config->key, rand, sqn, m_base->config->amf);
}
}
\ No newline at end of file
} // namespace nr::ue
\ No newline at end of file
......@@ -25,9 +25,10 @@ NasMm::NasMm(TaskBase *base, UeTimers *timers) : m_base{base}, m_timers{timers},
m_cmState = ECmState::CM_IDLE;
m_mmState = EMmState::MM_DEREGISTERED;
m_mmSubState = EMmSubState::MM_DEREGISTERED_NA;
m_uState = E5UState::U1_UPDATED;
m_autoBehaviour = base->config->autoBehaviour;
m_validSim = base->config->supi.has_value();
m_storage.m_uState = E5UState::U1_UPDATED;
m_storage.initialize(base->config->supi.has_value(), base->config->initials);
m_storage.m_currentPlmn = base->config->hplmn; // TODO: normally assigned after plmn search
}
void NasMm::onStart(NasSm *sm)
......@@ -52,7 +53,7 @@ void NasMm::performMmCycle()
if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NA)
{
if (m_validSim)
if (m_storage.isSimValid())
{
if (m_cmState == ECmState::CM_IDLE)
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH);
......@@ -81,8 +82,8 @@ void NasMm::performMmCycle()
if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE)
{
if (m_autoBehaviour && !m_timers->t3346.isRunning())
sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, nas::EFollowOnRequest::FOR_PENDING);
if (!m_timers->t3346.isRunning())
sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, false);
return;
}
......@@ -96,12 +97,6 @@ void NasMm::performMmCycle()
return;
if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NO_SUPI)
return;
if (m_autoBehaviour)
{
m_logger->err("unhandled UE MM state");
return;
}
}
void NasMm::switchMmState(EMmState state, EMmSubState subState)
......@@ -168,15 +163,15 @@ void NasMm::switchCmState(ECmState state)
void NasMm::switchUState(E5UState state)
{
E5UState oldState = m_uState;
m_uState = state;
E5UState oldState = m_storage.m_uState;
m_storage.m_uState = state;
onSwitchUState(oldState, m_uState);
onSwitchUState(oldState, m_storage.m_uState);
if (m_base->nodeListener)
{
m_base->nodeListener->onSwitch(app::NodeType::UE, m_base->config->getNodeName(), app::StateType::U5,
ToJson(oldState).str(), ToJson(m_uState).str());
ToJson(oldState).str(), ToJson(m_storage.m_uState).str());
}
if (state != oldState)
......@@ -192,12 +187,12 @@ void NasMm::onSwitchMmState(EMmState oldState, EMmState newState, EMmSubState ol
// 5GMM-DEREGISTERED for any other state except 5GMM-NULL.
if (oldState == EMmState::MM_DEREGISTERED && newState != EMmState::MM_DEREGISTERED && newState != EMmState::MM_NULL)
{
if (m_currentNsCtx.has_value() || m_nonCurrentNsCtx.has_value())
if (m_storage.m_currentNsCtx || m_storage.m_nonCurrentNsCtx)
{
m_logger->debug("Deleting NAS security context");
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
}
}
}
......@@ -210,8 +205,21 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState)
{
if (oldState == ECmState::CM_CONNECTED && newState == ECmState::CM_IDLE)
{
// 5.5.1.2.7 Abnormal cases in the UE (in registration)
if (m_mmState == EMmState::MM_REGISTERED_INITIATED)
{
// e) Lower layer failure or release of the NAS signalling connection received from lower layers before the
// REGISTRATION ACCEPT or REGISTRATION REJECT message is received. The UE shall abort the registration
// procedure for initial registration and proceed as ...
switchRmState(ERmState::RM_DEREGISTERED);
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
switchUState(E5UState::U2_NOT_UPDATED);
handleCommonAbnormalRegFailure(m_lastRegistrationRequest->registrationType.registrationType);
}
// 5.5.2.2.6 Abnormal cases in the UE (in de-registration)
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED)
else if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED)
{
// The de-registration procedure shall be aborted and the UE proceeds as follows:
// if the de-registration procedure was performed due to disabling of 5GS services, the UE shall enter the
......@@ -224,7 +232,8 @@ void NasMm::onSwitchCmState(ECmState oldState, ECmState newState)
nas::ESwitchOff::NORMAL_DE_REGISTRATION)
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
m_lastDeregistrationRequest = nullptr;
switchRmState(ERmState::RM_DEREGISTERED);
m_lastDeregDueToDisable5g = false;
}
}
......@@ -234,69 +243,25 @@ void NasMm::onSwitchUState(E5UState oldState, E5UState newState)
{
}
void NasMm::onTimerExpire(nas::NasTimer &timer)
void NasMm::setN1Capability(bool enabled)
{
switch (timer.getCode())
{
case 3346: {
if (m_autoBehaviour && m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE)
{
sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, nas::EFollowOnRequest::FOR_PENDING);
}
break;
}
case 3512: {
if (m_autoBehaviour && m_mmState == EMmState::MM_REGISTERED && m_cmState == ECmState::CM_CONNECTED)
{
sendRegistration(nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING,
nas::EFollowOnRequest::FOR_PENDING);
}
break;
}
case 3521: {
if (timer.getExpiryCount() == 5)
{
timer.resetExpiryCount();
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr)
{
m_logger->debug("De-registration aborted");
if (m_lastDeregDueToDisable5g)
switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA);
else if (m_lastDeregistrationRequest->deRegistrationType.switchOff ==
nas::ESwitchOff::NORMAL_DE_REGISTRATION)
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
}
}
else
{
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr)
{
m_logger->debug("Retrying de-registration request");
sendNasMessage(*m_lastDeregistrationRequest);
m_timers->t3521.start(false);
}
}
break;
}
}
}
void NasMm::invalidateAcquiredParams()
{
m_storedGuti = {};
m_lastVisitedRegisteredTai = {};
m_taiList = {};
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
// TODO
}
void NasMm::invalidateSim()
bool NasMm::hasEmergency()
{
m_logger->warn("USIM is removed or invalidated");
m_validSim = false;
invalidateAcquiredParams();
// Indicates emergency services are required (even if registered for normal initial registration)
// This happens if it 'has' or 'need' some emergency PDU Session, as well.
if (m_rmState == ERmState::RM_REGISTERED && m_registeredForEmergency)
return true;
if (m_mmState == EMmState::MM_REGISTERED_INITIATED && m_lastRegistrationRequest &&
m_lastRegistrationRequest->registrationType.registrationType == nas::ERegistrationType::EMERGENCY_REGISTRATION)
return true;
// TODO: Other case which is an emergency PDU session is established, or need to be established (and wanted to be
// established soon)
return false;
}
} // namespace nr::ue
......@@ -17,13 +17,13 @@ void NasMm::receiveConfigurationUpdate(const nas::ConfigurationUpdateCommand &ms
if (msg.guti.has_value() && msg.guti->type == nas::EIdentityType::GUTI)
{
m_storedGuti = msg.guti.value();
m_storedSuci = {};
m_storage.m_storedSuci = {};
m_storage.m_storedGuti = *msg.guti;
m_timers->t3519.stop();
}
if (msg.taiList.has_value())
m_taiList = msg.taiList.value();
m_storage.m_taiList = msg.taiList.value();
if (msg.configurationUpdateIndication.has_value())
{
......
......@@ -30,10 +30,10 @@ void NasMm::sendDeregistration(nas::ESwitchOff switchOff, bool dueToDisable5g)
request->deRegistrationType.reRegistrationRequired = nas::EReRegistrationRequired::NOT_REQUIRED;
request->deRegistrationType.switchOff = switchOff;
if (m_currentNsCtx.has_value())
if (m_storage.m_currentNsCtx)
{
request->ngKSI.tsc = m_currentNsCtx->tsc;
request->ngKSI.ksi = m_currentNsCtx->ngKsi;
request->ngKSI.tsc = m_storage.m_currentNsCtx->tsc;
request->ngKSI.ksi = m_storage.m_currentNsCtx->ngKsi;
}
else
{
......@@ -76,7 +76,7 @@ void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOrigina
m_timers->t3521.stop();
m_timers->t3519.stop();
m_storedSuci = {};
m_storage.m_storedSuci = {};
switchRmState(ERmState::RM_DEREGISTERED);
......@@ -85,7 +85,6 @@ void NasMm::receiveDeregistrationAccept(const nas::DeRegistrationAcceptUeOrigina
else
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
m_lastDeregistrationRequest = nullptr;
m_lastDeregDueToDisable5g = false;
m_logger->info("De-registration is successful");
......@@ -109,9 +108,16 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi
m_logger->debug("Network initiated de-registration request received");
bool forceIgnoreReregistration = false;
bool forceLocalReleaseNas = false;
// 5.5.1.2.7 Abnormal cases in the UE (de-registration collision)
if (m_mmState == EMmState::MM_REGISTERED_INITIATED)
{
if (msg.deRegistrationType.reRegistrationRequired == nas::EReRegistrationRequired::REQUIRED)
forceLocalReleaseNas = true;
}
// 5.5.2.2.6 Abnormal cases in the UE (de-registration collision)
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED)
else if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED)
{
// De-registration containing de-registration type "switch off", If the UE receives a DEREGISTRATION REQUEST
// message before the UE-initiated de-registration procedure has been completed, this message shall be ignored
......@@ -162,7 +168,12 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi
case nas::EMmCause::ILLEGAL_ME:
case nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED: {
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
invalidateSim();
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
m_storage.invalidateSim();
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
break;
}
......@@ -175,13 +186,21 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi
//}
case nas::EMmCause::TA_NOT_ALLOWED: {
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
invalidateAcquiredParams();
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE);
break;
}
case nas::EMmCause::N1_MODE_NOT_ALLOWED: {
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
invalidateAcquiredParams();
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA);
break;
}
......@@ -198,13 +217,25 @@ void NasMm::receiveDeregistrationRequest(const nas::DeRegistrationRequestUeTermi
nas::utils::EnumToString(msg.mmCause->value));
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
invalidateSim();
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
m_storage.invalidateSim();
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
break;
}
}
}
}
if (reRegistrationRequired)
{
// TODO: Perform re-registration, but consider forceLocalReleaseNas
// if 'forceLocalReleaseNas' is true, local release nas before re-registration.
// See "5.5.1.2.7 g) De-registration procedure collision."
}
}
} // namespace nr::ue
......@@ -42,22 +42,21 @@ void NasMm::receiveIdentityRequest(const nas::IdentityRequest &msg)
nas::IE5gsMobileIdentity NasMm::getOrGenerateSuci()
{
if (m_timers->t3519.isRunning())
return m_storedSuci;
m_storedSuci = generateSuci();
if (m_timers->t3519.isRunning() && m_storage.m_storedSuci.type != nas::EIdentityType::NO_IDENTITY)
return m_storage.m_storedSuci;
m_storage.m_storedSuci = generateSuci();
m_timers->t3519.start();
if (m_storedSuci.type == nas::EIdentityType::NO_IDENTITY)
if (m_storage.m_storedSuci.type == nas::EIdentityType::NO_IDENTITY)
return {};
return m_storedSuci;
return m_storage.m_storedSuci;
}
nas::IE5gsMobileIdentity NasMm::generateSuci()
{
auto &supi = m_base->config->supi;
auto &plmn = m_base->config->plmn;
auto &plmn = m_storage.m_currentPlmn;
if (!supi.has_value())
return {};
......@@ -69,14 +68,6 @@ nas::IE5gsMobileIdentity NasMm::generateSuci()
}
const std::string &imsi = supi->value;
int mccInImsi = utils::ParseInt(imsi.substr(0, 3));
int mncInImsi = utils::ParseInt(imsi.substr(3, plmn.isLongMnc ? 3 : 2));
if (mccInImsi != plmn.mcc || mncInImsi != plmn.mnc)
{
m_logger->err("MCC/MNC mismatch in SUCI generation.");
return {};
}
nas::IE5gsMobileIdentity ret;
ret.type = nas::EIdentityType::SUCI;
......@@ -93,35 +84,33 @@ nas::IE5gsMobileIdentity NasMm::generateSuci()
nas::IE5gsMobileIdentity NasMm::getOrGeneratePreferredId()
{
if (m_storedGuti.type != nas::EIdentityType::NO_IDENTITY)
return m_storedGuti;
if (m_storage.m_storedGuti.type != nas::EIdentityType::NO_IDENTITY)
return m_storage.m_storedGuti;
auto suci = getOrGenerateSuci();
if (suci.type != nas::EIdentityType::NO_IDENTITY)
{
return suci;
}
else if (m_base->config->imei.has_value())
{
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::IMEI;
res.value = *m_base->config->imei;
return res;
}
else if (m_base->config->imeiSv.has_value())
{
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::IMEISV;
res.value = *m_base->config->imeiSv;
return res;
}
else
{
auto suci = getOrGenerateSuci();
if (suci.type != nas::EIdentityType::NO_IDENTITY)
{
return suci;
}
else if (m_base->config->imei.has_value())
{
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::IMEI;
res.value = *m_base->config->imei;
return res;
}
else if (m_base->config->imeiSv.has_value())
{
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::IMEISV;
res.value = *m_base->config->imeiSv;
return res;
}
else
{
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::NO_IDENTITY;
return res;
}
nas::IE5gsMobileIdentity res{};
res.type = nas::EIdentityType::NO_IDENTITY;
return res;
}
}
......
......@@ -11,6 +11,7 @@
#include <crypt/milenage.hpp>
#include <nas/nas.hpp>
#include <nas/timer.hpp>
#include <ue/nas/storage.hpp>
#include <ue/nts.hpp>
#include <ue/types.hpp>
#include <utils/nts.hpp>
......@@ -28,31 +29,27 @@ class NasMm
UeTimers *m_timers;
std::unique_ptr<Logger> m_logger;
NasSm *m_sm;
MobileStorage m_storage{};
ERmState m_rmState;
ECmState m_cmState;
EMmState m_mmState;
EMmSubState m_mmSubState;
E5UState m_uState;
nas::IE5gsMobileIdentity m_storedSuci{};
nas::IE5gsMobileIdentity m_storedGuti{};
// Most recent registration request
std::unique_ptr<nas::RegistrationRequest> m_lastRegistrationRequest{};
// Most recent de-registration request
std::unique_ptr<nas::DeRegistrationRequestUeOriginating> m_lastDeregistrationRequest{};
// Indicates that the last de-registration request is issued due to disable 5G services
bool m_lastDeregDueToDisable5g{};
std::optional<nas::IE5gsTrackingAreaIdentity> m_lastVisitedRegisteredTai{};
std::optional<nas::IE5gsTrackingAreaIdentityList> m_taiList{};
std::optional<NasSecurityContext> m_currentNsCtx;
std::optional<NasSecurityContext> m_nonCurrentNsCtx;
bool m_autoBehaviour;
bool m_validSim;
// Last time PLMN search is triggered
long m_lastPlmnSearchTrigger{};
OctetString m_sqn{};
// Registration attempt counter
int m_regCounter{};
// Indicates registered for emergency services (Only meaningful in RM-REGISTERED state)
bool m_registeredForEmergency{};
// Network feature support information
nas::IE5gsNetworkFeatureSupport m_nwFeatureSupport{};
friend class UeCmdHandler;
......@@ -65,9 +62,8 @@ class NasMm
void onQuit();
void triggerMmCycle();
void performMmCycle();
void onTimerExpire(nas::NasTimer &timer);
/* Radio resource control */
/* Radio */
void handlePlmnSearchResponse(const std::string &gnbName);
void handlePlmnSearchFailure();
void handleRrcConnectionSetup();
......@@ -81,6 +77,9 @@ class NasMm
/* De-registration */
void sendDeregistration(nas::ESwitchOff switchOff, bool dueToDisable5g);
/* Timer */
void onTimerExpire(nas::NasTimer &timer);
private:
/* Base */
void switchMmState(EMmState state, EMmSubState subState);
......@@ -91,8 +90,8 @@ class NasMm
void onSwitchRmState(ERmState oldState, ERmState newState);
void onSwitchCmState(ECmState oldState, ECmState newState);
void onSwitchUState(E5UState oldState, E5UState newState);
void invalidateAcquiredParams();
void invalidateSim();
void setN1Capability(bool enabled);
bool hasEmergency();
/* Transport */
void sendMmStatus(nas::EMmCause cause);
......@@ -102,9 +101,10 @@ class NasMm
void receiveMmCause(const nas::IE5gMmCause &msg);
/* Registration */
void sendRegistration(nas::ERegistrationType registrationType, nas::EFollowOnRequest followOn);
void sendRegistration(nas::ERegistrationType regType, bool dueToDereg);
void receiveRegistrationAccept(const nas::RegistrationAccept &msg);
void receiveRegistrationReject(const nas::RegistrationReject &msg);
void handleCommonAbnormalRegFailure(nas::ERegistrationType regType);
/* Authentication */
void receiveAuthenticationRequest(const nas::AuthenticationRequest &msg);
......@@ -140,6 +140,13 @@ class NasMm
/* Service */
void receiveServiceAccept(const nas::ServiceAccept &msg);
void receiveServiceReject(const nas::ServiceReject &msg);
/* Network Slicing */
NetworkSlice makeRequestedNssai(bool &isDefaultNssai) const;
void handleNetworkSlicingSubscriptionChange();
/* Radio */
void localReleaseConnection();
};
} // namespace nr::ue
\ No newline at end of file
......@@ -9,6 +9,7 @@
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/rrc/task.hpp>
#include <ue/sm/sm.hpp>
namespace nr::ue
......@@ -54,4 +55,11 @@ void NasMm::handleRadioLinkFailure()
handleRrcConnectionRelease();
}
void NasMm::localReleaseConnection()
{
m_logger->info("Performing local release of NAS connection");
m_base->rrcTask->push(new NwUeNasToRrc(NwUeNasToRrc::LOCAL_RELEASE_CONNECTION));
}
} // namespace nr::ue
\ No newline at end of file
......@@ -8,41 +8,78 @@
#include "mm.hpp"
#include <algorithm>
#include <nas/utils.hpp>
#include <ue/nas/task.hpp>
#include <utils/common.hpp>
namespace nr::ue
{
void NasMm::sendRegistration(nas::ERegistrationType registrationType, nas::EFollowOnRequest followOn)
void NasMm::sendRegistration(nas::ERegistrationType regType, bool dueToDereg)
{
// The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE
// initiates an initial registration procedure
if (registrationType == nas::ERegistrationType::INITIAL_REGISTRATION)
if (m_mmState != EMmState::MM_DEREGISTERED)
{
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
m_logger->warn("Registration could not be triggered. UE is not in MM-DEREGISTERED state.");
return;
}
// 5.5.1.2.7 Abnormal cases in the UE
// a) Timer T3346 is running.
if (m_timers->t3346.isRunning() && regType == nas::ERegistrationType::INITIAL_REGISTRATION && !hasEmergency())
{
// From 24.501: A UE configured with one or more access identities equal to 1, 2, or 11-15 applicable in the
// selected PLMN as specified in subclause 4.5.2. Definition derived from 3GPP TS 22.261
bool isHighPriority = false; // TODO: Assign this property properly.
// The UE shall not start the registration procedure for initial registration in the following case
if (!isHighPriority && !dueToDereg)
{
m_logger->debug("Initial registration canceled, T3346 is running");
return;
}
}
m_logger->debug("Sending %s", nas::utils::EnumToString(regType));
// The UE shall mark the 5G NAS security context on the USIM or in the non-volatile memory as invalid when the UE
// initiates an initial registration procedure
if (regType == nas::ERegistrationType::INITIAL_REGISTRATION)
m_storage.m_currentNsCtx = {};
// Switch MM state
switchMmState(EMmState::MM_REGISTERED_INITIATED, EMmSubState::MM_REGISTERED_INITIATED_NA);
nas::IENasKeySetIdentifier ngKsi;
// Prepare requested NSSAI
bool isDefaultNssai{};
auto requestedNssai = makeRequestedNssai(isDefaultNssai);
if (m_currentNsCtx.has_value())
{
ngKsi.tsc = m_currentNsCtx->tsc;
ngKsi.ksi = m_currentNsCtx->ngKsi;
}
// Prepare FOR pending field
nas::EFollowOnRequest followOn = nas::EFollowOnRequest::FOR_PENDING;
// Create registration request
auto request = std::make_unique<nas::RegistrationRequest>();
request->registrationType = nas::IE5gsRegistrationType{followOn, registrationType};
request->nasKeySetIdentifier = ngKsi;
request->requestedNSSAI = nas::utils::NssaiFrom(m_base->config->nssais);
request->registrationType = nas::IE5gsRegistrationType{followOn, regType};
if (m_storage.m_currentNsCtx)
{
request->nasKeySetIdentifier.tsc = m_storage.m_currentNsCtx->tsc;
request->nasKeySetIdentifier.ksi = m_storage.m_currentNsCtx->ngKsi;
}
request->requestedNSSAI = nas::utils::NssaiFrom(requestedNssai);
request->ueSecurityCapability = createSecurityCapabilityIe();
request->updateType =
nas::IE5gsUpdateType(nas::ESmsRequested::NOT_SUPPORTED, nas::ENgRanRadioCapabilityUpdate::NOT_NEEDED);
if (registrationType != nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING)
#if 0
// TODO: Wireshark cannot decode the message if this IE is used, check later
request->networkSlicingIndication = nas::IENetworkSlicingIndication{};
request->networkSlicingIndication->dcni =
isDefaultNssai ? nas::EDefaultConfiguredNssaiIndication::CREATED_FROM_DEFAULT_CONFIGURED_NSSAI
: nas::EDefaultConfiguredNssaiIndication::NOT_CREATED_FROM_DEFAULT_CONFIGURED_NSSAI;
#endif
// MM capability should be included if it is not a periodic registration
if (regType != nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING)
{
request->mmCapability = nas::IE5gMmCapability{};
request->mmCapability->s1Mode = nas::EEpcNasSupported::NOT_SUPPORTED;
......@@ -50,10 +87,12 @@ void NasMm::sendRegistration(nas::ERegistrationType registrationType, nas::EFoll
request->mmCapability->lpp = nas::ELtePositioningProtocolCapability::NOT_SUPPORTED;
}
// Assign mobile identity
request->mobileIdentity = getOrGeneratePreferredId();
if (m_lastVisitedRegisteredTai.has_value())
request->lastVisitedRegisteredTai = m_lastVisitedRegisteredTai.value();
// Assign last visited registered TAI if available
if (m_storage.m_lastVisitedRegisteredTai.has_value())
request->lastVisitedRegisteredTai = *m_storage.m_lastVisitedRegisteredTai;
m_timers->t3510.start();
m_timers->t3502.stop();
......@@ -67,46 +106,140 @@ void NasMm::receiveRegistrationAccept(const nas::RegistrationAccept &msg)
{
if (m_mmState != EMmState::MM_REGISTERED_INITIATED)
{
m_logger->warn("Registration Accept ignored since the MM state is MM_REGISTERED_INITIATED");
m_logger->warn("Registration Accept ignored since the MM state is not MM_REGISTERED_INITIATED");
sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE);
return;
}
m_logger->debug("Registration accept received");
if (msg.registrationResult.registrationResult == nas::E5gsRegistrationResult::NON_THREEGPP_ACCESS)
{
m_logger->err("Non-3GPP registration accept is ignored");
sendMmStatus(nas::EMmCause::SEMANTICALLY_INCORRECT_MESSAGE);
return;
}
bool sendCompleteMes = false;
// Store the TAI list as a registration area
m_storage.m_taiList = msg.taiList.value_or(nas::IE5gsTrackingAreaIdentityList{});
// Store the service area list
m_storage.m_serviceAreaList = msg.serviceAreaList.value_or(nas::IEServiceAreaList{});
// Store the E-PLMN list and ..
m_storage.m_equivalentPlmnList = msg.equivalentPLMNs.value_or(nas::IEPlmnList{});
// .. if the initial registration procedure is not for emergency services, the UE shall remove from the list any
// PLMN code that is already in the list of "forbidden PLMNs". ..
if (!hasEmergency())
{
utils::EraseWhere(m_storage.m_equivalentPlmnList.plmns, [this](auto &plmn) {
return std::any_of(m_storage.m_forbiddenPlmnList.plmns.begin(), m_storage.m_forbiddenPlmnList.plmns.end(),
[&plmn](auto &forbidden) { return nas::utils::DeepEqualsV(plmn, forbidden); });
});
}
// .. in addition, the UE shall add to the stored list the PLMN code of the registered PLMN that sent the list
nas::utils::AddToPlmnList(m_storage.m_equivalentPlmnList, nas::utils::PlmnFrom(m_storage.m_currentPlmn));
m_taiList = msg.taiList;
// Upon receipt of the REGISTRATION ACCEPT message, the UE shall reset the registration attempt counter, enter state
// 5GMM-REGISTERED and set the 5GS update status to 5U1 UPDATED.
m_regCounter = 0;
switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE);
switchRmState(ERmState::RM_REGISTERED);
switchUState(E5UState::U1_UPDATED);
// If the REGISTRATION ACCEPT message included a T3512 value IE, the UE shall use the value in the T3512 value IE as
// periodic registration update timer (T3512).
if (msg.t3512Value.has_value() && nas::utils::HasValue(msg.t3512Value.value()))
{
m_timers->t3512.start(*msg.t3512Value);
m_logger->debug("T3512 started with int[%d]", m_timers->t3512.getInterval());
}
if (msg.mobileIdentity.has_value() && msg.mobileIdentity->type == nas::EIdentityType::GUTI)
// Registration complete is sent conditionally
bool sendComplete = false;
// Process the received GUTI
if (msg.mobileIdentity.has_value())
{
m_storedGuti = msg.mobileIdentity.value();
m_timers->t3519.stop();
if (msg.mobileIdentity->type == nas::EIdentityType::GUTI)
{
m_storage.m_storedGuti = *msg.mobileIdentity;
m_timers->t3519.stop();
sendComplete = true;
}
else
{
m_logger->warn("GUTI was expected in registration accept but another identity type received");
}
}
// Process rejected NSSAI
if (msg.rejectedNSSAI.has_value())
{
for (auto &rejectedSlice : msg.rejectedNSSAI->list)
{
SingleSlice slice{};
slice.sst = rejectedSlice.sst;
slice.sd = rejectedSlice.sd;
sendCompleteMes = true;
auto &list = rejectedSlice.cause == nas::ERejectedSNssaiCause::NA_IN_PLMN ? m_storage.m_rejectedNssaiInPlmn
: m_storage.m_rejectedNssaiInTa;
list.addIfNotExists(slice);
}
}
if (sendCompleteMes)
sendNasMessage(nas::RegistrationComplete{});
// Process network slicing subscription indication
if (msg.networkSlicingIndication.has_value() &&
msg.networkSlicingIndication->nssci == nas::ENetworkSlicingSubscriptionChangeIndication::CHANGED)
{
handleNetworkSlicingSubscriptionChange();
sendComplete = true;
}
switchMmState(EMmState::MM_REGISTERED, EMmSubState::MM_REGISTERED_NORMAL_SERVICE);
switchRmState(ERmState::RM_REGISTERED);
// Store the allowed NSSAI
m_storage.m_allowedNssai = nas::utils::NssaiTo(msg.allowedNSSAI.value_or(nas::IENssai{}));
// Process configured NSSAI IE
if (msg.configuredNSSAI.has_value())
{
m_storage.m_configuredNssai = nas::utils::NssaiTo(msg.configuredNSSAI.value_or(nas::IENssai{}));
sendComplete = true;
}
// Store the network feature support
m_nwFeatureSupport = msg.networkFeatureSupport.value_or(nas::IE5gsNetworkFeatureSupport{});
if (sendComplete)
sendNasMessage(nas::RegistrationComplete{});
auto regType = m_lastRegistrationRequest->registrationType.registrationType;
m_logger->info("%s is successful", nas::utils::EnumToString(regType));
if (regType == nas::ERegistrationType::INITIAL_REGISTRATION ||
regType == nas::ERegistrationType::EMERGENCY_REGISTRATION)
{
m_base->nasTask->push(new NwUeNasToNas(NwUeNasToNas::ESTABLISH_INITIAL_SESSIONS));
}
if (regType == nas::ERegistrationType::INITIAL_REGISTRATION)
m_registeredForEmergency = false;
else if (regType == nas::ERegistrationType::EMERGENCY_REGISTRATION)
m_registeredForEmergency = true;
else
{
// Other registration types will *not* alter 'is registered for emergency' state
}
m_logger->info("%s is successful", nas::utils::EnumToString(regType));
}
void NasMm::receiveRegistrationReject(const nas::RegistrationReject &msg)
{
if (m_mmState != EMmState::MM_REGISTERED_INITIATED)
{
m_logger->warn("Registration Reject ignored since the MM state is not MM_REGISTERED_INITIATED");
sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE);
return;
}
auto cause = msg.mmCause.value;
auto regType = m_lastRegistrationRequest->registrationType.registrationType;
......@@ -121,10 +254,21 @@ void NasMm::receiveRegistrationReject(const nas::RegistrationReject &msg)
nas::utils::EnumToString(msg.eapMessage->eap->code));
}
auto unhandledRejectCase = [cause, this]() {
m_logger->err("Registration rejected with unhandled MMCause: %s", nas::utils::EnumToString(cause));
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
switchRmState(ERmState::RM_DEREGISTERED);
switchRmState(ERmState::RM_DEREGISTERED);
auto handleAbnormalCase = [this, regType, cause]() {
m_logger->debug("Handling Registration Reject abnormal case");
// If the registration request is not an initial registration request for emergency services, upon reception of
// the 5GMM causes #95, #96, #97, #99 and #111 the UE should set the registration attempt counter to 5.
if (regType == nas::ERegistrationType::INITIAL_REGISTRATION)
{
int n = static_cast<int>(cause);
if (n == 95 || n == 96 || n == 97 || n == 99 || n == 111)
m_regCounter = 5;
}
handleCommonAbnormalRegFailure(regType);
};
if (regType == nas::ERegistrationType::INITIAL_REGISTRATION)
......@@ -132,27 +276,86 @@ void NasMm::receiveRegistrationReject(const nas::RegistrationReject &msg)
if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME ||
cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || cause == nas::EMmCause::PLMN_NOT_ALLOWED ||
cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA ||
cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA)
cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED)
{
switchUState(E5UState::U3_ROAMING_NOT_ALLOWED);
}
if (cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED)
{
switchUState(E5UState::U2_NOT_UPDATED);
}
if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME ||
cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED || cause == nas::EMmCause::PLMN_NOT_ALLOWED ||
cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA ||
cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA || cause == nas::EMmCause::N1_MODE_NOT_ALLOWED)
{
m_storage.m_storedGuti = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_taiList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
}
if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME ||
cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED)
{
m_storedGuti = {};
m_lastVisitedRegisteredTai = {};
m_taiList = {};
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
m_storage.invalidateSim();
}
// TODO Normally UE switches to PLMN SEARCH, but this leads to endless registration attempt again and again.
// due to RLS.
// switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH);
if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME ||
cause == nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED)
{
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
}
if (cause == nas::EMmCause::TA_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA ||
cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA)
{
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_LIMITED_SERVICE);
}
if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED)
{
switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA);
setN1Capability(false);
}
if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED)
{
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_PLMN_SEARCH);
}
if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::TA_NOT_ALLOWED ||
cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA ||
cause == nas::EMmCause::N1_MODE_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED)
{
m_regCounter = 0;
}
if (cause == nas::EMmCause::ILLEGAL_UE || cause == nas::EMmCause::ILLEGAL_ME ||
cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA)
{
m_storage.m_equivalentPlmnList = {};
}
if (cause == nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA || cause == nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA)
{
// TODO add to forbidden tai
}
switchRmState(ERmState::RM_DEREGISTERED);
if (cause == nas::EMmCause::PLMN_NOT_ALLOWED || cause == nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED)
{
nas::utils::AddToPlmnList(m_storage.m_forbiddenPlmnList, nas::utils::PlmnFrom(m_storage.m_currentPlmn));
}
else if (cause == nas::EMmCause::CONGESTION)
if (cause == nas::EMmCause::CONGESTION)
{
if (msg.t3346value.has_value() && nas::utils::HasValue(*msg.t3346value))
{
switchUState(E5UState::U2_NOT_UPDATED);
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION);
switchRmState(ERmState::RM_DEREGISTERED);
m_timers->t3346.stop();
if (msg.sht != nas::ESecurityHeaderType::NOT_PROTECTED)
......@@ -162,44 +365,77 @@ void NasMm::receiveRegistrationReject(const nas::RegistrationReject &msg)
}
else
{
// TODO abnormal case see 5.5.1.2.7.
unhandledRejectCase();
handleAbnormalCase();
}
}
else if (cause == nas::EMmCause::N1_MODE_NOT_ALLOWED)
{
m_storedGuti = {};
m_lastVisitedRegisteredTai = {};
m_taiList = {};
m_currentNsCtx = {};
m_nonCurrentNsCtx = {};
switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA);
switchRmState(ERmState::RM_DEREGISTERED);
}
else
if (cause != nas::EMmCause::ILLEGAL_UE && cause != nas::EMmCause::ILLEGAL_ME &&
cause != nas::EMmCause::FIVEG_SERVICES_NOT_ALLOWED && cause != nas::EMmCause::PLMN_NOT_ALLOWED &&
cause != nas::EMmCause::TA_NOT_ALLOWED && cause != nas::EMmCause::ROAMING_NOT_ALLOWED_IN_TA &&
cause != nas::EMmCause::NO_SUITIBLE_CELLS_IN_TA && cause != nas::EMmCause::CONGESTION &&
cause != nas::EMmCause::N1_MODE_NOT_ALLOWED && cause != nas::EMmCause::SERVING_NETWORK_NOT_AUTHORIZED)
{
// TODO
unhandledRejectCase();
handleAbnormalCase();
}
}
else if (regType == nas::ERegistrationType::EMERGENCY_REGISTRATION)
{
if (cause == nas::EMmCause::PEI_NOT_ACCEPTED)
{
// TODO
unhandledRejectCase();
}
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NO_SUPI);
else
{
// TODO: abnormal case
unhandledRejectCase();
// Spec perseveringly says that upper layers should be informed as well, for additional action for emergency
// registration, but no need for now.
handleAbnormalCase();
}
}
else
{
// TODO
unhandledRejectCase();
}
}
void NasMm::handleCommonAbnormalRegFailure(nas::ERegistrationType regType)
{
// Timer T3510 shall be stopped if still running
m_timers->t3510.stop();
// If the registration procedure is neither an initial registration for emergency services nor for establishing an
// emergency PDU session with registration type not set to "emergency registration", the registration attempt
// counter shall be incremented, unless it was already set to 5.
if (regType != nas::ERegistrationType::EMERGENCY_REGISTRATION && !hasEmergency() && m_regCounter != 5)
m_regCounter++;
// If the registration attempt counter is less than 5:
if (m_regCounter < 5)
{
// If the initial registration request is not for emergency services, timer T3511 is started and the state is
// changed to 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION. When timer T3511 expires the registration procedure for
// initial registration shall be restarted, if still required.
if (!hasEmergency())
{
m_timers->t3511.start();
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION);
}
}
else
{
// The UE shall delete 5G-GUTI, TAI list, last visited TAI, list of equivalent PLMNs and ngKSI, ..
m_storage.m_storedGuti = {};
m_storage.m_taiList = {};
m_storage.m_lastVisitedRegisteredTai = {};
m_storage.m_equivalentPlmnList = {};
m_storage.m_currentNsCtx = {};
m_storage.m_nonCurrentNsCtx = {};
// .. start timer T3502 ..
m_timers->t3502.start();
// .. and shall set the 5GS update status to 5U2 NOT UPDATED. The state is changed to
// 5GMM-DEREGISTERED.ATTEMPTING-REGISTRATION or optionally to 5GMM-DEREGISTERED.PLMN-SEARCH in order to perform
// a PLMN selection according to 3GPP TS 23.122 [5].
switchUState(E5UState::U2_NOT_UPDATED);
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_ATTEMPTING_REGISTRATION);
}
}
......
......@@ -13,8 +13,26 @@
namespace nr::ue
{
static bool IsValidKsi(const nas::IENasKeySetIdentifier &ngKsi)
{
return ngKsi.tsc == nas::ETypeOfSecurityContext::NATIVE_SECURITY_CONTEXT &&
ngKsi.ksi != nas::IENasKeySetIdentifier::NOT_AVAILABLE_OR_RESERVED;
}
static int FindSecurityContext(int ksi, const std::unique_ptr<NasSecurityContext> &current,
const std::unique_ptr<NasSecurityContext> &nonCurrent)
{
if (current != nullptr && current->ngKsi == ksi)
return 0;
if (nonCurrent != nullptr && nonCurrent->ngKsi == ksi)
return 1;
return -1;
}
void NasMm::receiveSecurityModeCommand(const nas::SecurityModeCommand &msg)
{
m_logger->debug("Security Mode Command received");
auto reject = [this](nas::EMmCause cause) {
nas::SecurityModeReject resp;
resp.mmCause.value = cause;
......@@ -22,66 +40,111 @@ void NasMm::receiveSecurityModeCommand(const nas::SecurityModeCommand &msg)
m_logger->err("Rejecting Security Mode Command with cause: %s", nas::utils::EnumToString(cause));
};
if (!m_nonCurrentNsCtx.has_value())
// ============================== Check the received ngKSI ==============================
if (!IsValidKsi(msg.ngKsi))
{
reject(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE);
m_logger->err("Invalid ngKSI received, tsc[%d], ksi[%d]", (int)msg.ngKsi.tsc, msg.ngKsi.ksi);
reject(nas::EMmCause::SEC_MODE_REJECTED_UNSPECIFIED);
return;
}
// TODO: check the integrity with new security context
if (msg.ngKsi.ksi == 0 &&
msg.selectedNasSecurityAlgorithms.integrity == nas::ETypeOfIntegrityProtectionAlgorithm::IA0 &&
msg.selectedNasSecurityAlgorithms.ciphering == nas::ETypeOfCipheringAlgorithm::EA0)
{
// TODO
}
int whichCtx = FindSecurityContext(msg.ngKsi.ksi, m_storage.m_currentNsCtx, m_storage.m_nonCurrentNsCtx);
if (whichCtx == -1)
{
m_logger->err("Security context with ngKSI[%d] not found", msg.ngKsi.ksi);
reject(nas::EMmCause::SEC_MODE_REJECTED_UNSPECIFIED);
return;
}
auto &nsCtx = whichCtx == 0 ? m_storage.m_currentNsCtx : m_storage.m_nonCurrentNsCtx;
// ======================== Check the integrity with new security context ========================
{
// TODO:
octet4 mac = msg._macForNewSC;
(void)mac;
}
// Check replayed UE security capabilities
// ======================== Check replayed UE security capabilities ========================
if (!nas::utils::DeepEqualsIe(msg.replayedUeSecurityCapabilities, createSecurityCapabilityIe()))
{
m_logger->err("Replayed UE security capability mismatch");
reject(nas::EMmCause::UE_SECURITY_CAP_MISMATCH);
return;
}
// ======================== Check selected NAS security algorithms ========================
{
auto &replayed = msg.replayedUeSecurityCapabilities;
auto real = createSecurityCapabilityIe();
auto integrity = msg.selectedNasSecurityAlgorithms.integrity;
auto ciphering = msg.selectedNasSecurityAlgorithms.ciphering;
if (!nas::utils::DeepEqualsIe(replayed, real))
if (integrity > nas::ETypeOfIntegrityProtectionAlgorithm::IA3_128 ||
ciphering > nas::ETypeOfCipheringAlgorithm::EA3_128)
{
m_logger->err("Selected NAS security algorithms are invalid");
reject(nas::EMmCause::UE_SECURITY_CAP_MISMATCH);
return;
}
}
// Handle EAP-Success message if any.
if (msg.eapMessage.has_value())
{
if (msg.eapMessage->eap->code == eap::ECode::SUCCESS)
receiveEapSuccessMessage(*msg.eapMessage->eap);
else
m_logger->warn(
"EAP message with inconvenient code received in Security Mode Command. Ignoring EAP message.");
}
// ============================ Process the security context. ============================
// Assign ABBA (if any)
if (msg.abba.has_value())
m_nonCurrentNsCtx->keys.abba = msg.abba->rawData.copy();
nsCtx->keys.abba = msg.abba->rawData.copy();
// Check selected algorithms
// Assign selected algorithms to security context, and derive NAS keys
nsCtx->integrity = msg.selectedNasSecurityAlgorithms.integrity;
nsCtx->ciphering = msg.selectedNasSecurityAlgorithms.ciphering;
keys::DeriveNasKeys(*nsCtx);
m_logger->debug("Derived kNasEnc[%s] kNasInt[%s]", nsCtx->keys.kNasEnc.toHexString().c_str(),
nsCtx->keys.kNasInt.toHexString().c_str());
m_logger->debug("Selected integrity[%d] ciphering[%d]", (int)nsCtx->integrity, (int)nsCtx->ciphering);
// The UE shall in addition reset the uplink NAS COUNT counter if a) the SECURITY MODE COMMAND message is received
// in order to take a 5G NAS security context into use created after a successful execution of the 5G AKA based
// primary authentication and key agreement procedure or the EAP based ...
if (whichCtx == 1) // It is unclear how we can detect this, but checking if it is 'non-current' one.
{
nsCtx->uplinkCount.sqn = 0;
nsCtx->uplinkCount.overflow = octet2{0};
}
if (msg.selectedNasSecurityAlgorithms.integrity != nas::ETypeOfIntegrityProtectionAlgorithm::IA0)
{
// TODO
// if (msg.selectedNasSecurityAlgorithms.integrity is supported according to config file)
// if (msg.selectedNasSecurityAlgorithms.ciphering is supported according to config file)
}
// Assign selected algorithms to security context, and derive NAS keys
m_nonCurrentNsCtx->integrity = msg.selectedNasSecurityAlgorithms.integrity;
m_nonCurrentNsCtx->ciphering = msg.selectedNasSecurityAlgorithms.ciphering;
keys::DeriveNasKeys(*m_nonCurrentNsCtx);
// Set the new NAS Security Context as current one. (If it is not already the current one)
if (whichCtx == 1)
m_storage.m_currentNsCtx = std::make_unique<NasSecurityContext>(nsCtx->deepCopy());
// ============================ Handle EAP-Success message if any. ============================
m_logger->debug("Derived kNasEnc[%s] kNasInt[%s]", m_nonCurrentNsCtx->keys.kNasEnc.toHexString().c_str(),
m_nonCurrentNsCtx->keys.kNasInt.toHexString().c_str());
m_logger->debug("Selected integrity[%d] ciphering[%d]", (int)m_nonCurrentNsCtx->integrity,
(int)m_nonCurrentNsCtx->ciphering);
if (msg.eapMessage.has_value())
{
if (msg.eapMessage->eap->code == eap::ECode::SUCCESS)
receiveEapSuccessMessage(*msg.eapMessage->eap);
else
m_logger->warn(
"EAP message with inconvenient code received in Security Mode Command. Ignoring EAP message.");
}
// Set non-current NAS Security Context as current one.
m_currentNsCtx = m_nonCurrentNsCtx->deepCopy();
// ============================ Send the Security Mode Complete. ============================
// Prepare response
nas::SecurityModeComplete resp;
nas::SecurityModeComplete resp{};
// Append IMEISV if requested
if (msg.imeiSvRequest.has_value() && msg.imeiSvRequest->imeiSvRequest == nas::EImeiSvRequest::REQUESTED)
......@@ -94,6 +157,8 @@ void NasMm::receiveSecurityModeCommand(const nas::SecurityModeCommand &msg)
}
}
// TODO: Bu service request de olabilir en son hangisiyse, ayrıca son mesaj yerine son unciphered mesaj da olabilir
// See 4.4.6
resp.nasMessageContainer = nas::IENasMessageContainer{};
nas::EncodeNasMessage(*m_lastRegistrationRequest, resp.nasMessageContainer->data);
......@@ -106,7 +171,7 @@ nas::IEUeSecurityCapability NasMm::createSecurityCapabilityIe()
auto &algs = m_base->config->supportedAlgs;
auto supported = ~0;
nas::IEUeSecurityCapability res;
nas::IEUeSecurityCapability res{};
res.b_5G_EA0 = supported;
res.b_5G_IA0 = supported;
res.b_EEA0 = supported;
......
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/sm/sm.hpp>
namespace nr::ue
{
static bool ContainsSNssai(const NetworkSlice &nssai, const SingleSlice &sNssai)
{
return std::any_of(nssai.slices.begin(), nssai.slices.end(), [&sNssai](auto &i) { return i == sNssai; });
}
static void AppendSubset(const NetworkSlice &source, NetworkSlice &target, const NetworkSlice &rejectedInPlmn,
const NetworkSlice &rejectedInTa, size_t maxSNssai)
{
size_t appended = 0;
for (auto &slice : source.slices)
{
if (appended == maxSNssai)
break;
if (ContainsSNssai(rejectedInPlmn, slice))
continue;
if (ContainsSNssai(rejectedInTa, slice))
continue;
target.slices.push_back(slice);
appended++;
}
}
NetworkSlice NasMm::makeRequestedNssai(bool &isDefaultNssai) const
{
isDefaultNssai = false;
NetworkSlice res{};
if (!m_storage.m_allowedNssai.slices.empty() || !m_storage.m_configuredNssai.slices.empty())
{
if (!m_storage.m_allowedNssai.slices.empty())
{
AppendSubset(m_storage.m_allowedNssai, res, m_storage.m_rejectedNssaiInPlmn, m_storage.m_rejectedNssaiInTa,
8);
AppendSubset(m_storage.m_configuredNssai, res, m_storage.m_rejectedNssaiInPlmn,
m_storage.m_rejectedNssaiInTa, static_cast<size_t>(8) - res.slices.size());
}
else
{
AppendSubset(m_storage.m_configuredNssai, res, m_storage.m_rejectedNssaiInPlmn,
m_storage.m_rejectedNssaiInTa, 8);
}
}
else if (!m_storage.m_defConfiguredNssai.slices.empty())
{
AppendSubset(m_storage.m_defConfiguredNssai, res, m_storage.m_rejectedNssaiInPlmn,
m_storage.m_rejectedNssaiInTa, 8);
isDefaultNssai = true;
}
return res;
}
void NasMm::handleNetworkSlicingSubscriptionChange()
{
// TODO
}
} // namespace nr::ue
\ No newline at end of file
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "mm.hpp"
#include <nas/utils.hpp>
#include <ue/app/task.hpp>
#include <ue/nas/task.hpp>
#include <ue/rrc/task.hpp>
#include <utils/common.hpp>
namespace nr::ue
{
void NasMm::onTimerExpire(nas::NasTimer &timer)
{
auto logExpired = [this, &timer]() {
m_logger->debug("NAS timer[%d] expired [%d]", timer.getCode(), timer.getExpiryCount());
};
switch (timer.getCode())
{
case 3346: {
if (m_mmSubState == EMmSubState::MM_DEREGISTERED_NORMAL_SERVICE)
{
logExpired();
sendRegistration(nas::ERegistrationType::INITIAL_REGISTRATION, false);
}
break;
}
case 3510: {
// The UE shall abort the registration procedure for initial registration and the NAS signalling connection, if
// any, shall be released locally if the initial registration request is not for emergency services..
if (m_mmState == EMmState::MM_REGISTERED_INITIATED && m_lastRegistrationRequest)
{
logExpired();
switchRmState(ERmState::RM_DEREGISTERED);
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
switchUState(E5UState::U2_NOT_UPDATED);
if (m_lastRegistrationRequest->registrationType.registrationType !=
nas::ERegistrationType::EMERGENCY_REGISTRATION)
{
localReleaseConnection();
}
handleCommonAbnormalRegFailure(m_lastRegistrationRequest->registrationType.registrationType);
}
break;
}
case 3512: {
if (m_mmState == EMmState::MM_REGISTERED && m_cmState == ECmState::CM_CONNECTED)
{
logExpired();
sendRegistration(nas::ERegistrationType::PERIODIC_REGISTRATION_UPDATING, false);
}
break;
}
case 3519: {
m_storage.m_storedSuci = {};
break;
}
case 3521: {
if (timer.getExpiryCount() == 5)
{
timer.resetExpiryCount();
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr)
{
logExpired();
m_logger->debug("De-registration aborted");
if (m_lastDeregDueToDisable5g)
switchMmState(EMmState::MM_NULL, EMmSubState::MM_NULL_NA);
else if (m_lastDeregistrationRequest->deRegistrationType.switchOff ==
nas::ESwitchOff::NORMAL_DE_REGISTRATION)
switchMmState(EMmState::MM_DEREGISTERED, EMmSubState::MM_DEREGISTERED_NA);
}
}
else
{
if (m_mmState == EMmState::MM_DEREGISTERED_INITIATED && m_lastDeregistrationRequest != nullptr)
{
logExpired();
m_logger->debug("Retrying de-registration request");
sendNasMessage(*m_lastDeregistrationRequest);
m_timers->t3521.start(false);
}
}
break;
}
}
}
} // namespace nr::ue
......@@ -21,10 +21,11 @@ void NasMm::sendNasMessage(const nas::PlainMmMessage &msg)
// TODO trigger on send
OctetString pdu{};
if (m_currentNsCtx.has_value() && (m_currentNsCtx->integrity != nas::ETypeOfIntegrityProtectionAlgorithm::IA0 ||
m_currentNsCtx->ciphering != nas::ETypeOfCipheringAlgorithm::EA0))
if (m_storage.m_currentNsCtx &&
(m_storage.m_currentNsCtx->integrity != nas::ETypeOfIntegrityProtectionAlgorithm::IA0 ||
m_storage.m_currentNsCtx->ciphering != nas::ETypeOfCipheringAlgorithm::EA0))
{
auto secured = nas_enc::Encrypt(*m_currentNsCtx, msg);
auto secured = nas_enc::Encrypt(*m_storage.m_currentNsCtx, msg);
nas::EncodeNasMessage(*secured, pdu);
}
else
......@@ -62,7 +63,7 @@ void NasMm::receiveNasMessage(const nas::NasMessage &msg)
{
// If any NAS signalling message is received as not integrity protected even though the secure exchange of NAS
// messages has been established by the network, then the NAS shall discard this message
if (m_currentNsCtx.has_value())
if (m_storage.m_currentNsCtx)
{
m_logger->err(
"Not integrity protected NAS message received after security establishment. Ignoring received "
......@@ -100,14 +101,14 @@ void NasMm::receiveNasMessage(const nas::NasMessage &msg)
return;
}
if (!m_currentNsCtx.has_value())
if (!m_storage.m_currentNsCtx)
{
m_logger->warn("Secured NAS message received while no security context");
sendMmStatus(nas::EMmCause::MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE);
return;
}
auto decrypted = nas_enc::Decrypt(*m_currentNsCtx, securedMm);
auto decrypted = nas_enc::Decrypt(*m_storage.m_currentNsCtx, securedMm);
if (decrypted == nullptr)
{
m_logger->err("MAC mismatch in NAS encryption. Ignoring received NAS Message.");
......
......@@ -122,7 +122,7 @@ void UeMrTask::onLoop()
break;
}
case NwUeRrcToMr::RRC_CONNECTION_RELEASE: {
m_rlsEntity->localReleaseConnection(rls::ECause::RRC_RELEASE);
m_rlsEntity->localReleaseConnection(w->cause);
m_rlsEntity->resetEntity();
break;
}
......
......@@ -19,10 +19,10 @@ static const int N_UP_int_alg = 0x06;
namespace nr::ue::keys
{
void DeriveKeysSeafAmf(const UeConfig &ueConfig, NasSecurityContext &nasSecurityContext)
void DeriveKeysSeafAmf(const UeConfig &ueConfig, const Plmn& currentPlmn, NasSecurityContext &nasSecurityContext)
{
auto &keys = nasSecurityContext.keys;
std::string snn = ConstructServingNetworkName(ueConfig.plmn);
std::string snn = ConstructServingNetworkName(currentPlmn);
OctetString s1[1];
s1[0] = crypto::EncodeKdfString(snn);
......
......@@ -16,7 +16,7 @@ namespace nr::ue::keys
/**
* Derives SEAF and AMF keys
*/
void DeriveKeysSeafAmf(const UeConfig &ueConfig, NasSecurityContext &nasSecurityContext);
void DeriveKeysSeafAmf(const UeConfig &ueConfig, const Plmn &currentPlmn, NasSecurityContext &nasSecurityContext);
/**
* Derives NAS keys
......
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include "storage.hpp"
namespace nr::ue
{
} // namespace nr::ue
//
// This file is a part of UERANSIM open source project.
// Copyright (c) 2021 ALİ GÜNGÖR.
//
// The software and all associated files are licensed under GPL-3.0
// and subject to the terms and conditions defined in LICENSE file.
//
#include <nas/nas.hpp>
#include <ue/types.hpp>
#pragma once
namespace nr::ue
{
class MobileStorage
{
private:
bool m_simIsValid{};
public:
// Location related
nas::IE5gsMobileIdentity m_storedGuti{};
std::optional<nas::IE5gsTrackingAreaIdentity> m_lastVisitedRegisteredTai{};
E5UState m_uState{};
// Identity related
nas::IE5gsMobileIdentity m_storedSuci{};
// Plmn related
Plmn m_currentPlmn{};
nas::IE5gsTrackingAreaIdentityList m_taiList{};
nas::IE5gsTrackingAreaIdentityList m_forbiddenTaiList{};
nas::IEPlmnList m_equivalentPlmnList{};
nas::IEPlmnList m_forbiddenPlmnList{};
nas::IEServiceAreaList m_serviceAreaList{};
// Security related
std::unique_ptr<NasSecurityContext> m_currentNsCtx{};
std::unique_ptr<NasSecurityContext> m_nonCurrentNsCtx{};
OctetString m_sqn{};
// NSSAI related
NetworkSlice m_defConfiguredNssai{};
NetworkSlice m_configuredNssai{};
NetworkSlice m_allowedNssai{};
NetworkSlice m_rejectedNssaiInPlmn{};
NetworkSlice m_rejectedNssaiInTa{};
public:
void initialize(bool hasSupi, const UeConfig::Initials &initials)
{
m_simIsValid = hasSupi;
m_defConfiguredNssai = initials.defaultConfiguredNssai;
m_configuredNssai = initials.configuredNssai;
}
void invalidateSim()
{
m_simIsValid = false;
}
[[nodiscard]] bool isSimValid() const
{
return m_simIsValid;
}
};
} // namespace nr::ue
\ No newline at end of file
......@@ -141,8 +141,6 @@ void NasTask::onTimerExpire(nas::NasTimer &timer)
void NasTask::performTick()
{
auto sendExpireMsg = [this](nas::NasTimer *timer) {
logger->debug("NAS timer[%d] expired [%d]", timer->getCode(), timer->getExpiryCount());
auto *nw = new NwUeNasToNas(NwUeNasToNas::NAS_TIMER_EXPIRE);
nw->timer = timer;
push(nw);
......
......@@ -177,6 +177,7 @@ struct NwUeNasToRrc : NtsMessage
enum PR
{
PLMN_SEARCH_REQUEST,
LOCAL_RELEASE_CONNECTION,
INITIAL_NAS_DELIVERY,
UPLINK_NAS_DELIVERY
} present;
......@@ -206,6 +207,9 @@ struct NwUeRrcToMr : NtsMessage
rrc::RrcChannel channel{};
OctetString pdu{};
// RRC_CONNECTION_RELEASE
rls::ECause cause{};
explicit NwUeRrcToMr(PR present) : NtsMessage(NtsMessageType::UE_RRC_TO_MR), present(present)
{
}
......
......@@ -130,7 +130,10 @@ void UeRrcTask::receiveRrcRelease(const ASN_RRC_RRCRelease &msg)
m_logger->debug("RRC Release received");
m_state = ERrcState::RRC_IDLE;
m_base->mrTask->push(new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE));
auto *wr = new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE);
wr->cause = rls::ECause::RRC_NORMAL_RELEASE;
m_base->mrTask->push(wr);
m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE));
}
......
......@@ -84,6 +84,15 @@ void UeRrcTask::onLoop()
case NwUeNasToRrc::UPLINK_NAS_DELIVERY:
deliverUplinkNas(std::move(w->nasPdu));
break;
case NwUeNasToRrc::LOCAL_RELEASE_CONNECTION:
m_state = ERrcState::RRC_IDLE;
auto *wr = new NwUeRrcToMr(NwUeRrcToMr::RRC_CONNECTION_RELEASE);
wr->cause = rls::ECause::RRC_LOCAL_RELEASE;
m_base->mrTask->push(wr);
m_base->nasTask->push(new NwUeRrcToNas(NwUeRrcToNas::RRC_CONNECTION_RELEASE));
break;
}
break;
}
......
......@@ -33,15 +33,13 @@ void NasSm::sendEstablishmentRequest(const SessionConfig &config)
return;
}
// TODO
nas::ProtocolConfigurationOptions opt;
nas::ProtocolConfigurationOptions opt{};
opt.additionalParams.push_back(std::make_unique<nas::ProtocolConfigurationItem>(
nas::EProtocolConfigId::CONT_ID_UP_IP_ADDRESS_ALLOCATION_VIA_NAS_SIGNALLING, true, OctetString::Empty()));
opt.additionalParams.push_back(std::make_unique<nas::ProtocolConfigurationItem>(
nas::EProtocolConfigId::CONT_ID_DOWN_DNS_SERVER_IPV4_ADDRESS, true, OctetString::Empty()));
// TODO
nas::PduSessionEstablishmentRequest req;
nas::PduSessionEstablishmentRequest req{};
req.pti = pti;
req.pduSessionId = static_cast<nas::EPduSessionIdentity>(psi);
req.integrityProtectionMaximumDataRate.maxRateUplink =
......@@ -58,7 +56,7 @@ void NasSm::sendEstablishmentRequest(const SessionConfig &config)
req.extendedProtocolConfigurationOptions->options = opt.encode();
m_timers->t3580.start();
sendSmMessage(pti, req);
sendSmMessage(psi, req);
}
void NasSm::receivePduSessionEstablishmentAccept(const nas::PduSessionEstablishmentAccept &msg)
......
......@@ -124,10 +124,9 @@ Json ToJson(const UeConfig &v)
{
return Json::Obj({
{"supi", ToJson(v.supi)},
{"plmn", ToJson(v.plmn)},
{"hplmn", ToJson(v.hplmn)},
{"imei", ::ToJson(v.imei)},
{"imeiSv", ::ToJson(v.imeiSv)},
{"nssai", ::ToJson(v.nssais)},
});
}
......
......@@ -47,7 +47,7 @@ enum class OpType
struct SessionConfig
{
nas::EPduSessionType type{};
std::optional<SliceSupport> sNssai{};
std::optional<SingleSlice> sNssai{};
std::optional<std::string> apn{};
};
......@@ -55,20 +55,26 @@ struct UeConfig
{
/* Read from config file */
std::optional<Supi> supi{};
Plmn plmn{};
Plmn hplmn{};
OctetString key{};
OctetString opC{};
OpType opType{};
OctetString amf{};
std::optional<std::string> imei{};
std::optional<std::string> imeiSv{};
std::vector<SliceSupport> nssais{};
SupportedAlgs supportedAlgs{};
std::vector<std::string> gnbSearchList{};
std::vector<SessionConfig> initSessions{};
/* Read from config file as well, but should be stored in non-volatile
* mobile storage and subject to change in runtime */
struct Initials
{
NetworkSlice defaultConfiguredNssai{};
NetworkSlice configuredNssai{};
} initials{};
/* Assigned by program */
bool autoBehaviour{};
bool configureRouting{};
bool prefixLogger{};
......@@ -120,9 +126,9 @@ struct UeTimers
nas::NasTimer t3444; /* MM - ... */
nas::NasTimer t3445; /* MM - ... */
nas::NasTimer t3502; /* MM - ... */
nas::NasTimer t3502; /* MM - Initiation of the registration procedure, if still required */
nas::NasTimer t3510; /* MM - Registration Request transmission timer */
nas::NasTimer t3511; /* MM - ... */
nas::NasTimer t3511; /* MM - Retransmission of the REGISTRATION REQUEST, if still required */
nas::NasTimer t3512; /* MM - Periodic registration update timer */
nas::NasTimer t3516; /* MM - 5G AKA - RAND and RES* storing timer */
nas::NasTimer t3517; /* MM - Service Request transmission timer */
......@@ -226,7 +232,7 @@ struct PduSession
nas::EPduSessionType sessionType{};
std::optional<std::string> apn{};
std::optional<SliceSupport> sNssai{};
std::optional<SingleSlice> sNssai{};
std::optional<nas::IEQoSRules> authorizedQoSRules{};
std::optional<nas::IESessionAmbr> sessionAmbr{};
......
......@@ -95,8 +95,10 @@ const char *CauseToString(ECause cause)
return "RLS-SETUP-TIMEOUT";
case ECause::HEARTBEAT_TIMEOUT:
return "RLS-HEARTBEAT-TIMEOUT";
case ECause::RRC_RELEASE:
return "RLS-RRC-RELEASE";
case ECause::RRC_NORMAL_RELEASE:
return "RLS-RRC-NORMAL-RELEASE";
case ECause::RRC_LOCAL_RELEASE:
return "RLS-RRC-LOCAL-RELEASE";
default:
return "?";
}
......
......@@ -53,13 +53,14 @@ enum class ECause : uint8_t
HEARTBEAT_TIMEOUT,
// Successful causes
RRC_RELEASE,
RRC_NORMAL_RELEASE, // release with UE-gNB coordination over RRC
RRC_LOCAL_RELEASE, // release locally without UE-gNB coordination
};
// Checks if the cause treated as radio link failure
inline bool IsRlf(ECause cause)
{
return cause != ECause::RRC_RELEASE;
return cause != ECause::RRC_NORMAL_RELEASE && cause != ECause::RRC_LOCAL_RELEASE;
}
enum class EPayloadType : uint8_t
......
......@@ -113,6 +113,12 @@ void RlsUeEntity::startGnbSearch()
void RlsUeEntity::onReceive(const InetAddress &address, const OctetString &pdu)
{
if (ueToken == 0)
{
logWarn("Received PDU ignored, UE entity is not initialized");
return;
}
RlsMessage msg{};
auto res = Decode(OctetView{pdu}, msg, AppVersion);
......
......@@ -12,6 +12,7 @@
#include "octet.hpp"
#include "octet_string.hpp"
#include "time_stamp.hpp"
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <string>
......@@ -46,6 +47,12 @@ inline void ClearAndDelete(std::vector<T *> &vector)
vector.clear();
}
template <typename T, typename P>
inline void EraseWhere(std::vector<T> &vector, P predicate)
{
vector.erase(std::remove_if(vector.begin(), vector.end(), std::forward<P>(predicate)), vector.end());
}
template <typename T>
static std::string IntToHex(T i)
{
......
......@@ -7,6 +7,7 @@
//
#include "common_types.hpp"
#include <algorithm>
#include <iomanip>
#include <sstream>
......@@ -38,12 +39,34 @@ Json ToJson(const Plmn &v)
return ss.str();
}
Json ToJson(const SliceSupport &v)
Json ToJson(const SingleSlice &v)
{
return Json::Obj({{"sst", ToJson(v.sst)}, {"sd", ToJson(v.sd)}});
}
Json ToJson(const NetworkSlice &v)
{
return ToJson(v.slices);
}
Json ToJson(const PlmnSupport &v)
{
return Json::Obj({{"plmn", ToJson(v.plmn)}, {"nssai", ToJson(v.sliceSupportList)}});
}
bool operator==(const SingleSlice &lhs, const SingleSlice &rhs)
{
if ((int)lhs.sst != (int)rhs.sst)
return false;
if (lhs.sd.has_value() != rhs.sd.has_value())
return false;
if (!lhs.sd.has_value())
return true;
return ((int)*lhs.sd) == ((int)*rhs.sd);
}
void NetworkSlice::addIfNotExists(const SingleSlice &slice)
{
if (!std::any_of(slices.begin(), slices.end(), [&slice](auto &s) { return s == slice; }))
slices.push_back(slice);
}
......@@ -30,12 +30,19 @@ struct Plmn
bool isLongMnc{};
};
struct SliceSupport
struct SingleSlice
{
octet sst{};
std::optional<octet3> sd{};
};
struct NetworkSlice
{
std::vector<SingleSlice> slices{};
void addIfNotExists(const SingleSlice &slice);
};
enum class PduSessionType
{
IPv4,
......@@ -48,7 +55,7 @@ enum class PduSessionType
struct PlmnSupport
{
Plmn plmn{};
std::vector<std::unique_ptr<SliceSupport>> sliceSupportList{};
NetworkSlice sliceSupportList{};
};
struct GutiMobileIdentity
......@@ -104,5 +111,8 @@ struct Supi
Json ToJson(const Supi &v);
Json ToJson(const Plmn &v);
Json ToJson(const SliceSupport &v);
Json ToJson(const SingleSlice &v);
Json ToJson(const NetworkSlice &v);
Json ToJson(const PlmnSupport &v);
bool operator==(const SingleSlice &lhs, const SingleSlice &rhs);
......@@ -15,10 +15,10 @@ struct cons
// Version information
static constexpr const uint8_t Major = 3;
static constexpr const uint8_t Minor = 1;
static constexpr const uint8_t Patch = 1;
static constexpr const uint8_t Patch = 2;
static constexpr const char *Project = "UERANSIM";
static constexpr const char *Tag = "v3.1.1";
static constexpr const char *Name = "UERANSIM v3.1.1";
static constexpr const char *Tag = "v3.1.2";
static constexpr const char *Name = "UERANSIM v3.1.2";
static constexpr const char *Owner = "ALİ GÜNGÖR";
// Some port values
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment