mirror of
https://github.com/open5gs/open5gs.git
synced 2026-10-19 09:44:04 +00:00
[AMF] Preserve PDU sessions during GUTI re-registration (#4577)
An unprotected Registration Request using a registered UE's GUTI could release its existing PDU sessions before authentication. Defer session release until authentication, Security Mode Control and the optional EIR check complete. Wait for all release responses before continuing UDM registration, and prevent stale responses from resuming a cancelled procedure. Restore the previous NAS security context on authentication or SMC failure, and ignore unverified Deregistration Requests during these procedures. Add unit and EIR integration regressions for session preservation and replacement with EIR enabled or disabled and one or two PDU sessions. Complete the SMF/BSF SBI configuration and make EIR fixtures safe to rerun.
This commit is contained in:
parent
0e006417b3
commit
2f02f7e2a4
31 changed files with 1841 additions and 147 deletions
|
|
@ -16,7 +16,6 @@ global:
|
|||
parameter:
|
||||
no_scp: true
|
||||
no_sepp: true
|
||||
no_bsf: true
|
||||
|
||||
mme:
|
||||
freeDiameter:
|
||||
|
|
@ -94,6 +93,13 @@ sgwc:
|
|||
- address: 127.0.0.6
|
||||
|
||||
smf:
|
||||
sbi:
|
||||
server:
|
||||
- address: 127.0.0.4
|
||||
port: 7777
|
||||
client:
|
||||
nrf:
|
||||
- uri: http://127.0.0.10:7777
|
||||
pfcp:
|
||||
server:
|
||||
- address: 127.0.0.4
|
||||
|
|
@ -293,6 +299,15 @@ nssf:
|
|||
s_nssai:
|
||||
sst: 1
|
||||
|
||||
bsf:
|
||||
sbi:
|
||||
server:
|
||||
- address: 127.0.0.15
|
||||
port: 7777
|
||||
client:
|
||||
nrf:
|
||||
- uri: http://127.0.0.10:7777
|
||||
|
||||
udr:
|
||||
sbi:
|
||||
server:
|
||||
|
|
|
|||
|
|
@ -622,6 +622,12 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
|
|||
} else {
|
||||
ogs_error("[%s:%d] HTTP response error [%d]",
|
||||
amf_ue->supi, sess->psi, sbi_message.res_status);
|
||||
if (state ==
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION) {
|
||||
amf_sbi_fail_registration_session_release(
|
||||
amf_ue, ran_ue, sbi_message.res_status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
amf_nsmf_pdusession_handle_release_sm_context(
|
||||
amf_ue, ran_ue, sess, state);
|
||||
|
|
@ -972,6 +978,13 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
|
|||
|
||||
amf_ue = amf_ue_find_by_id(ran_ue->amf_ue_id);
|
||||
|
||||
if (amf_ue && amf_ue->registration_session_release_pending) {
|
||||
ogs_error("[%s] Ignore NAS message while registration session "
|
||||
"release is pending", amf_ue->supi);
|
||||
ogs_pkbuf_free(pkbuf);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* REGISTRATION REQUEST on an NG context that already belongs to
|
||||
* someone else. The 5GS counterpart of the same defect in the
|
||||
|
|
@ -1104,6 +1117,17 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
|
|||
|
||||
ogs_assert(CM_IDLE(amf_ue));
|
||||
} else {
|
||||
if (amf_ue->registration_session_release_pending) {
|
||||
ogs_error("[%s] Ignore new NG context while registration "
|
||||
"session release is pending", amf_ue->supi);
|
||||
r = ngap_send_ran_ue_context_release_command(ran_ue,
|
||||
NGAP_Cause_PR_nas, NGAP_CauseNas_normal_release,
|
||||
NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE, 0);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
ogs_pkbuf_free(pkbuf);
|
||||
break;
|
||||
}
|
||||
/* Here, if the AMF_UE Context is found,
|
||||
* the integrity check is not performed
|
||||
* For example, REGISTRATION_REQUEST, SERVICE_REQUEST message
|
||||
|
|
|
|||
|
|
@ -2980,6 +2980,10 @@ void amf_ue_save_memento(amf_ue_t *amf_ue, amf_ue_memento_t *memento)
|
|||
ogs_assert(amf_ue);
|
||||
ogs_assert(memento);
|
||||
|
||||
memento->ue_ksi = amf_ue->nas.ue.ksi;
|
||||
memento->ue_tsc = amf_ue->nas.ue.tsc;
|
||||
memento->amf_ksi = amf_ue->nas.amf.ksi;
|
||||
memento->amf_tsc = amf_ue->nas.amf.tsc;
|
||||
memcpy(&memento->ue_security_capability, &amf_ue->ue_security_capability,
|
||||
sizeof(memento->ue_security_capability));
|
||||
memcpy(&memento->ue_network_capability, &amf_ue->ue_network_capability,
|
||||
|
|
@ -3008,6 +3012,10 @@ void amf_ue_restore_memento(amf_ue_t *amf_ue, const amf_ue_memento_t *memento)
|
|||
ogs_assert(amf_ue);
|
||||
ogs_assert(memento);
|
||||
|
||||
amf_ue->nas.ue.ksi = memento->ue_ksi;
|
||||
amf_ue->nas.ue.tsc = memento->ue_tsc;
|
||||
amf_ue->nas.amf.ksi = memento->amf_ksi;
|
||||
amf_ue->nas.amf.tsc = memento->amf_tsc;
|
||||
memcpy(&amf_ue->ue_security_capability, &memento->ue_security_capability,
|
||||
sizeof(amf_ue->ue_security_capability));
|
||||
memcpy(&amf_ue->ue_network_capability, &memento->ue_network_capability,
|
||||
|
|
|
|||
|
|
@ -280,6 +280,8 @@ struct ran_ue_s {
|
|||
};
|
||||
|
||||
typedef struct amf_ue_memento_s {
|
||||
uint8_t ue_ksi, ue_tsc, amf_ksi, amf_tsc;
|
||||
|
||||
/* UE security capability info: supported security features. */
|
||||
ogs_nas_ue_security_capability_t ue_security_capability;
|
||||
/* UE network capability info: supported network features. */
|
||||
|
|
@ -482,6 +484,10 @@ struct amf_ue_s {
|
|||
/* flag: 1 = allow restoration of context, 0 = disallow */
|
||||
bool can_restore_context;
|
||||
|
||||
/* Same-AMF re-registration release waits for SMC and optional EIR. */
|
||||
bool registration_session_release_after_authentication;
|
||||
bool registration_session_release_pending;
|
||||
|
||||
/* Memento of context fields */
|
||||
amf_ue_memento_t memento;
|
||||
|
||||
|
|
|
|||
|
|
@ -503,18 +503,20 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_update(
|
|||
psimask |= pdu_session_status->psi << 8;
|
||||
psimask |= pdu_session_status->psi >> 8;
|
||||
|
||||
ogs_list_for_each(&amf_ue->sess_list, sess) {
|
||||
if ((psimask & (1 << sess->psi)) == 0) {
|
||||
if (SESSION_CONTEXT_IN_SMF(sess)) {
|
||||
amf_nsmf_pdusession_sm_context_param_t param;
|
||||
if (!amf_ue->registration_session_release_after_authentication) {
|
||||
ogs_list_for_each(&amf_ue->sess_list, sess) {
|
||||
if ((psimask & (1 << sess->psi)) == 0) {
|
||||
if (SESSION_CONTEXT_IN_SMF(sess)) {
|
||||
amf_nsmf_pdusession_sm_context_param_t param;
|
||||
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.ue_location = true;
|
||||
param.ue_timezone = true;
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.ue_location = true;
|
||||
param.ue_timezone = true;
|
||||
|
||||
amf_sbi_send_release_session(
|
||||
ran_ue, sess,
|
||||
AMF_RELEASE_SM_CONTEXT_REGISTRATION_ACCEPT, ¶m);
|
||||
amf_sbi_send_release_session(
|
||||
ran_ue, sess,
|
||||
AMF_RELEASE_SM_CONTEXT_REGISTRATION_ACCEPT, ¶m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -530,12 +532,14 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_update(
|
|||
psimask |= uplink_data_status->psi << 8;
|
||||
psimask |= uplink_data_status->psi >> 8;
|
||||
|
||||
ogs_list_for_each(&amf_ue->sess_list, sess) {
|
||||
if (psimask & (1 << sess->psi)) {
|
||||
if (SESSION_CONTEXT_IN_SMF(sess))
|
||||
amf_sbi_send_activating_session(
|
||||
ran_ue, sess,
|
||||
AMF_UPDATE_SM_CONTEXT_REGISTRATION_REQUEST);
|
||||
if (!amf_ue->registration_session_release_after_authentication) {
|
||||
ogs_list_for_each(&amf_ue->sess_list, sess) {
|
||||
if (psimask & (1 << sess->psi)) {
|
||||
if (SESSION_CONTEXT_IN_SMF(sess))
|
||||
amf_sbi_send_activating_session(
|
||||
ran_ue, sess,
|
||||
AMF_UPDATE_SM_CONTEXT_REGISTRATION_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -986,12 +990,12 @@ int gmm_handle_authentication_response(amf_ue_t *amf_ue,
|
|||
amf_ue->rand, authentication_response_parameter->res, hxres_star);
|
||||
|
||||
if (memcmp(hxres_star, amf_ue->hxres_star, OGS_MAX_RES_LEN) != 0) {
|
||||
ogs_error("[%s] MAC failure", amf_ue->suci);
|
||||
ogs_log_hexdump(OGS_LOG_ERROR,
|
||||
ogs_warn("[%s] MAC failure", amf_ue->suci);
|
||||
ogs_log_hexdump(OGS_LOG_WARN,
|
||||
authentication_response_parameter->res,
|
||||
authentication_response_parameter->length);
|
||||
ogs_log_hexdump(OGS_LOG_ERROR, hxres_star, OGS_MAX_RES_LEN);
|
||||
ogs_log_hexdump(OGS_LOG_ERROR,
|
||||
ogs_log_hexdump(OGS_LOG_WARN, hxres_star, OGS_MAX_RES_LEN);
|
||||
ogs_log_hexdump(OGS_LOG_WARN,
|
||||
amf_ue->hxres_star, OGS_MAX_RES_LEN);
|
||||
return OGS_ERROR;
|
||||
}
|
||||
|
|
|
|||
198
src/amf/gmm-sm.c
198
src/amf/gmm-sm.c
|
|
@ -36,22 +36,22 @@
|
|||
#undef OGS_LOG_DOMAIN
|
||||
#define OGS_LOG_DOMAIN __gmm_log_domain
|
||||
|
||||
#define AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s) do { \
|
||||
if ((amf_ue)->can_restore_context) { \
|
||||
/* Restore context if allowed */ \
|
||||
amf_ue_restore_memento((amf_ue), &((amf_ue)->memento)); \
|
||||
(amf_ue)->security_context_available = 1; \
|
||||
(amf_ue)->mac_failed = 0; \
|
||||
if (!OGS_FSM_CHECK(&amf_ue->sm, gmm_state_registered)) \
|
||||
OGS_FSM_TRAN((s), &gmm_state_registered); \
|
||||
#define AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s) do { \
|
||||
CLEAR_AMF_UE_TIMER((amf_ue)->t3560); \
|
||||
(amf_ue)->registration_session_release_after_authentication = false; \
|
||||
if ((amf_ue)->can_restore_context) { \
|
||||
amf_ue_restore_memento((amf_ue), &((amf_ue)->memento)); \
|
||||
(amf_ue)->security_context_available = 1; \
|
||||
(amf_ue)->mac_failed = 0; \
|
||||
if (!OGS_FSM_CHECK((s), gmm_state_registered)) \
|
||||
OGS_FSM_TRAN((s), &gmm_state_registered); \
|
||||
ogs_warn("[%s] Failure in transaction; restoring context and " \
|
||||
"transitioning to REGISTERED.", (amf_ue)->supi); \
|
||||
} else { \
|
||||
/* Transition to exception state if not allowed */ \
|
||||
OGS_FSM_TRAN((s), &gmm_state_exception); \
|
||||
"transitioning to REGISTERED.", (amf_ue)->supi); \
|
||||
} else { \
|
||||
OGS_FSM_TRAN((s), &gmm_state_exception); \
|
||||
ogs_warn("[%s] Failure in transaction; no context " \
|
||||
"restoration.", (amf_ue)->supi); \
|
||||
} \
|
||||
"restoration.", (amf_ue)->supi); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
typedef enum {
|
||||
|
|
@ -1706,6 +1706,7 @@ static void common_register_state(ogs_fsm_t *s, amf_event_t *e,
|
|||
|
||||
switch (nas_message->gmm.h.message_type) {
|
||||
case OGS_NAS_5GS_REGISTRATION_REQUEST:
|
||||
amf_ue->registration_session_release_after_authentication = false;
|
||||
ogs_info("Registration request");
|
||||
gmm_cause = gmm_handle_registration_request(
|
||||
amf_ue, h, e->ngap.code,
|
||||
|
|
@ -1781,6 +1782,7 @@ static void common_register_state(ogs_fsm_t *s, amf_event_t *e,
|
|||
}
|
||||
|
||||
if (h.integrity_protected && SECURITY_CONTEXT_IS_VALID(amf_ue)) {
|
||||
amf_ue->can_restore_context = false;
|
||||
|
||||
/*
|
||||
* If the OLD RAN_UE is being maintained in AMF-UE Context,
|
||||
|
|
@ -1848,36 +1850,13 @@ static void common_register_state(ogs_fsm_t *s, amf_event_t *e,
|
|||
OGS_FSM_TRAN(s, &gmm_state_registered);
|
||||
|
||||
} else {
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.ue_location = true;
|
||||
param.ue_timezone = true;
|
||||
|
||||
/*
|
||||
* SUCI handling may have adopted the old UE's sessions and
|
||||
* their pending SBI transactions after xact_count was saved.
|
||||
* The count can therefore grow even when release-all sends
|
||||
* no new request because no SM context remains. Comparing
|
||||
* against that stale count would skip the AUSF request but
|
||||
* still enter gmm_state_authentication, stalling registration.
|
||||
*
|
||||
* Refresh the baseline immediately before release-all to
|
||||
* detect newly sent requests. AMF_SESSION_RELEASE_PENDING()
|
||||
* still covers releases that were already in progress.
|
||||
*/
|
||||
xact_count = amf_sess_xact_count(amf_ue);
|
||||
amf_sbi_send_release_all_sessions(
|
||||
ran_ue, amf_ue,
|
||||
AMF_RELEASE_SM_CONTEXT_NO_STATE, ¶m);
|
||||
|
||||
if (!AMF_SESSION_RELEASE_PENDING(amf_ue) &&
|
||||
amf_sess_xact_count(amf_ue) == xact_count) {
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
amf_ue->registration_session_release_after_authentication = true;
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
|
||||
OGS_FSM_TRAN(s, &gmm_state_authentication);
|
||||
}
|
||||
|
|
@ -2192,7 +2171,7 @@ void gmm_state_authentication(ogs_fsm_t *s, amf_event_t *e)
|
|||
amf_ue, &nas_message->gmm.authentication_response);
|
||||
|
||||
if (rv != OGS_OK) {
|
||||
ogs_error("gmm_handle_authentication_response() failed");
|
||||
ogs_warn("gmm_handle_authentication_response() failed");
|
||||
r = nas_5gs_send_authentication_reject(amf_ue);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
|
|
@ -2313,7 +2292,14 @@ void gmm_state_authentication(ogs_fsm_t *s, amf_event_t *e)
|
|||
break;
|
||||
|
||||
case OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE:
|
||||
ogs_warn("[%s] Deregistration request", amf_ue->supi);
|
||||
if (amf_ue->can_restore_context &&
|
||||
(!h.integrity_protected ||
|
||||
!SECURITY_CONTEXT_IS_VALID(amf_ue))) {
|
||||
ogs_warn("[%s] Ignore unverified Deregistration request "
|
||||
"during authentication", amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
ogs_error("[%s] Deregistration request", amf_ue->supi);
|
||||
|
||||
gmm_handle_deregistration_request(
|
||||
amf_ue, &nas_message->gmm.deregistration_request_from_ue);
|
||||
|
|
@ -2640,24 +2626,42 @@ void gmm_state_authentication(ogs_fsm_t *s, amf_event_t *e)
|
|||
static void gmm_continue_registration(ogs_fsm_t *s, amf_ue_t *amf_ue)
|
||||
{
|
||||
int r;
|
||||
ran_ue_t *ran_ue = NULL;
|
||||
amf_nsmf_pdusession_sm_context_param_t param;
|
||||
|
||||
ogs_assert(s);
|
||||
ogs_assert(amf_ue);
|
||||
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nudm_uecm, NULL,
|
||||
amf_nudm_uecm_build_registration, amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
|
||||
if (amf_ue->nas.message_type == OGS_NAS_5GS_REGISTRATION_REQUEST) {
|
||||
OGS_FSM_TRAN(s, &gmm_state_initial_context_setup);
|
||||
if (amf_ue->registration_session_release_after_authentication) {
|
||||
amf_ue->registration_session_release_after_authentication = false;
|
||||
ran_ue = ran_ue_find_by_id(amf_ue->ran_ue_id);
|
||||
ogs_assert(ran_ue);
|
||||
|
||||
memset(¶m, 0, sizeof(param));
|
||||
param.ue_location = true;
|
||||
param.ue_timezone = true;
|
||||
amf_ue->registration_session_release_pending = true;
|
||||
amf_sbi_send_release_all_sessions(ran_ue, amf_ue,
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION, ¶m);
|
||||
if (!AMF_SESSION_SYNC_DONE(amf_ue,
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION))
|
||||
return;
|
||||
amf_ue->registration_session_release_pending = false;
|
||||
}
|
||||
} else if (amf_ue->nas.message_type == OGS_NAS_5GS_SERVICE_REQUEST) {
|
||||
OGS_FSM_TRAN(s, &gmm_state_registered);
|
||||
} else {
|
||||
ogs_fatal("Invalid OGS_NAS_5GS[%d]", amf_ue->nas.message_type);
|
||||
ogs_assert_if_reached();
|
||||
}
|
||||
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nudm_uecm, NULL,
|
||||
amf_nudm_uecm_build_registration, amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
|
||||
static void gmm_complete_equipment_identity_check(
|
||||
|
|
@ -2799,14 +2803,16 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
|
||||
h.type = e->nas.type;
|
||||
|
||||
if (amf_ue->eir_check_pending &&
|
||||
nas_message->gmm.h.message_type !=
|
||||
OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE) {
|
||||
ogs_error("[%s] Ignore NAS message while 5G-EIR check is pending",
|
||||
amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (nas_message->gmm.h.message_type) {
|
||||
case OGS_NAS_5GS_SECURITY_MODE_COMPLETE:
|
||||
if (amf_ue->eir_check_pending) {
|
||||
ogs_error("[%s] Ignore repeated Security mode complete",
|
||||
amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
|
||||
ogs_info("[%s] Security mode complete", amf_ue->supi);
|
||||
|
||||
/*
|
||||
|
|
@ -2836,13 +2842,6 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the OLD RAN_UE is being maintained in AMF-UE Context,
|
||||
* it deletes the NG Context after exchanging
|
||||
* the UEContextReleaseCommand/Complete with the gNB
|
||||
*/
|
||||
CLEAR_NG_CONTEXT(amf_ue);
|
||||
|
||||
CLEAR_AMF_UE_TIMER(amf_ue->t3560);
|
||||
|
||||
gmm_cause = gmm_handle_security_mode_complete(
|
||||
|
|
@ -2854,10 +2853,18 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
r = nas_5gs_send_gmm_reject(ran_ue, amf_ue, gmm_cause);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
OGS_FSM_TRAN(s, gmm_state_exception);
|
||||
AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s);
|
||||
break;
|
||||
}
|
||||
|
||||
amf_ue->can_restore_context = false;
|
||||
/*
|
||||
* If the OLD RAN_UE is being maintained in AMF-UE Context,
|
||||
* it deletes the NG Context after exchanging
|
||||
* the UEContextReleaseCommand/Complete with the gNB
|
||||
*/
|
||||
CLEAR_NG_CONTEXT(amf_ue);
|
||||
|
||||
if (amf_ue->amf_ue_context_transfer_state ==
|
||||
UE_CONTEXT_TRANSFER_NEW_AMF_STATE) {
|
||||
/*
|
||||
|
|
@ -2904,11 +2911,10 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
ogs_warn("[%s] Security mode reject : Cause[%d]",
|
||||
amf_ue->supi,
|
||||
nas_message->gmm.security_mode_reject.gmm_cause);
|
||||
CLEAR_AMF_UE_TIMER(amf_ue->t3560);
|
||||
OGS_FSM_TRAN(s, &gmm_state_exception);
|
||||
AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s);
|
||||
break;
|
||||
case OGS_NAS_5GS_REGISTRATION_REQUEST:
|
||||
ogs_warn("Registration request");
|
||||
ogs_error("Registration request");
|
||||
gmm_cause = gmm_handle_registration_request(
|
||||
amf_ue, h, e->ngap.code,
|
||||
&nas_message->gmm.registration_request);
|
||||
|
|
@ -2918,7 +2924,7 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
r = nas_5gs_send_registration_reject(ran_ue, amf_ue, gmm_cause);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
OGS_FSM_TRAN(s, gmm_state_exception);
|
||||
AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2938,7 +2944,7 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
OGS_FSM_TRAN(s, &gmm_state_exception);
|
||||
AMF_RESTORE_CONTEXT_ON_FAILURE(amf_ue, s);
|
||||
break;
|
||||
|
||||
case OGS_NAS_5GS_5GMM_STATUS:
|
||||
|
|
@ -2947,7 +2953,14 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
break;
|
||||
|
||||
case OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE:
|
||||
ogs_warn("[%s] Deregistration request", amf_ue->supi);
|
||||
if ((amf_ue->can_restore_context || amf_ue->eir_check_pending) &&
|
||||
(!h.integrity_protected ||
|
||||
!SECURITY_CONTEXT_IS_VALID(amf_ue))) {
|
||||
ogs_warn("[%s] Ignore unverified Deregistration request "
|
||||
"during security mode", amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
ogs_error("[%s] Deregistration request", amf_ue->supi);
|
||||
|
||||
gmm_handle_deregistration_request(
|
||||
amf_ue, &nas_message->gmm.deregistration_request_from_ue);
|
||||
|
|
@ -3127,7 +3140,7 @@ void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e)
|
|||
|
||||
void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
||||
{
|
||||
int rv, r, state, xact_count = 0;
|
||||
int rv, r, state;
|
||||
ogs_nas_5gmm_cause_t gmm_cause;
|
||||
|
||||
amf_ue_t *amf_ue = NULL;
|
||||
|
|
@ -3139,7 +3152,6 @@ void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
|||
ogs_sbi_message_t *sbi_message = NULL;
|
||||
|
||||
gmm_configuration_update_command_param_t gmm_param;
|
||||
amf_nsmf_pdusession_sm_context_param_t nsmf_param;
|
||||
|
||||
int service_name_id = OpenAPI_service_name_NULL;
|
||||
|
||||
|
|
@ -3161,6 +3173,8 @@ void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
|||
case OGS_FSM_ENTRY_SIG:
|
||||
break;
|
||||
case OGS_FSM_EXIT_SIG:
|
||||
if (amf_ue->registration_session_release_pending)
|
||||
amf_sbi_cancel_registration_session_release(amf_ue);
|
||||
break;
|
||||
|
||||
case OGS_EVENT_SBI_CLIENT:
|
||||
|
|
@ -3168,6 +3182,12 @@ void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
|||
ogs_assert(sbi_message);
|
||||
state = e->h.sbi.state;
|
||||
|
||||
if (amf_ue->registration_session_release_pending) {
|
||||
ogs_error("[%s] Ignore SBI result while registration session "
|
||||
"release is pending", amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
|
||||
service_name_id = ogs_sbi_service_name_id_from_string(
|
||||
sbi_message->h.service.name);
|
||||
switch (service_name_id) {
|
||||
|
|
@ -3477,7 +3497,11 @@ void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
|||
|
||||
h.type = e->nas.type;
|
||||
|
||||
xact_count = amf_sess_xact_count(amf_ue);
|
||||
if (amf_ue->registration_session_release_pending) {
|
||||
ogs_error("[%s] Ignore NAS message while registration session "
|
||||
"release is pending", amf_ue->supi);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (nas_message->gmm.h.message_type) {
|
||||
case OGS_NAS_5GS_REGISTRATION_COMPLETE:
|
||||
|
|
@ -3579,23 +3603,13 @@ void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e)
|
|||
break;
|
||||
}
|
||||
|
||||
memset(&nsmf_param, 0, sizeof(nsmf_param));
|
||||
nsmf_param.ue_location = true;
|
||||
nsmf_param.ue_timezone = true;
|
||||
|
||||
amf_sbi_send_release_all_sessions(
|
||||
ran_ue, amf_ue,
|
||||
AMF_RELEASE_SM_CONTEXT_NO_STATE, &nsmf_param);
|
||||
|
||||
if (!AMF_SESSION_RELEASE_PENDING(amf_ue) &&
|
||||
amf_sess_xact_count(amf_ue) == xact_count) {
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
amf_ue->registration_session_release_after_authentication = true;
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
OGS_FSM_TRAN(s, &gmm_state_authentication);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -838,6 +838,16 @@ void ngap_handle_initial_ue_message(amf_gnb_t *gnb, ogs_ngap_message_t *message)
|
|||
* or associate this RAN UE with the AMF UE.
|
||||
*/
|
||||
if (amf_ue) {
|
||||
if (amf_ue->registration_session_release_pending) {
|
||||
ogs_error("[%s] Ignore new NG context while registration session "
|
||||
"release is pending", amf_ue->supi);
|
||||
r = ngap_send_ran_ue_context_release_command(ran_ue,
|
||||
NGAP_Cause_PR_nas, NGAP_CauseNas_normal_release,
|
||||
NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE, 0);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
return;
|
||||
}
|
||||
/* If NAS(amf_ue_t) has already been associated with
|
||||
* older NG(ran_ue_t) context */
|
||||
if (CM_CONNECTED(amf_ue)) {
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ static void amf_nnrf_send_session_failure_to_ran(
|
|||
void amf_nnrf_handle_nf_discover(
|
||||
ogs_sbi_xact_t *xact, ogs_sbi_message_t *recvmsg)
|
||||
{
|
||||
int r;
|
||||
int r, state;
|
||||
|
||||
ogs_sbi_nf_instance_t *nf_instance = NULL;
|
||||
ogs_sbi_object_t *sbi_object = NULL;
|
||||
|
|
@ -142,6 +142,7 @@ void amf_nnrf_handle_nf_discover(
|
|||
ogs_assert(xact);
|
||||
sbi_object = xact->sbi_object;
|
||||
ogs_assert(sbi_object);
|
||||
state = xact->state;
|
||||
service_name = xact->service_name;
|
||||
ogs_assert(service_name);
|
||||
target_nf_type = ogs_sbi_service_name_to_nf_type(service_name);
|
||||
|
|
@ -241,6 +242,12 @@ void amf_nnrf_handle_nf_discover(
|
|||
ogs_assert(r != OGS_ERROR);
|
||||
break;
|
||||
case OGS_SBI_OBJ_SESS_TYPE:
|
||||
if (state == AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION) {
|
||||
amf_sbi_fail_registration_session_release(amf_ue,
|
||||
ran_ue_find_by_id(ran_ue_id),
|
||||
OGS_SBI_HTTP_STATUS_GATEWAY_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
ogs_warn("[%d:%d] (NF discover) No [%s]", sess->psi, sess->pti,
|
||||
OpenAPI_service_name_ToString(service_name));
|
||||
|
||||
|
|
@ -362,6 +369,13 @@ void amf_nnrf_handle_failed_amf_discovery(
|
|||
ogs_error("[%s:%s:%d:%d] Cannot receive SBI message",
|
||||
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
|
||||
|
||||
if (state == AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION) {
|
||||
amf_sbi_fail_registration_session_release(amf_ue,
|
||||
ran_ue_find_by_id(ran_ue_id),
|
||||
OGS_SBI_HTTP_STATUS_GATEWAY_TIMEOUT);
|
||||
break;
|
||||
}
|
||||
|
||||
if (state == AMF_UPDATE_SM_CONTEXT_STALE_USER_PLANE) {
|
||||
/*
|
||||
* [Issue #4741]
|
||||
|
|
|
|||
|
|
@ -1239,6 +1239,38 @@ int amf_nsmf_pdusession_handle_release_sm_context(
|
|||
return OGS_ERROR;
|
||||
}
|
||||
|
||||
if (state == AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION) {
|
||||
bool continue_registration =
|
||||
amf_ue->registration_session_release_pending &&
|
||||
amf_ue->nas.message_type == OGS_NAS_5GS_REGISTRATION_REQUEST &&
|
||||
OGS_FSM_CHECK(&amf_ue->sm, gmm_state_initial_context_setup);
|
||||
|
||||
AMF_SESS_CLEAR(sess);
|
||||
if (!continue_registration) {
|
||||
ogs_error("[%s] Ignore stale registration session release result",
|
||||
amf_ue->supi);
|
||||
return OGS_OK;
|
||||
}
|
||||
|
||||
if (!ran_ue || !RAN_UE_IS_SERVING(amf_ue, ran_ue)) {
|
||||
amf_sbi_cancel_registration_session_release(amf_ue);
|
||||
ogs_error("[%s] No serving NG context after registration session "
|
||||
"release", amf_ue->supi);
|
||||
return OGS_OK;
|
||||
}
|
||||
|
||||
if (AMF_SESSION_SYNC_DONE(amf_ue,
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION)) {
|
||||
amf_ue->registration_session_release_pending = false;
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nudm_uecm, NULL,
|
||||
amf_nudm_uecm_build_registration, amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
return OGS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* To check if Reactivation Request has been used.
|
||||
*
|
||||
|
|
@ -1493,12 +1525,16 @@ int amf_nsmf_pdusession_handle_release_sm_context(
|
|||
} else if (OGS_FSM_CHECK(&amf_ue->sm,
|
||||
gmm_state_authentication)) {
|
||||
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
/* A new registration may already have started AKA
|
||||
* while this older session release was pending. */
|
||||
if (!amf_ue->registration_session_release_after_authentication) {
|
||||
r = amf_ue_sbi_discover_and_send(
|
||||
OpenAPI_service_name_nausf_auth, NULL,
|
||||
amf_nausf_auth_build_authenticate,
|
||||
amf_ue, 0, NULL);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
|
||||
} else if (OGS_FSM_CHECK(
|
||||
&amf_ue->sm, gmm_state_security_mode)) {
|
||||
|
|
|
|||
|
|
@ -916,6 +916,56 @@ void amf_sbi_send_release_session(
|
|||
}
|
||||
}
|
||||
|
||||
void amf_sbi_cancel_registration_session_release(amf_ue_t *amf_ue)
|
||||
{
|
||||
amf_sess_t *sess = NULL;
|
||||
ogs_sbi_xact_t *xact = NULL, *next_xact = NULL;
|
||||
|
||||
ogs_assert(amf_ue);
|
||||
|
||||
amf_ue->registration_session_release_after_authentication = false;
|
||||
amf_ue->registration_session_release_pending = false;
|
||||
ogs_list_for_each(&amf_ue->sess_list, sess) {
|
||||
ogs_list_for_each_safe(&sess->sbi.xact_list, next_xact, xact) {
|
||||
if (xact->state ==
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION)
|
||||
ogs_sbi_xact_remove(xact);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void amf_sbi_fail_registration_session_release(
|
||||
amf_ue_t *amf_ue, ran_ue_t *ran_ue, int status)
|
||||
{
|
||||
int r;
|
||||
|
||||
ogs_assert(amf_ue);
|
||||
|
||||
if (!amf_ue->registration_session_release_pending ||
|
||||
amf_ue->nas.message_type != OGS_NAS_5GS_REGISTRATION_REQUEST ||
|
||||
!OGS_FSM_CHECK(&amf_ue->sm, gmm_state_initial_context_setup)) {
|
||||
ogs_error("[%s] Ignore stale registration session release failure",
|
||||
amf_ue->supi);
|
||||
return;
|
||||
}
|
||||
|
||||
amf_sbi_cancel_registration_session_release(amf_ue);
|
||||
if (!ran_ue || !RAN_UE_IS_SERVING(amf_ue, ran_ue)) {
|
||||
ogs_error("[%s] No serving NG context after registration session "
|
||||
"release failure", amf_ue->supi);
|
||||
return;
|
||||
}
|
||||
|
||||
r = nas_5gs_send_gmm_reject_from_sbi(amf_ue, status);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
r = ngap_send_ran_ue_context_release_command(ran_ue,
|
||||
NGAP_Cause_PR_nas, NGAP_CauseNas_normal_release,
|
||||
NGAP_UE_CTX_REL_UE_CONTEXT_REMOVE, 0);
|
||||
ogs_expect(r == OGS_OK);
|
||||
ogs_assert(r != OGS_ERROR);
|
||||
}
|
||||
|
||||
void amf_sbi_send_release_all_sessions(
|
||||
ran_ue_t *ran_ue, amf_ue_t *amf_ue, int state, void *data)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ bool amf_sbi_send_request(
|
|||
#define AMF_RELEASE_SM_CONTEXT_NO_STATE 31
|
||||
#define AMF_RELEASE_SM_CONTEXT_REGISTRATION_ACCEPT 33
|
||||
#define AMF_RELEASE_SM_CONTEXT_SERVICE_ACCEPT 34
|
||||
#define AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION 35
|
||||
#define AMF_REMOVE_S1_CONTEXT_BY_LO_CONNREFUSED 51
|
||||
#define AMF_REMOVE_S1_CONTEXT_BY_RESET_ALL 52
|
||||
#define AMF_REMOVE_S1_CONTEXT_BY_RESET_PARTIAL 53
|
||||
|
|
@ -125,6 +126,9 @@ void amf_sbi_send_deactivate_stale_user_plane(ran_ue_t *ran_ue);
|
|||
|
||||
void amf_sbi_send_release_session(
|
||||
ran_ue_t *ran_ue, amf_sess_t *sess, int state, void *data);
|
||||
void amf_sbi_cancel_registration_session_release(amf_ue_t *amf_ue);
|
||||
void amf_sbi_fail_registration_session_release(
|
||||
amf_ue_t *amf_ue, ran_ue_t *ran_ue, int status);
|
||||
void amf_sbi_send_release_all_sessions(
|
||||
ran_ue_t *ran_ue, amf_ue_t *amf_ue, int state, void *data);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,4 +25,4 @@ testapp_310014_exe = executable('310014',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtestapp_dep)
|
||||
|
||||
test('310014', testapp_310014_exe, is_parallel : false, suite: 'app')
|
||||
test('310014', testapp_310014_exe, is_parallel : false, suite: ['310014', 'epc'])
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ testapp_attach_exe = executable('attach',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtestepc_dep)
|
||||
|
||||
test('attach', testapp_attach_exe, is_parallel : false, suite: 'epc')
|
||||
test('attach', testapp_attach_exe, is_parallel : false, suite: ['attach', 'epc'])
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ void testgmm_recv(test_ue_t *test_ue, ogs_pkbuf_t *pkbuf)
|
|||
&message.gmm.authentication_request);
|
||||
break;
|
||||
case OGS_NAS_5GS_AUTHENTICATION_REJECT:
|
||||
case OGS_NAS_5GS_DEREGISTRATION_ACCEPT_FROM_UE:
|
||||
break;
|
||||
case OGS_NAS_5GS_SECURITY_MODE_COMMAND:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@ testapp_csfb_exe = executable('csfb',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtestepc_dep)
|
||||
|
||||
test('csfb', testapp_csfb_exe, is_parallel : false, suite: 'epc')
|
||||
test('csfb', testapp_csfb_exe, is_parallel : false, suite: ['csfb', 'epc'])
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@ testapp_dns_exe = executable('dns',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtestepc_dep)
|
||||
|
||||
test('dns', testapp_dns_exe, is_parallel : false, suite: 'epc')
|
||||
test('dns', testapp_dns_exe, is_parallel : false, suite: ['dns', 'epc'])
|
||||
|
||||
endif
|
||||
|
|
|
|||
710
tests/eir/amf-guti-test.c
Normal file
710
tests/eir/amf-guti-test.c
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
/* Copyright (C) 2026 by Sukchan Lee <acetcom@gmail.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later */
|
||||
|
||||
#include "integration.h"
|
||||
#include <poll.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define AMF_GUTI_TEST_PEI "imeisv-8665070400405307"
|
||||
|
||||
typedef enum {
|
||||
BAD_RES,
|
||||
AUTH_FAILURE,
|
||||
SMC_REJECT,
|
||||
DEREG_DURING_AUTH,
|
||||
DEREG_DURING_SMC,
|
||||
REREGISTER,
|
||||
EIR_REJECT,
|
||||
} scenario_t;
|
||||
|
||||
static const struct {
|
||||
scenario_t scenario;
|
||||
unsigned int sessions;
|
||||
} cases[] = {
|
||||
{ BAD_RES, 1 },
|
||||
{ AUTH_FAILURE, 1 },
|
||||
{ SMC_REJECT, 1 },
|
||||
{ DEREG_DURING_AUTH, 1 },
|
||||
{ DEREG_DURING_SMC, 1 },
|
||||
{ REREGISTER, 1 },
|
||||
{ EIR_REJECT, 1 },
|
||||
{ AUTH_FAILURE, 2 },
|
||||
{ REREGISTER, 2 },
|
||||
};
|
||||
|
||||
static bool send_ngap(abts_case *tc, ogs_socknode_t *ngap, ogs_pkbuf_t *buf)
|
||||
{
|
||||
int rv;
|
||||
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return false;
|
||||
rv = testgnb_ngap_send(ngap, buf);
|
||||
ABTS_INT_EQUAL(tc, OGS_OK, rv);
|
||||
return rv == OGS_OK;
|
||||
}
|
||||
|
||||
static bool send_nas(abts_case *tc, ogs_socknode_t *ngap,
|
||||
test_ue_t *ue, ogs_pkbuf_t *buf)
|
||||
{
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return false;
|
||||
return send_ngap(tc, ngap, testngap_build_uplink_nas_transport(ue, buf));
|
||||
}
|
||||
|
||||
static bool send_plain_deregistration(abts_case *tc,
|
||||
ogs_socknode_t *ngap, test_ue_t *ue)
|
||||
{
|
||||
ogs_nas_5gs_message_t message;
|
||||
ogs_nas_5gs_mobile_identity_guti_t *identity;
|
||||
ogs_pkbuf_t *buf, *copy;
|
||||
bool valid;
|
||||
int rv;
|
||||
|
||||
buf = testgmm_build_de_registration_request(ue, true, false, false);
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return false;
|
||||
copy = ogs_pkbuf_copy(buf);
|
||||
ABTS_PTR_NOTNULL(tc, copy);
|
||||
if (!copy) {
|
||||
ogs_pkbuf_free(buf);
|
||||
return false;
|
||||
}
|
||||
memset(&message, 0, sizeof(message));
|
||||
rv = ogs_nas_5gmm_decode(&message, copy);
|
||||
ABTS_INT_EQUAL(tc, OGS_OK, rv);
|
||||
identity = message.gmm.deregistration_request_from_ue.mobile_identity.buffer;
|
||||
/* Ensure this stimulus reaches the AMF's guard instead of failing decode. */
|
||||
valid = rv == OGS_OK &&
|
||||
message.gmm.h.security_header_type == OGS_NAS_SECURITY_HEADER_PLAIN_NAS_MESSAGE &&
|
||||
message.gmm.h.extended_protocol_discriminator ==
|
||||
OGS_NAS_EXTENDED_PROTOCOL_DISCRIMINATOR_5GMM &&
|
||||
message.gmm.h.message_type == OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE &&
|
||||
message.gmm.deregistration_request_from_ue.de_registration_type.switch_off == 1 &&
|
||||
message.gmm.deregistration_request_from_ue.mobile_identity.length ==
|
||||
sizeof(*identity) && identity &&
|
||||
identity->h.type == OGS_NAS_5GS_MOBILE_IDENTITY_GUTI &&
|
||||
be32toh(identity->m_tmsi) == ue->nas_5gs_guti.m_tmsi;
|
||||
ABTS_TRUE(tc, valid);
|
||||
ogs_pkbuf_free(copy);
|
||||
if (!valid) {
|
||||
ogs_pkbuf_free(buf);
|
||||
return false;
|
||||
}
|
||||
return send_nas(tc, ngap, ue, buf);
|
||||
}
|
||||
|
||||
static bool receive_ngap(abts_case *tc, ogs_socknode_t *ngap,
|
||||
test_ue_t *ue, long procedure, int gmm, int gsm)
|
||||
{
|
||||
#if !HAVE_USRSCTP
|
||||
struct pollfd fd = { ngap->sock->fd, POLLIN, 0 };
|
||||
int rv;
|
||||
#endif
|
||||
ogs_time_t deadline = ogs_time_now() + ogs_time_from_sec(5);
|
||||
ogs_pkbuf_t *buf;
|
||||
|
||||
while (ogs_time_now() < deadline) {
|
||||
uint64_t ran_id = ue->ran_ue_ngap_id;
|
||||
uint64_t amf_id = ue->amf_ue_ngap_id;
|
||||
#if !HAVE_USRSCTP
|
||||
ogs_time_t remaining = deadline - ogs_time_now();
|
||||
if (remaining <= 0)
|
||||
break;
|
||||
rv = poll(&fd, 1, ogs_time_to_msec(remaining) + 1);
|
||||
if (rv < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (rv < 0 || (rv > 0 &&
|
||||
(fd.revents & (POLLERR | POLLHUP | POLLNVAL)))) {
|
||||
ABTS_FAIL(tc, "Cannot poll the NGAP socket");
|
||||
return false;
|
||||
}
|
||||
if (rv <= 0 || !(fd.revents & POLLIN))
|
||||
break;
|
||||
#endif
|
||||
/* usrsctp has no native fd; Meson's timeout bounds its SCTP reader. */
|
||||
buf = testgnb_ngap_read(ngap);
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return false;
|
||||
ue->gmm_message_type = ue->gsm_message_type = 0;
|
||||
testngap_recv(ue, buf);
|
||||
/* The held RAN context can be released before or after the next NAS
|
||||
* message. Acknowledge it without replacing the current RAN IDs. */
|
||||
if (procedure != NGAP_ProcedureCode_id_UEContextRelease && ran_id &&
|
||||
ue->ngap_procedure_code == NGAP_ProcedureCode_id_UEContextRelease &&
|
||||
ue->ran_ue_ngap_id != ran_id) {
|
||||
bool sent = send_ngap(tc, ngap,
|
||||
testngap_build_ue_context_release_complete(ue));
|
||||
ue->ran_ue_ngap_id = ran_id;
|
||||
ue->amf_ue_ngap_id = amf_id;
|
||||
if (!sent)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
ABTS_INT_EQUAL(tc, procedure, ue->ngap_procedure_code);
|
||||
if (gmm)
|
||||
ABTS_INT_EQUAL(tc, gmm, ue->gmm_message_type);
|
||||
if (gsm)
|
||||
ABTS_INT_EQUAL(tc, gsm, ue->gsm_message_type);
|
||||
return ue->ngap_procedure_code == procedure &&
|
||||
(!gmm || ue->gmm_message_type == gmm) &&
|
||||
(!gsm || ue->gsm_message_type == gsm);
|
||||
}
|
||||
ABTS_FAIL(tc, "Timed out waiting for the expected NGAP message");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matching_qfi(ogs_pkbuf_t *buf, int header_len, uint8_t qfi)
|
||||
{
|
||||
ogs_gtp2_header_t *header = (ogs_gtp2_header_t *)buf->data;
|
||||
int offset = OGS_GTPV1U_HEADER_LEN + OGS_GTPV1U_EXTENSION_HEADER_LEN;
|
||||
uint8_t type;
|
||||
bool matched = false;
|
||||
|
||||
if (!(header->flags & OGS_GTPU_FLAGS_E) || header_len < offset + 4)
|
||||
return false;
|
||||
type = buf->data[offset - 1];
|
||||
while (type) {
|
||||
uint8_t *ext = buf->data + offset;
|
||||
int len;
|
||||
|
||||
if (offset + 4 > header_len)
|
||||
return false;
|
||||
len = ext[0] * 4;
|
||||
if (len < 4 || offset + len > header_len)
|
||||
return false;
|
||||
if (type == OGS_GTP2_EXTENSION_HEADER_TYPE_PDU_SESSION_CONTAINER) {
|
||||
if (matched || (ext[1] >> 4) !=
|
||||
OGS_GTP2_EXTENSION_HEADER_PDU_TYPE_DL_PDU_SESSION_INFORMATION ||
|
||||
(ext[2] & 0x3f) != qfi)
|
||||
return false;
|
||||
matched = true;
|
||||
}
|
||||
type = ext[len - 1];
|
||||
offset += len;
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/* Match a fresh Echo Reply on the expected N3 tunnel and QFI. In the unfixed
|
||||
* AMF, Authentication Request follows old-session deletion, so the original
|
||||
* tunnel probe at that point fails without waiting for an AKA timeout. */
|
||||
|
||||
static bool check_bearer(abts_case *tc, ogs_socknode_t *gtpu,
|
||||
test_bearer_t *bearer, bool alive)
|
||||
{
|
||||
static uint16_t sequence;
|
||||
ogs_gtp2_header_desc_t desc;
|
||||
ogs_pkbuf_t *buf;
|
||||
struct pollfd fd = { gtpu->sock->fd, POLLIN, 0 };
|
||||
uint16_t packet_words[18] = { 0 };
|
||||
uint8_t *packet = (uint8_t *)packet_words;
|
||||
uint16_t value, seq = ++sequence;
|
||||
uint32_t dst;
|
||||
int round;
|
||||
bool reply = false;
|
||||
|
||||
ogs_assert(inet_pton(AF_INET, TEST_PING_IPV4, &dst) == 1);
|
||||
packet[0] = 0x45;
|
||||
packet[2] = 0; packet[3] = sizeof(packet_words);
|
||||
packet[8] = 64; packet[9] = IPPROTO_ICMP;
|
||||
memcpy(packet + 12, &bearer->sess->ue_ip.addr, 4);
|
||||
memcpy(packet + 16, &dst, 4);
|
||||
packet[20] = 8; /* Echo Request */
|
||||
packet[24] = 0x45; packet[25] = 0x77;
|
||||
packet[26] = seq >> 8; packet[27] = seq;
|
||||
memcpy(packet + 28, "guti-amf", 8);
|
||||
value = ogs_in_cksum((uint16_t *)(packet + 20), 16);
|
||||
memcpy(packet + 22, &value, 2);
|
||||
value = ogs_in_cksum((uint16_t *)packet, 20);
|
||||
memcpy(packet + 10, &value, 2);
|
||||
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.type = OGS_GTPU_MSGTYPE_GPDU;
|
||||
desc.teid = bearer->sess->upf_n3_teid;
|
||||
desc.pdu_type =
|
||||
OGS_GTP2_EXTENSION_HEADER_PDU_TYPE_UL_PDU_SESSION_INFORMATION;
|
||||
desc.qos_flow_identifier = bearer->qfi;
|
||||
for (round = 0; round < (alive ? 3 : 1) && !reply; round++) {
|
||||
ogs_time_t deadline = ogs_time_now() + ogs_time_from_sec(1);
|
||||
|
||||
buf = ogs_pkbuf_alloc(NULL, 128);
|
||||
ogs_assert(buf);
|
||||
ogs_pkbuf_reserve(buf, OGS_GTPV1U_5GC_HEADER_LEN);
|
||||
ogs_pkbuf_put_data(buf, packet, sizeof(packet_words));
|
||||
ABTS_INT_EQUAL(tc, OGS_OK, test_gtpu_send(gtpu, bearer, &desc, buf));
|
||||
while (ogs_time_now() < deadline) {
|
||||
ogs_gtp2_header_t *gtp;
|
||||
ogs_gtp2_header_desc_t received;
|
||||
uint8_t *ip;
|
||||
int rv, header_len, ip_len;
|
||||
ogs_time_t remaining = deadline - ogs_time_now();
|
||||
|
||||
if (remaining <= 0)
|
||||
break;
|
||||
rv = poll(&fd, 1, ogs_time_to_msec(remaining) + 1);
|
||||
if (rv < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (rv < 0 || (rv > 0 &&
|
||||
(fd.revents & (POLLERR | POLLHUP | POLLNVAL)))) {
|
||||
ABTS_FAIL(tc, "Cannot poll the GTP-U socket");
|
||||
return false;
|
||||
}
|
||||
if (rv <= 0 || !(fd.revents & POLLIN))
|
||||
break;
|
||||
buf = test_gtpu_read(gtpu);
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return false;
|
||||
if (!buf->len ||
|
||||
buf->len > (unsigned int)(buf->tail - buf->data)) {
|
||||
ogs_pkbuf_free(buf);
|
||||
ABTS_FAIL(tc, "Cannot receive the GTP-U packet");
|
||||
return false;
|
||||
}
|
||||
if (buf->len < OGS_GTPV1U_HEADER_LEN) {
|
||||
ogs_pkbuf_free(buf);
|
||||
continue;
|
||||
}
|
||||
gtp = (ogs_gtp2_header_t *)buf->data;
|
||||
if (gtp->type == OGS_GTPU_MSGTYPE_ERR_IND) {
|
||||
ogs_pkbuf_free(buf);
|
||||
/* Another tunnel's delayed Error Indication may share this
|
||||
* socket. Only this probe's Echo Reply proves it is alive. */
|
||||
continue;
|
||||
}
|
||||
header_len = ogs_gtpu_parse_header(&received, buf);
|
||||
if (gtp->version == OGS_GTP1_VERSION_1 &&
|
||||
gtp->type == OGS_GTPU_MSGTYPE_GPDU &&
|
||||
be32toh(gtp->teid) == bearer->sess->gnb_n3_teid &&
|
||||
header_len >= OGS_GTPV1U_HEADER_LEN &&
|
||||
buf->len >= header_len + sizeof(packet_words) &&
|
||||
matching_qfi(buf, header_len, bearer->qfi)) {
|
||||
ip = buf->data + header_len;
|
||||
ip_len = (ip[0] & 0x0f) * 4;
|
||||
if ((ip[0] >> 4) == 4 && ip_len >= 20 &&
|
||||
buf->len >= header_len + ip_len + 16 &&
|
||||
ip[9] == IPPROTO_ICMP &&
|
||||
!memcmp(ip + 12, &dst, 4) &&
|
||||
!memcmp(ip + 16, &bearer->sess->ue_ip.addr, 4) &&
|
||||
ip[ip_len] == 0 && ip[ip_len + 1] == 0 &&
|
||||
!memcmp(ip + ip_len + 4, packet + 24, 12))
|
||||
reply = true;
|
||||
}
|
||||
ogs_pkbuf_free(buf);
|
||||
if (reply)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reply != alive)
|
||||
ABTS_FAIL(tc, alive ? "No ICMP Echo Reply on the original bearer" :
|
||||
"The deleted bearer still forwards ICMP Echo Replies");
|
||||
return reply == alive;
|
||||
}
|
||||
|
||||
static bool check_bearers(abts_case *tc, ogs_socknode_t *gtpu,
|
||||
test_bearer_t *bearers[], unsigned int count, bool alive)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!check_bearer(tc, gtpu, bearers[i], alive))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static test_ue_t *new_ue(const char *msin)
|
||||
{
|
||||
ogs_nas_5gs_mobile_identity_suci_t suci;
|
||||
test_ue_t *ue;
|
||||
|
||||
memset(&suci, 0, sizeof(suci));
|
||||
suci.h.supi_format = OGS_NAS_5GS_SUPI_FORMAT_IMSI;
|
||||
suci.h.type = OGS_NAS_5GS_MOBILE_IDENTITY_SUCI;
|
||||
suci.routing_indicator2 = suci.routing_indicator3 =
|
||||
suci.routing_indicator4 = 0xf;
|
||||
ue = test_ue_add_by_suci(&suci, msin);
|
||||
ogs_assert(ue);
|
||||
ue->nr_cgi.cell_id = 0x40001;
|
||||
ue->nas.registration.ksi = OGS_NAS_KSI_NO_KEY_IS_AVAILABLE;
|
||||
ue->nas.registration.follow_on_request = 1;
|
||||
ue->nas.registration.value = OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL;
|
||||
ue->k_string = "465b5ce8b199b49faa5f0a2ee238a6bc";
|
||||
ue->opc_string = "e8ed289deba952e4283b54e88e6183ca";
|
||||
ogs_assert(ogs_hex_from_string_checked(
|
||||
ue->k_string, ue->k, sizeof(ue->k)) == OGS_OK);
|
||||
ogs_assert(ogs_hex_from_string_checked(
|
||||
ue->opc_string, ue->opc, sizeof(ue->opc)) == OGS_OK);
|
||||
ue->mobile_identity_imeisv.digit16 = 7;
|
||||
return ue;
|
||||
}
|
||||
|
||||
static ogs_pkbuf_t *registration_request(test_ue_t *ue,
|
||||
bool guti, bool container)
|
||||
{
|
||||
memset(&ue->registration_request_param, 0,
|
||||
sizeof(ue->registration_request_param));
|
||||
ue->registration_request_param.guti = guti;
|
||||
if (container) {
|
||||
ue->registration_request_param.gmm_capability = 1;
|
||||
ue->registration_request_param.s1_ue_network_capability = 1;
|
||||
ue->registration_request_param.requested_nssai = 1;
|
||||
ue->registration_request_param.last_visited_registered_tai = 1;
|
||||
ue->registration_request_param.ue_usage_setting = 1;
|
||||
}
|
||||
return testgmm_build_registration_request(ue, NULL, false, false);
|
||||
}
|
||||
|
||||
static bool finish_registration(abts_case *tc,
|
||||
ogs_socknode_t *ngap, test_ue_t *ue)
|
||||
{
|
||||
if (!receive_ngap(tc, ngap, ue, NGAP_ProcedureCode_id_InitialContextSetup,
|
||||
OGS_NAS_5GS_REGISTRATION_ACCEPT, 0) ||
|
||||
!send_ngap(tc, ngap,
|
||||
testngap_build_ue_radio_capability_info_indication(ue)) ||
|
||||
!send_ngap(tc, ngap,
|
||||
testngap_build_initial_context_setup_response(ue, false)) ||
|
||||
!send_nas(tc, ngap, ue, testgmm_build_registration_complete(ue)))
|
||||
return false;
|
||||
return receive_ngap(tc, ngap, ue, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_CONFIGURATION_UPDATE_COMMAND, 0);
|
||||
}
|
||||
|
||||
static bool establish_session(abts_case *tc,
|
||||
ogs_socknode_t *ngap, test_ue_t *ue, char *dnn, uint8_t psi)
|
||||
{
|
||||
test_sess_t *sess = test_sess_add_by_dnn_and_psi(ue, dnn, psi);
|
||||
test_bearer_t *flow;
|
||||
ogs_pkbuf_t *gsm;
|
||||
|
||||
ogs_assert(sess);
|
||||
sess->ul_nas_transport_param.request_type = OGS_NAS_5GS_REQUEST_TYPE_INITIAL;
|
||||
sess->ul_nas_transport_param.dnn = 1;
|
||||
sess->ul_nas_transport_param.s_nssai = 1;
|
||||
sess->pdu_session_establishment_param.ssc_mode = 1;
|
||||
sess->pdu_session_establishment_param.epco = 1;
|
||||
gsm = testgsm_build_pdu_session_establishment_request(sess);
|
||||
if (!send_nas(tc, ngap, ue, testgmm_build_ul_nas_transport(sess,
|
||||
OGS_NAS_PAYLOAD_CONTAINER_N1_SM_INFORMATION, gsm)) ||
|
||||
!receive_ngap(tc, ngap, ue, NGAP_ProcedureCode_id_PDUSessionResourceSetup,
|
||||
OGS_NAS_5GS_DL_NAS_TRANSPORT,
|
||||
OGS_NAS_5GS_PDU_SESSION_ESTABLISHMENT_ACCEPT))
|
||||
return false;
|
||||
flow = test_qos_flow_find_by_qfi(sess, 1);
|
||||
ABTS_PTR_NOTNULL(tc, flow);
|
||||
ABTS_TRUE(tc, sess->ue_ip.ipv4 && sess->ue_ip.addr);
|
||||
if (!flow)
|
||||
return false;
|
||||
if (!send_ngap(tc, ngap,
|
||||
testngap_sess_build_pdu_session_resource_setup_response(sess)))
|
||||
return false;
|
||||
|
||||
/* Setup Response starts an asynchronous AMF-to-SMF update with no
|
||||
* NAS/NGAP completion acknowledgement. An N3 Echo Reply can arrive before
|
||||
* the AMF receives its SBI response. Allow the initial setup to settle
|
||||
* before starting the GUTI scenario, as in the registration idle tests. */
|
||||
ogs_msleep(100);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool release_ue(abts_case *tc, ogs_socknode_t *ngap, test_ue_t *ue)
|
||||
{
|
||||
uint64_t current_id = ue->ran_ue_ngap_id;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
bool current;
|
||||
if (!receive_ngap(tc, ngap, ue,
|
||||
NGAP_ProcedureCode_id_UEContextRelease, 0, 0))
|
||||
return false;
|
||||
current = ue->ran_ue_ngap_id == current_id;
|
||||
if (!send_ngap(tc, ngap, testngap_build_ue_context_release_complete(ue)))
|
||||
return false;
|
||||
if (current)
|
||||
return true;
|
||||
}
|
||||
ABTS_FAIL(tc, "The current RAN UE context was not released");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool equipment_begin(abts_case *tc)
|
||||
{
|
||||
bson_error_t error;
|
||||
bson_t *query = BCON_NEW("pei", BCON_UTF8(AMF_GUTI_TEST_PEI));
|
||||
bool rv;
|
||||
|
||||
/* Reset only this test's identity, including records from interrupted runs. */
|
||||
rv = mongoc_collection_delete_many(ogs_mongoc()->collection.eir,
|
||||
query, NULL, NULL, &error);
|
||||
if (!rv)
|
||||
ABTS_FAIL(tc, error.message);
|
||||
bson_destroy(query);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool equipment_record(abts_case *tc, bson_oid_t *id, const char *status)
|
||||
{
|
||||
bson_error_t error;
|
||||
bson_t *query = BCON_NEW("_id", BCON_OID(id));
|
||||
bson_t *doc = BCON_NEW("$set", "{",
|
||||
"pei", BCON_UTF8(AMF_GUTI_TEST_PEI),
|
||||
"status", BCON_UTF8(status), "}");
|
||||
bson_t *opts = BCON_NEW("upsert", BCON_BOOL(true));
|
||||
bool rv = mongoc_collection_update_one(ogs_mongoc()->collection.eir,
|
||||
query, doc, opts, NULL, &error);
|
||||
|
||||
if (!rv)
|
||||
ABTS_FAIL(tc, error.message);
|
||||
bson_destroy(opts);
|
||||
bson_destroy(doc);
|
||||
bson_destroy(query);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static ogs_pkbuf_t *bad_authentication_response(abts_case *tc, test_ue_t *ue)
|
||||
{
|
||||
ogs_nas_5gs_message_t message;
|
||||
ogs_pkbuf_t *buf = testgmm_build_authentication_response(ue);
|
||||
ogs_pkbuf_t *result = NULL;
|
||||
int rv;
|
||||
|
||||
ABTS_PTR_NOTNULL(tc, buf);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
rv = ogs_nas_5gmm_decode(&message, buf);
|
||||
ABTS_INT_EQUAL(tc, OGS_OK, rv);
|
||||
if (rv == OGS_OK) {
|
||||
ogs_nas_authentication_response_parameter_t *response =
|
||||
&message.gmm.authentication_response.authentication_response_parameter;
|
||||
ABTS_INT_EQUAL(tc, OGS_AUTN_LEN, response->length);
|
||||
response->res[0] ^= 0xff;
|
||||
result = ogs_nas_5gs_plain_encode(&message);
|
||||
}
|
||||
ogs_pkbuf_free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ogs_pkbuf_t *security_mode_reject(void)
|
||||
{
|
||||
ogs_nas_5gs_message_t message;
|
||||
|
||||
memset(&message, 0, sizeof(message));
|
||||
message.gmm.h.extended_protocol_discriminator =
|
||||
OGS_NAS_EXTENDED_PROTOCOL_DISCRIMINATOR_5GMM;
|
||||
message.gmm.h.message_type = OGS_NAS_5GS_SECURITY_MODE_REJECT;
|
||||
message.gmm.security_mode_reject.gmm_cause =
|
||||
OGS_5GMM_CAUSE_SECURITY_MODE_REJECTED_UNSPECIFIED;
|
||||
return ogs_nas_5gs_plain_encode(&message);
|
||||
}
|
||||
|
||||
static void test_amf_case(abts_case *tc, void *data)
|
||||
{
|
||||
const unsigned int index = *(const unsigned int *)data;
|
||||
scenario_t scenario = cases[index].scenario;
|
||||
unsigned int sessions = cases[index].sessions, i;
|
||||
bool eir = ogs_global_conf()->parameter.eir_count > 0;
|
||||
bool subscriber = false, equipment = false;
|
||||
test_ue_t *victim = NULL, *peer = NULL, *cleanup_ue;
|
||||
test_bearer_t *old_flows[2] = { NULL, NULL }, *new_flow;
|
||||
ogs_socknode_t *ngap = NULL, *gtpu = NULL;
|
||||
ogs_pkbuf_t *container = NULL;
|
||||
bson_oid_t equipment_id;
|
||||
char msin[11];
|
||||
#if !HAVE_USRSCTP
|
||||
struct timeval timeout = { 5, 0 };
|
||||
#endif
|
||||
|
||||
ogs_snprintf(msin, sizeof(msin), "00004577%02u", index);
|
||||
victim = new_ue(msin);
|
||||
if (test_db_insert_ue(victim, sessions == 2 ?
|
||||
test_db_new_ims(victim) : test_db_new_simple(victim)) != OGS_OK) {
|
||||
ABTS_FAIL(tc, "Cannot insert AMF GUTI test subscriber");
|
||||
goto cleanup;
|
||||
}
|
||||
subscriber = true;
|
||||
if (eir) {
|
||||
if (!equipment_begin(tc))
|
||||
goto cleanup;
|
||||
bson_oid_init(&equipment_id, NULL);
|
||||
equipment = true;
|
||||
if (!equipment_record(tc, &equipment_id, "WHITELISTED"))
|
||||
goto cleanup;
|
||||
}
|
||||
ngap = testngap_client(1, AF_INET);
|
||||
gtpu = test_gtpu_server(1, AF_INET);
|
||||
ABTS_PTR_NOTNULL(tc, ngap);
|
||||
ABTS_PTR_NOTNULL(tc, gtpu);
|
||||
if (!ngap || !gtpu)
|
||||
goto cleanup;
|
||||
#if !HAVE_USRSCTP
|
||||
ABTS_INT_EQUAL(tc, 0, setsockopt(ngap->sock->fd, SOL_SOCKET, SO_RCVTIMEO,
|
||||
&timeout, sizeof(timeout)));
|
||||
#endif
|
||||
if (!send_ngap(tc, ngap, testngap_build_ng_setup_request(0x4577 + index, 22)) ||
|
||||
!receive_ngap(tc, ngap, victim, NGAP_ProcedureCode_id_NGSetup, 0, 0) ||
|
||||
!send_ngap(tc, ngap, testngap_build_initial_ue_message(victim,
|
||||
registration_request(victim, false, false),
|
||||
NGAP_RRCEstablishmentCause_mo_Signalling, false, true)) ||
|
||||
!receive_ngap(tc, ngap, victim, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_AUTHENTICATION_REQUEST, 0) ||
|
||||
!send_nas(tc, ngap, victim, testgmm_build_authentication_response(victim)) ||
|
||||
!receive_ngap(tc, ngap, victim, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_SECURITY_MODE_COMMAND, 0) ||
|
||||
!send_nas(tc, ngap, victim, testgmm_build_security_mode_complete(victim,
|
||||
registration_request(victim, false, true))) ||
|
||||
!finish_registration(tc, ngap, victim))
|
||||
goto cleanup;
|
||||
for (i = 0; i < sessions; i++) {
|
||||
test_sess_t *sess;
|
||||
if (!establish_session(tc, ngap, victim, i ? "ims" : "internet", 5 + i))
|
||||
goto cleanup;
|
||||
sess = test_sess_find_by_psi(victim, 5 + i);
|
||||
ogs_assert(sess);
|
||||
old_flows[i] = test_qos_flow_find_by_qfi(sess, 1);
|
||||
ogs_assert(old_flows[i]);
|
||||
}
|
||||
if (sessions == 2) {
|
||||
ABTS_TRUE(tc, old_flows[0]->sess->upf_n3_teid != old_flows[1]->sess->upf_n3_teid);
|
||||
ABTS_TRUE(tc, old_flows[0]->sess->gnb_n3_teid != old_flows[1]->sess->gnb_n3_teid);
|
||||
ABTS_TRUE(tc, old_flows[0]->sess->ue_ip.addr != old_flows[1]->sess->ue_ip.addr);
|
||||
}
|
||||
if (!check_bearers(tc, gtpu, old_flows, sessions, true))
|
||||
goto cleanup;
|
||||
|
||||
/* Copy only the assigned GUTI. The victim keeps the original NAS keys,
|
||||
* counters, PDU sessions and N3 tunnels for preservation probes. */
|
||||
peer = new_ue(msin);
|
||||
peer->nas_5gs_guti = victim->nas_5gs_guti;
|
||||
peer->ran_ue_ngap_id = victim->ran_ue_ngap_id;
|
||||
if (scenario == EIR_REJECT &&
|
||||
!equipment_record(tc, &equipment_id, "BLACKLISTED"))
|
||||
goto cleanup;
|
||||
if (!send_ngap(tc, ngap, testngap_build_initial_ue_message(peer,
|
||||
registration_request(peer, true, false),
|
||||
NGAP_RRCEstablishmentCause_mo_Signalling, false, true)) ||
|
||||
!receive_ngap(tc, ngap, peer, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_AUTHENTICATION_REQUEST, 0) ||
|
||||
!check_bearers(tc, gtpu, old_flows, sessions, true))
|
||||
goto cleanup;
|
||||
ABTS_TRUE(tc, peer->ran_ue_ngap_id != victim->ran_ue_ngap_id);
|
||||
|
||||
if (scenario == DEREG_DURING_AUTH &&
|
||||
!send_plain_deregistration(tc, ngap, peer))
|
||||
goto cleanup;
|
||||
if (scenario == BAD_RES) {
|
||||
if (!send_nas(tc, ngap, peer, bad_authentication_response(tc, peer)))
|
||||
goto cleanup;
|
||||
} else if (scenario == AUTH_FAILURE || scenario == DEREG_DURING_AUTH) {
|
||||
if (!send_nas(tc, ngap, peer, testgmm_build_authentication_failure(
|
||||
peer, OGS_5GMM_CAUSE_MAC_FAILURE, 0)))
|
||||
goto cleanup;
|
||||
} else {
|
||||
if (!send_nas(tc, ngap, peer, testgmm_build_authentication_response(peer)) ||
|
||||
!receive_ngap(tc, ngap, peer, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_SECURITY_MODE_COMMAND, 0) ||
|
||||
!check_bearers(tc, gtpu, old_flows, sessions, true))
|
||||
goto cleanup;
|
||||
if (scenario == DEREG_DURING_SMC &&
|
||||
!send_plain_deregistration(tc, ngap, peer))
|
||||
goto cleanup;
|
||||
if (scenario == SMC_REJECT || scenario == DEREG_DURING_SMC) {
|
||||
if (!send_nas(tc, ngap, peer, security_mode_reject()))
|
||||
goto cleanup;
|
||||
} else {
|
||||
container = registration_request(peer, true, true);
|
||||
if (!send_nas(tc, ngap, peer,
|
||||
testgmm_build_security_mode_complete(peer, container))) {
|
||||
container = NULL;
|
||||
goto cleanup;
|
||||
}
|
||||
container = NULL;
|
||||
if (scenario == EIR_REJECT) {
|
||||
if (!receive_ngap(tc, ngap, peer, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_REGISTRATION_REJECT, 0))
|
||||
goto cleanup;
|
||||
ABTS_INT_EQUAL(tc, OGS_5GMM_CAUSE_ILLEGAL_ME, peer->registration_reject_cause);
|
||||
if (!release_ue(tc, ngap, peer) ||
|
||||
!check_bearers(tc, gtpu, old_flows, sessions, false))
|
||||
goto cleanup;
|
||||
goto cleanup;
|
||||
}
|
||||
if (!finish_registration(tc, ngap, peer) ||
|
||||
!check_bearers(tc, gtpu, old_flows, sessions, false) ||
|
||||
!establish_session(tc, ngap, peer, "internet", 5))
|
||||
goto cleanup;
|
||||
new_flow = test_qos_flow_find_by_qfi(test_sess_find_by_psi(peer, 5), 1);
|
||||
ogs_assert(new_flow);
|
||||
for (i = 0; i < sessions; i++)
|
||||
ABTS_TRUE(tc, new_flow->sess->upf_n3_teid != old_flows[i]->sess->upf_n3_teid);
|
||||
if (!check_bearer(tc, gtpu, new_flow, true))
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (scenario == BAD_RES || scenario == AUTH_FAILURE ||
|
||||
scenario == DEREG_DURING_AUTH) {
|
||||
if (!receive_ngap(tc, ngap, peer, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_AUTHENTICATION_REJECT, 0))
|
||||
goto cleanup;
|
||||
}
|
||||
if (scenario != REREGISTER &&
|
||||
!check_bearers(tc, gtpu, old_flows, sessions, true))
|
||||
goto cleanup;
|
||||
/* A protected Deregistration with the original keys verifies rollback.
|
||||
* SMC Reject has no reply, so this follows it on the same SCTP stream.
|
||||
* This stage retains the peer's NG association after failure; use current
|
||||
* IDs here and revise cleanup when NG association restoration is added. */
|
||||
cleanup_ue = scenario == REREGISTER ? peer : victim;
|
||||
cleanup_ue->ran_ue_ngap_id = peer->ran_ue_ngap_id;
|
||||
cleanup_ue->amf_ue_ngap_id = peer->amf_ue_ngap_id;
|
||||
if (!send_nas(tc, ngap, cleanup_ue,
|
||||
testgmm_build_de_registration_request(cleanup_ue, false, true, false)) ||
|
||||
!receive_ngap(tc, ngap, cleanup_ue, NGAP_ProcedureCode_id_DownlinkNASTransport,
|
||||
OGS_NAS_5GS_DEREGISTRATION_ACCEPT_FROM_UE, 0))
|
||||
goto cleanup;
|
||||
ABTS_INT_EQUAL(tc, 0, cleanup_ue->mac_failed);
|
||||
if (!release_ue(tc, ngap, cleanup_ue))
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (container)
|
||||
ogs_pkbuf_free(container);
|
||||
if (ngap)
|
||||
testgnb_ngap_close(ngap);
|
||||
if (gtpu)
|
||||
test_gtpu_close(gtpu);
|
||||
if (equipment) {
|
||||
bson_error_t error;
|
||||
bson_t *query = BCON_NEW("_id", BCON_OID(&equipment_id));
|
||||
if (!mongoc_collection_delete_one(ogs_mongoc()->collection.eir,
|
||||
query, NULL, NULL, &error))
|
||||
ABTS_FAIL(tc, error.message);
|
||||
bson_destroy(query);
|
||||
}
|
||||
if (subscriber)
|
||||
ABTS_INT_EQUAL(tc, OGS_OK, test_db_remove_ue(victim));
|
||||
if (peer)
|
||||
test_ue_remove(peer);
|
||||
if (victim)
|
||||
test_ue_remove(victim);
|
||||
}
|
||||
|
||||
abts_suite *test_guti_amf(abts_suite *suite)
|
||||
{
|
||||
unsigned int i;
|
||||
static unsigned int indexes[OGS_ARRAY_SIZE(cases)];
|
||||
|
||||
suite = ADD_SUITE(suite);
|
||||
for (i = 0; i < OGS_ARRAY_SIZE(cases); i++) {
|
||||
if (cases[i].scenario == EIR_REJECT &&
|
||||
!ogs_global_conf()->parameter.eir_count)
|
||||
continue;
|
||||
indexes[i] = i;
|
||||
abts_run_test(suite, test_amf_case, &indexes[i]);
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ static char *eir_uri;
|
|||
static char *nrf_uri;
|
||||
static bool allow_policy;
|
||||
static bool epc_guti;
|
||||
static bool amf_guti;
|
||||
|
||||
bool test_eir_allow_policy(void)
|
||||
{
|
||||
|
|
@ -129,7 +130,7 @@ static void initialize(const char *const argv[])
|
|||
ogs_assert(rv == OGS_OK);
|
||||
test_app_init();
|
||||
|
||||
if (!epc_guti) {
|
||||
if (!epc_guti && !amf_guti) {
|
||||
eir_uri = config_sbi_uri("eir");
|
||||
nrf_uri = config_sbi_uri("nrf");
|
||||
ogs_assert(eir_uri && nrf_uri);
|
||||
|
|
@ -152,6 +153,8 @@ int main(int argc, const char *const argv[])
|
|||
if (mode) {
|
||||
if (!strcmp(mode, "epc-guti"))
|
||||
epc_guti = true;
|
||||
else if (!strcmp(mode, "amf-guti"))
|
||||
amf_guti = true;
|
||||
else if (strcmp(mode, "integration")) {
|
||||
fprintf(stderr, "Invalid EIR test mode: %s\n", mode);
|
||||
return EXIT_FAILURE;
|
||||
|
|
@ -175,13 +178,15 @@ int main(int argc, const char *const argv[])
|
|||
}
|
||||
|
||||
atexit(terminate);
|
||||
config = epc_guti ? "attach.yaml" :
|
||||
config = epc_guti ? "attach.yaml" : amf_guti ? "sample.yaml" :
|
||||
allow_policy ? "eir-allow.yaml" : "eir.yaml";
|
||||
test_app_run(argc, argv, config, initialize);
|
||||
|
||||
/* EPC regressions also run without EIR, so do not require SBI discovery. */
|
||||
if (epc_guti)
|
||||
return abts_report(test_guti_epc(suite));
|
||||
if (amf_guti)
|
||||
return abts_report(test_guti_amf(suite));
|
||||
|
||||
/* Discovery confirms that EIR registered its SBI service with the NRF. */
|
||||
if (!test_eir_wait_ready()) {
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@ abts_suite *test_eir_service(abts_suite *suite);
|
|||
abts_suite *test_eir_registration(abts_suite *suite);
|
||||
abts_suite *test_eir_attach(abts_suite *suite);
|
||||
abts_suite *test_guti_epc(abts_suite *suite);
|
||||
abts_suite *test_guti_amf(abts_suite *suite);
|
||||
|
||||
#endif /* TEST_EIR_INTEGRATION_H */
|
||||
|
|
|
|||
|
|
@ -9,31 +9,53 @@ testapp_eir_exe = executable('eir',
|
|||
registration-test.c
|
||||
attach-test.c
|
||||
epc-guti-test.c
|
||||
amf-guti-test.c
|
||||
'''.split()),
|
||||
c_args : testunit_core_cc_flags,
|
||||
dependencies : [libtestapp_dep, libcurl_dep])
|
||||
|
||||
test('eir', testapp_eir_exe,
|
||||
test('eir-4g', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=integration', 'OPEN5GS_EIR_TEST_POLICY=reject'],
|
||||
args : ['attach-test'],
|
||||
is_parallel : false,
|
||||
timeout : 180,
|
||||
suite : ['eir', 'app'])
|
||||
suite : ['eir', 'epc'])
|
||||
|
||||
test('eir-allow', testapp_eir_exe,
|
||||
test('eir-4g-allow', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=integration', 'OPEN5GS_EIR_TEST_POLICY=allow'],
|
||||
args : ['attach-test'],
|
||||
is_parallel : false,
|
||||
timeout : 180,
|
||||
suite : ['eir', 'app'])
|
||||
suite : ['eir', 'epc'])
|
||||
|
||||
test('eir-epc-guti', testapp_eir_exe,
|
||||
test('eir-5g', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=integration', 'OPEN5GS_EIR_TEST_POLICY=reject'],
|
||||
args : ['dbi-test', 'service-test', 'registration-test'],
|
||||
is_parallel : false,
|
||||
suite : ['eir', '5gc'])
|
||||
|
||||
test('eir-5g-allow', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=integration', 'OPEN5GS_EIR_TEST_POLICY=allow'],
|
||||
args : ['registration-test'],
|
||||
is_parallel : false,
|
||||
suite : ['eir', '5gc'])
|
||||
|
||||
test('eir-epc', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=epc-guti'],
|
||||
is_parallel : false,
|
||||
timeout : 180,
|
||||
suite : ['epc', 'eir'])
|
||||
suite : ['eir', 'epc'])
|
||||
|
||||
test('eir-epc-guti-s13', testapp_eir_exe,
|
||||
test('eir-s13', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=epc-guti'],
|
||||
args : ['-c', join_paths(build_configs_dir, 'eir.yaml')],
|
||||
is_parallel : false,
|
||||
timeout : 180,
|
||||
suite : ['epc', 'eir'])
|
||||
suite : ['eir', 'epc'])
|
||||
|
||||
test('eir-amf', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=amf-guti'],
|
||||
is_parallel : false,
|
||||
suite : ['eir', '5gc'])
|
||||
|
||||
test('eir-n5g', testapp_eir_exe,
|
||||
env : ['OPEN5GS_EIR_TEST_MODE=amf-guti'],
|
||||
args : ['-c', join_paths(build_configs_dir, 'eir.yaml')],
|
||||
is_parallel : false,
|
||||
suite : ['eir', '5gc'])
|
||||
|
|
|
|||
|
|
@ -28,4 +28,12 @@ testapp_handover_exe = executable('handover',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtestapp_dep)
|
||||
|
||||
test('handover', testapp_handover_exe, is_parallel : false, suite: 'app')
|
||||
test('handover-epc', testapp_handover_exe,
|
||||
args : ['epc-x2-test', 'epc-s1-test'],
|
||||
is_parallel : false,
|
||||
suite : ['handover', 'epc'])
|
||||
|
||||
test('handover-5gc', testapp_handover_exe,
|
||||
args : ['5gc-xn-test', '5gc-n2-test'],
|
||||
is_parallel : false,
|
||||
suite : ['handover', '5gc'])
|
||||
|
|
|
|||
|
|
@ -35,4 +35,4 @@ testapp_non3gpp_exe = executable('non3gpp',
|
|||
'-DFD_EXT_DIR="@0@"'.format(build_subprojects_freeDiameter_extensions_dir)],
|
||||
dependencies : libtestepc_dep)
|
||||
|
||||
test('non3gpp', testapp_non3gpp_exe, is_parallel : false, suite: 'epc')
|
||||
test('non3gpp', testapp_non3gpp_exe, is_parallel : false, suite: ['non3gpp', 'epc'])
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ test5gc_registration_exe = executable('registration',
|
|||
test('registration',
|
||||
test5gc_registration_exe,
|
||||
is_parallel : false,
|
||||
suite: '5gc')
|
||||
suite: ['registration', '5gc'])
|
||||
|
|
|
|||
|
|
@ -25,4 +25,4 @@ testsystem_sctp_exe = executable('sctp',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libsctp_dep)
|
||||
|
||||
test('sctp', testsystem_sctp_exe, is_parallel : false, suite: 'app')
|
||||
test('sctp', testsystem_sctp_exe, is_parallel : false, suite: 'unit')
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@ test5gc_slice_exe = executable('slice',
|
|||
test('slice',
|
||||
test5gc_slice_exe,
|
||||
is_parallel : false,
|
||||
suite: '5gc')
|
||||
suite: ['slice', '5gc'])
|
||||
|
|
|
|||
|
|
@ -35,5 +35,5 @@ testapp_transfer_error_exe = executable('transfer-error',
|
|||
c_args : testunit_core_cc_flags,
|
||||
dependencies : libtest5gc_dep)
|
||||
|
||||
test('transfer', testapp_transfer_exe, is_parallel : false, suite: '5gc')
|
||||
test('transfer-error', testapp_transfer_error_exe, is_parallel : false, suite: '5gc')
|
||||
test('transfer', testapp_transfer_exe, is_parallel : false, suite: ['transfer', '5gc'])
|
||||
test('transfer-error', testapp_transfer_error_exe, is_parallel : false, suite: ['transfer', '5gc'])
|
||||
|
|
@ -43,6 +43,7 @@ abts_suite *test_mme_s13(abts_suite *suite);
|
|||
abts_suite *test_mme_attach(abts_suite *suite);
|
||||
abts_suite *test_mme_enb_id(abts_suite *suite);
|
||||
abts_suite *test_amf_eir(abts_suite *suite);
|
||||
abts_suite *test_amf_registration(abts_suite *suite);
|
||||
abts_suite *test_amf_gnb_id(abts_suite *suite);
|
||||
abts_suite *test_eir_config(abts_suite *suite);
|
||||
abts_suite *test_ipfw(abts_suite *suite);
|
||||
|
|
@ -65,6 +66,7 @@ const struct testlist {
|
|||
{test_mme_attach},
|
||||
{test_mme_enb_id},
|
||||
{test_amf_eir},
|
||||
{test_amf_registration},
|
||||
{test_amf_gnb_id},
|
||||
{test_eir_config},
|
||||
{test_ipfw},
|
||||
|
|
|
|||
759
tests/unit/amf-registration-test.c
Normal file
759
tests/unit/amf-registration-test.c
Normal file
|
|
@ -0,0 +1,759 @@
|
|||
/*
|
||||
* Copyright (C) 2026 by Sukchan Lee <acetcom@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/* Exercise the production GMM FSM, NAS handlers and SM-context completion
|
||||
* without NF startup. Pool lookup, fixed serving-area/RAT/slice policy and
|
||||
* outbound messages are substituted. NG release completion is not simulated.
|
||||
* SBI responses model removal of their completed transaction,
|
||||
* as amf-sm.c does before calling the Nsmf completion handler. */
|
||||
#include "amf/event.h"
|
||||
#include "amf/timer.h"
|
||||
#include "amf/gmm-handler.h"
|
||||
#include "amf/nas-path.h"
|
||||
#include "amf/ngap-path.h"
|
||||
#include "amf/nausf-handler.h"
|
||||
#include "amf/nsmf-handler.h"
|
||||
#include "amf/nudm-handler.h"
|
||||
#include "amf/npcf-handler.h"
|
||||
#include "amf/n5geir-handler.h"
|
||||
#include "amf/nnrf-handler.h"
|
||||
#include "amf/sbi-path.h"
|
||||
#include "amf/amf-sm.h"
|
||||
#include "amf/metrics.h"
|
||||
#include "core/abts.h"
|
||||
|
||||
static struct {
|
||||
amf_ue_t ue;
|
||||
ran_ue_t ran;
|
||||
amf_sess_t sess[2];
|
||||
ogs_sbi_xact_t xact[2];
|
||||
ogs_sbi_xact_t unrelated_xact;
|
||||
amf_sbi_xact_ctx_t xact_context[2];
|
||||
ogs_timer_mgr_t *timers;
|
||||
amf_m_tmsi_t m_tmsi;
|
||||
int auth, releases, continuation, eir, rejects, smc, ran_releases;
|
||||
int num_of_integrity_order;
|
||||
uint8_t integrity_order;
|
||||
bool eir_enabled;
|
||||
bool ran_removed;
|
||||
} *f;
|
||||
|
||||
static amf_ue_t *registration_ue(ogs_pool_id_t id)
|
||||
{ return id == f->ue.id ? &f->ue : NULL; }
|
||||
static ran_ue_t *registration_ran(ogs_pool_id_t id)
|
||||
{ return !f->ran_removed && id == f->ran.id ? &f->ran : NULL; }
|
||||
static amf_sess_t *registration_sess(ogs_pool_id_t id)
|
||||
{
|
||||
amf_sess_t *sess;
|
||||
ogs_list_for_each(&f->ue.sess_list, sess)
|
||||
if (sess->id == id) return sess;
|
||||
return NULL;
|
||||
}
|
||||
static void registration_remove(amf_sess_t *sess)
|
||||
{ ogs_list_remove(&f->ue.sess_list, sess); }
|
||||
static void registration_guti(amf_ue_t *ue)
|
||||
{ ue->next.m_tmsi = &f->m_tmsi; }
|
||||
static int registration_tai(ogs_5gs_tai_t *tai)
|
||||
{ return 0; }
|
||||
static bool registration_rat(amf_ue_t *ue)
|
||||
{ return false; }
|
||||
static bool registration_nssai(amf_ue_t *ue)
|
||||
{ return true; }
|
||||
static int registration_send(OpenAPI_service_name_e service,
|
||||
ogs_sbi_discovery_option_t *option,
|
||||
ogs_sbi_request_t *(*build)(amf_ue_t *, void *),
|
||||
amf_ue_t *ue, int state, void *data)
|
||||
{
|
||||
if (service == OpenAPI_service_name_nausf_auth) {
|
||||
f->auth++;
|
||||
/* Model the local effects of a subsequent AUSF vector response. */
|
||||
CLEAR_SECURITY_CONTEXT(ue);
|
||||
ue->nas.ue.ksi = ue->nas.amf.ksi = 3;
|
||||
ue->nas.ue.tsc = ue->nas.amf.tsc = 0;
|
||||
} else if (service == OpenAPI_service_name_nudm_uecm)
|
||||
f->continuation++;
|
||||
return OGS_OK;
|
||||
}
|
||||
static int registration_eir(amf_ue_t *ue)
|
||||
{ f->eir++; return OGS_OK; }
|
||||
static void registration_release(ran_ue_t *ran, amf_ue_t *ue,
|
||||
int state, void *data)
|
||||
{
|
||||
int i;
|
||||
amf_sess_t *sess;
|
||||
f->releases++;
|
||||
ogs_list_for_each(&ue->sess_list, sess) {
|
||||
i = sess->id - 3;
|
||||
f->xact[i].state = state;
|
||||
f->xact[i].service_name = OpenAPI_service_name_nsmf_pdusession;
|
||||
f->xact[i].requester_nf_type = OpenAPI_nf_type_AMF;
|
||||
f->xact[i].sbi_object = &sess->sbi;
|
||||
f->xact[i].sbi_object_id = sess->id;
|
||||
f->xact_context[i].ran_ue_id = ran ? ran->id : OGS_INVALID_POOL_ID;
|
||||
f->xact[i].user_data = &f->xact_context[i];
|
||||
ogs_list_add(&sess->sbi.xact_list, &f->xact[i]);
|
||||
}
|
||||
}
|
||||
static int registration_reject(amf_ue_t *ue)
|
||||
{ f->rejects++; return OGS_OK; }
|
||||
static int registration_gmm_reject(ran_ue_t *ran, amf_ue_t *ue,
|
||||
ogs_nas_5gmm_cause_t cause)
|
||||
{ f->rejects++; return OGS_OK; }
|
||||
static int registration_smc(amf_ue_t *ue)
|
||||
{
|
||||
f->smc++;
|
||||
ue->security_context_available = 1;
|
||||
return OGS_OK;
|
||||
}
|
||||
static void registration_xact_remove(ogs_sbi_xact_t *xact)
|
||||
{ ogs_list_remove(&xact->sbi_object->xact_list, xact); }
|
||||
static int registration_ran_release(ran_ue_t *ran,
|
||||
NGAP_Cause_PR group, long cause, uint8_t action, ogs_time_t duration)
|
||||
{
|
||||
f->ran_releases++;
|
||||
ran->ue_ctx_rel_action = action;
|
||||
return OGS_OK;
|
||||
}
|
||||
static int registration_sbi_reject(amf_ue_t *ue, int status)
|
||||
{ f->rejects++; return OGS_OK; }
|
||||
static void registration_metric(amf_metric_type_global_t metric) { }
|
||||
static void registration_cause(uint8_t cause,
|
||||
amf_metric_type_by_cause_t metric, int value) { }
|
||||
|
||||
#define amf_ue_find_by_id registration_ue
|
||||
#define ran_ue_find_by_id registration_ran
|
||||
#define amf_sess_find_by_id registration_sess
|
||||
#define amf_sess_remove registration_remove
|
||||
#define amf_ue_new_guti registration_guti
|
||||
#define amf_find_served_tai registration_tai
|
||||
#define amf_ue_is_rat_restricted registration_rat
|
||||
#define amf_update_allowed_nssai registration_nssai
|
||||
#define amf_ue_sbi_discover_and_send registration_send
|
||||
#define amf_ue_sbi_discover_and_send_eir registration_eir
|
||||
#define amf_sbi_send_release_all_sessions registration_release
|
||||
#define nas_5gs_send_authentication_reject registration_reject
|
||||
#define nas_5gs_send_registration_reject registration_gmm_reject
|
||||
#define nas_5gs_send_gmm_reject registration_gmm_reject
|
||||
#define nas_5gs_send_security_mode_command registration_smc
|
||||
#define amf_metrics_inst_global_inc registration_metric
|
||||
#define amf_metrics_inst_by_cause_add registration_cause
|
||||
#define amf_sbi_cancel_registration_session_release registration_cancel
|
||||
#define amf_sbi_fail_registration_session_release registration_failure
|
||||
#define ngap_send_ran_ue_context_release_command registration_ran_release
|
||||
#define nas_5gs_send_gmm_reject_from_sbi registration_sbi_reject
|
||||
|
||||
#define gmm_handle_registration_request registration_request
|
||||
#define gmm_handle_registration_update registration_update
|
||||
#define gmm_registration_request_from_old_amf registration_old_amf
|
||||
#define gmm_handle_service_request registration_service_request
|
||||
#define gmm_handle_service_update registration_service_update
|
||||
#define gmm_handle_deregistration_request registration_deregistration
|
||||
#define gmm_handle_authentication_response registration_auth_response
|
||||
#define gmm_handle_identity_response registration_identity_response
|
||||
#define gmm_handle_security_mode_complete registration_security_complete
|
||||
#define gmm_handle_ul_nas_transport registration_ul_transport
|
||||
#undef GMM_HANDLER_H
|
||||
#include "amf/gmm-handler.h"
|
||||
#include "../../src/amf/gmm-handler.c"
|
||||
|
||||
#define gmm_state_initial registration_state_initial
|
||||
#define gmm_state_final registration_state_final
|
||||
#define gmm_state_de_registered registration_state_de_registered
|
||||
#define gmm_state_authentication registration_state_authentication
|
||||
#define gmm_state_security_mode registration_state_security_mode
|
||||
#define gmm_state_initial_context_setup registration_state_context_setup
|
||||
#define gmm_state_registered registration_state_registered
|
||||
#define gmm_state_ue_context_will_remove registration_state_will_remove
|
||||
#define gmm_state_exception registration_state_exception
|
||||
void gmm_state_initial(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_final(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_de_registered(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_authentication(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_security_mode(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_initial_context_setup(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_registered(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_ue_context_will_remove(ogs_fsm_t *s, amf_event_t *e);
|
||||
void gmm_state_exception(ogs_fsm_t *s, amf_event_t *e);
|
||||
|
||||
#define amf_nsmf_pdusession_handle_create_sm_context registration_create_context
|
||||
#define amf_nsmf_pdusession_handle_update_sm_context registration_update_context
|
||||
#define amf_nsmf_pdusession_handle_release_sm_context registration_release_context
|
||||
#undef AMF_NSMF_HANDLER_H
|
||||
#include "amf/nsmf-handler.h"
|
||||
|
||||
/* Keep cancellation and the timeout/discovery-failure decisions real. The
|
||||
* fixture owns its transaction storage; pool removal is the only substitute. */
|
||||
#define ogs_sbi_xact_remove registration_xact_remove
|
||||
#define amf_sbi_open registration_sbi_open
|
||||
#define amf_sbi_close registration_sbi_close
|
||||
#define amf_sbi_send_request registration_sbi_send_request
|
||||
#undef amf_ue_sbi_discover_and_send
|
||||
#define amf_ue_sbi_discover_and_send registration_real_ue_send
|
||||
#undef amf_ue_sbi_discover_and_send_eir
|
||||
#define amf_ue_sbi_discover_and_send_eir registration_real_eir_send
|
||||
#define amf_sess_sbi_discover_and_send registration_real_sess_send
|
||||
#define amf_sess_sbi_discover_by_nsi registration_discover_nsi
|
||||
#define amf_sbi_send_activating_session registration_activate
|
||||
#define amf_sbi_send_deactivate_session registration_deactivate
|
||||
#define amf_sbi_send_deactivate_all_sessions registration_deactivate_all
|
||||
#define amf_sbi_send_deactivate_all_ue_in_gnb registration_deactivate_gnb
|
||||
#define amf_sbi_send_deactivate_stale_user_plane registration_deactivate_stale
|
||||
#define amf_sbi_send_release_session registration_real_release_session
|
||||
#undef amf_sbi_send_release_all_sessions
|
||||
#define amf_sbi_send_release_all_sessions registration_real_release_all
|
||||
#define amf_sbi_send_n1_n2_failure_notify registration_failure_notify
|
||||
#define amf_ue_have_session_release_pending registration_ue_release_pending
|
||||
#define amf_sess_have_session_release_pending registration_sess_release_pending
|
||||
#define amf_sbi_xact_ctx_s registration_xact_ctx_s
|
||||
#define amf_sbi_xact_ctx_t registration_xact_ctx_t
|
||||
#undef AMF_SBI_PATH_H
|
||||
#include "amf/sbi-path.h"
|
||||
#include "../../src/amf/sbi-path.c"
|
||||
#undef amf_ue_sbi_discover_and_send
|
||||
#define amf_ue_sbi_discover_and_send registration_send
|
||||
#undef amf_sbi_send_release_all_sessions
|
||||
#define amf_sbi_send_release_all_sessions registration_release
|
||||
#undef amf_ue_sbi_discover_and_send_eir
|
||||
#define amf_ue_sbi_discover_and_send_eir registration_eir
|
||||
#define amf_nnrf_handle_nf_discover registration_nf_discover
|
||||
#define amf_nnrf_handle_failed_amf_discovery registration_discovery_failure
|
||||
#undef AMF_NNRF_HANDLER_H
|
||||
#include "amf/nnrf-handler.h"
|
||||
#include "../../src/amf/nnrf-handler.c"
|
||||
#include "../../src/amf/nsmf-handler.c"
|
||||
#include "../../src/amf/gmm-sm.c"
|
||||
|
||||
static void timer_unused(void *data) { }
|
||||
|
||||
static void fixture_init(int sessions)
|
||||
{
|
||||
int i;
|
||||
f = ogs_calloc(1, sizeof(*f));
|
||||
ogs_assert(f);
|
||||
f->num_of_integrity_order = amf_self()->num_of_integrity_order;
|
||||
f->integrity_order = amf_self()->integrity_order[0];
|
||||
f->eir_enabled = amf_self()->eir.enabled;
|
||||
amf_self()->num_of_integrity_order = 1;
|
||||
amf_self()->integrity_order[0] = OGS_NAS_SECURITY_ALGORITHMS_128_NIA2;
|
||||
amf_self()->eir.enabled = false;
|
||||
f->ue.id = 1;
|
||||
f->ue.ran_ue_id = f->ran.id = 2;
|
||||
f->ran.amf_ue_id = f->ue.id;
|
||||
f->ran.ue_ctx_rel_action = NGAP_UE_CTX_REL_INVALID_ACTION;
|
||||
f->ue.suci = "suci-0-001-01-0-0-0-0000000001";
|
||||
f->ue.supi = "imsi-001010000000001";
|
||||
f->ue.pei = "imeisv-4901542032375186";
|
||||
f->ue.nas.access_type = OGS_ACCESS_TYPE_3GPP;
|
||||
f->ue.nas.message_type = OGS_NAS_5GS_REGISTRATION_REQUEST;
|
||||
f->ue.nas.ue.ksi = f->ue.nas.amf.ksi = 2;
|
||||
f->ue.nas.ue.tsc = f->ue.nas.amf.tsc = 1;
|
||||
f->ue.security_context_available = 1;
|
||||
f->ue.ue_security_capability.length = 2;
|
||||
f->ue.ue_security_capability.nr_ia = 0x20;
|
||||
f->ue.selected_int_algorithm = OGS_NAS_SECURITY_ALGORITHMS_128_NIA2;
|
||||
f->ue.ul_count.i32 = 41;
|
||||
f->ue.ul_count_accepted = true;
|
||||
f->ue.dl_count = 37;
|
||||
memset(f->ue.kamf, 0x11, sizeof(f->ue.kamf));
|
||||
memset(f->ue.knas_int, 0x22, sizeof(f->ue.knas_int));
|
||||
f->timers = ogs_timer_mgr_create(8);
|
||||
#define TIMER(name) f->ue.name.timer = ogs_timer_add(f->timers, timer_unused, NULL)
|
||||
TIMER(t3513); TIMER(t3522); TIMER(t3550); TIMER(t3555);
|
||||
TIMER(t3560); TIMER(t3570); TIMER(mobile_reachable);
|
||||
TIMER(implicit_deregistration);
|
||||
#undef TIMER
|
||||
for (i = 0; i < sessions; i++) {
|
||||
f->sess[i].id = 3 + i;
|
||||
f->sess[i].psi = 5 + i;
|
||||
f->sess[i].amf_ue_id = f->ue.id;
|
||||
f->sess[i].ran_ue_id = f->ran.id;
|
||||
f->sess[i].sbi.type = OGS_SBI_OBJ_SESS_TYPE;
|
||||
f->sess[i].sm_context_ref = "registered-sm-context";
|
||||
ogs_list_add(&f->ue.sess_list, &f->sess[i]);
|
||||
}
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_registered);
|
||||
}
|
||||
|
||||
static void fixture_final(void)
|
||||
{
|
||||
#define TIMER(name) ogs_timer_delete(f->ue.name.timer)
|
||||
TIMER(t3513); TIMER(t3522); TIMER(t3550); TIMER(t3555);
|
||||
TIMER(t3560); TIMER(t3570); TIMER(mobile_reachable);
|
||||
TIMER(implicit_deregistration);
|
||||
#undef TIMER
|
||||
ogs_timer_mgr_destroy(f->timers);
|
||||
amf_self()->num_of_integrity_order = f->num_of_integrity_order;
|
||||
amf_self()->integrity_order[0] = f->integrity_order;
|
||||
amf_self()->eir.enabled = f->eir_enabled;
|
||||
ogs_free(f);
|
||||
f = NULL;
|
||||
}
|
||||
|
||||
static void dispatch(uint8_t type, bool protected, bool invalid)
|
||||
{
|
||||
amf_event_t event = {0};
|
||||
ogs_nas_5gs_message_t message = {0}, inner = {0};
|
||||
ogs_nas_5gs_mobile_identity_guti_t guti = {0};
|
||||
ogs_nas_security_header_type_t h = {0};
|
||||
ogs_pkbuf_t *container = NULL;
|
||||
event.h.id = AMF_EVENT_5GMM_MESSAGE;
|
||||
event.amf_ue_id = f->ue.id;
|
||||
event.ran_ue_id = f->ran.id;
|
||||
event.ngap.code = NGAP_ProcedureCode_id_InitialUEMessage;
|
||||
event.nas.message = &message;
|
||||
h.integrity_protected = protected;
|
||||
event.nas.type = h.type;
|
||||
message.gmm.h.message_type = type;
|
||||
if (type == OGS_NAS_5GS_REGISTRATION_REQUEST) {
|
||||
message.gmm.registration_request.registration_type.value =
|
||||
OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL;
|
||||
message.gmm.registration_request.registration_type.ksi = protected ? 2 : 7;
|
||||
guti.h.type = OGS_NAS_5GS_MOBILE_IDENTITY_GUTI;
|
||||
message.gmm.registration_request.mobile_identity.length = sizeof(guti);
|
||||
message.gmm.registration_request.mobile_identity.buffer = &guti;
|
||||
} else if (type == OGS_NAS_5GS_AUTHENTICATION_RESPONSE) {
|
||||
message.gmm.authentication_response.authentication_response_parameter
|
||||
.length = OGS_MAX_RES_LEN;
|
||||
memset(message.gmm.authentication_response.authentication_response_parameter
|
||||
.res, 0xff, OGS_MAX_RES_LEN);
|
||||
} else if (type == OGS_NAS_5GS_AUTHENTICATION_FAILURE)
|
||||
message.gmm.authentication_failure.gmm_cause = OGS_5GMM_CAUSE_MAC_FAILURE;
|
||||
else if (type == OGS_NAS_5GS_SECURITY_MODE_COMPLETE && !invalid) {
|
||||
inner.gmm.h.extended_protocol_discriminator = OGS_NAS_EXTENDED_PROTOCOL_DISCRIMINATOR_5GMM;
|
||||
inner.gmm.h.message_type = OGS_NAS_5GS_REGISTRATION_REQUEST;
|
||||
inner.gmm.registration_request.registration_type.value =
|
||||
OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL;
|
||||
guti.h.type = OGS_NAS_5GS_MOBILE_IDENTITY_GUTI;
|
||||
inner.gmm.registration_request.mobile_identity.length = sizeof(guti);
|
||||
inner.gmm.registration_request.mobile_identity.buffer = &guti;
|
||||
container = ogs_nas_5gs_plain_encode(&inner);
|
||||
ogs_assert(container);
|
||||
message.gmm.security_mode_complete.presencemask =
|
||||
OGS_NAS_5GS_SECURITY_MODE_COMPLETE_NAS_MESSAGE_CONTAINER_PRESENT;
|
||||
message.gmm.security_mode_complete.nas_message_container.length = container->len;
|
||||
message.gmm.security_mode_complete.nas_message_container.buffer = container->data;
|
||||
}
|
||||
ogs_fsm_dispatch(&f->ue.sm, &event);
|
||||
if (container) ogs_pkbuf_free(container);
|
||||
}
|
||||
|
||||
static void assert_preserved(abts_case *tc, int sessions)
|
||||
{
|
||||
ABTS_INT_EQUAL(tc, sessions, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, 0, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
}
|
||||
|
||||
static void assert_restored(abts_case *tc, int sessions)
|
||||
{
|
||||
assert_preserved(tc, sessions);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_registered));
|
||||
ABTS_INT_EQUAL(tc, 2, f->ue.nas.ue.ksi);
|
||||
ABTS_INT_EQUAL(tc, 2, f->ue.nas.amf.ksi);
|
||||
ABTS_INT_EQUAL(tc, 1, f->ue.nas.ue.tsc);
|
||||
ABTS_INT_EQUAL(tc, 1, f->ue.nas.amf.tsc);
|
||||
ABTS_INT_EQUAL(tc, 41, f->ue.ul_count.i32);
|
||||
ABTS_TRUE(tc, f->ue.ul_count_accepted);
|
||||
ABTS_INT_EQUAL(tc, 37, f->ue.dl_count);
|
||||
ABTS_INT_EQUAL(tc, 0x11, f->ue.kamf[0]);
|
||||
ABTS_INT_EQUAL(tc, 0x22, f->ue.knas_int[0]);
|
||||
ABTS_TRUE(tc, !memcmp(f->ue.kamf, f->ue.memento.kamf, sizeof(f->ue.kamf)));
|
||||
ABTS_TRUE(tc, !memcmp(f->ue.knas_int, f->ue.memento.knas_int,
|
||||
sizeof(f->ue.knas_int)));
|
||||
ABTS_TRUE(tc, SECURITY_CONTEXT_IS_VALID(&f->ue));
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_after_authentication);
|
||||
}
|
||||
|
||||
static void failures_preserve_sessions(abts_case *tc, void *data)
|
||||
{
|
||||
int sessions = *(int *)data;
|
||||
int i;
|
||||
uint8_t failures[] = {
|
||||
OGS_NAS_5GS_AUTHENTICATION_RESPONSE,
|
||||
OGS_NAS_5GS_AUTHENTICATION_FAILURE,
|
||||
OGS_NAS_5GS_SECURITY_MODE_REJECT,
|
||||
OGS_NAS_5GS_SECURITY_MODE_COMPLETE,
|
||||
};
|
||||
for (i = 0; i < OGS_ARRAY_SIZE(failures); i++) {
|
||||
fixture_init(sessions);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_authentication));
|
||||
ABTS_INT_EQUAL(tc, 1, f->auth);
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_after_authentication);
|
||||
assert_preserved(tc, sessions);
|
||||
memset(f->ue.kamf, 0x55, sizeof(f->ue.kamf));
|
||||
memset(f->ue.knas_int, 0x55, sizeof(f->ue.knas_int));
|
||||
f->ue.ul_count.i32 = f->ue.dl_count = 0;
|
||||
f->ue.ul_count_accepted = false;
|
||||
if (i >= 2) {
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_security_mode);
|
||||
f->ue.security_context_available = 1;
|
||||
}
|
||||
dispatch(failures[i], i >= 2, true);
|
||||
assert_restored(tc, sessions);
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void unverified_nas_is_discarded(abts_case *tc, void *data)
|
||||
{
|
||||
int i, sessions = *(int *)data;
|
||||
for (i = 0; i < 2; i++) {
|
||||
fixture_init(sessions);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
if (i) {
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_security_mode);
|
||||
f->ue.security_context_available = 1;
|
||||
ogs_timer_start(f->ue.t3560.timer, ogs_time_from_sec(1));
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, false, false);
|
||||
ABTS_TRUE(tc, f->ue.t3560.timer->running);
|
||||
f->ue.mac_failed = 1;
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
ABTS_TRUE(tc, f->ue.t3560.timer->running);
|
||||
f->ue.mac_failed = 0;
|
||||
}
|
||||
dispatch(OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE, false, false);
|
||||
assert_preserved(tc, sessions);
|
||||
ABTS_INT_EQUAL(tc, OGS_NAS_5GS_REGISTRATION_REQUEST, f->ue.nas.message_type);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm,
|
||||
(i ? gmm_state_security_mode : gmm_state_authentication)));
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void release_response(int i)
|
||||
{
|
||||
ogs_sbi_xact_t *xact;
|
||||
amf_sess_t *sess = registration_sess(f->sess[i].id);
|
||||
if (sess) {
|
||||
ogs_list_for_each(&sess->sbi.xact_list, xact) {
|
||||
if (xact == &f->xact[i]) {
|
||||
ogs_list_remove(&sess->sbi.xact_list, xact);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
amf_nsmf_pdusession_handle_release_sm_context(&f->ue, &f->ran, sess,
|
||||
AMF_RELEASE_SM_CONTEXT_AUTHENTICATED_REGISTRATION);
|
||||
}
|
||||
|
||||
static void start_security_mode(void)
|
||||
{
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_security_mode);
|
||||
f->ue.security_context_available = 1;
|
||||
}
|
||||
|
||||
static void equipment_status(OpenAPI_equipment_status_e status)
|
||||
{
|
||||
amf_event_t event = {0};
|
||||
ogs_sbi_message_t message = {0};
|
||||
OpenAPI_eir_response_data_t response = {0};
|
||||
event.h.id = OGS_EVENT_SBI_CLIENT;
|
||||
event.h.sbi.message = &message;
|
||||
event.amf_ue_id = f->ue.id;
|
||||
message.h.service.name = "n5g-eir-eic";
|
||||
message.h.resource.component[0] = OGS_SBI_RESOURCE_NAME_EQUIPMENT_STATUS;
|
||||
message.res_status = OGS_SBI_HTTP_STATUS_OK;
|
||||
response.status = status;
|
||||
message.EirResponseData = &response;
|
||||
ogs_fsm_dispatch(&f->ue.sm, &event);
|
||||
}
|
||||
|
||||
static void eir_and_last_release(abts_case *tc, void *data)
|
||||
{
|
||||
int enabled, sessions = *(int *)data;
|
||||
for (enabled = 0; enabled < 2; enabled++) {
|
||||
fixture_init(sessions);
|
||||
start_security_mode();
|
||||
amf_self()->eir.enabled = enabled;
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
ABTS_TRUE(tc, !f->ue.can_restore_context);
|
||||
if (enabled) {
|
||||
assert_preserved(tc, sessions);
|
||||
ABTS_INT_EQUAL(tc, 1, f->eir);
|
||||
ABTS_TRUE(tc, f->ue.eir_check_pending);
|
||||
equipment_status(OpenAPI_equipment_status_WHITELISTED);
|
||||
}
|
||||
ABTS_INT_EQUAL(tc, 1, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_pending);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_initial_context_setup));
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_pending);
|
||||
ABTS_INT_EQUAL(tc, 1, f->auth);
|
||||
ABTS_INT_EQUAL(tc, 1, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, sessions, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, sessions, amf_sess_xact_count(&f->ue));
|
||||
if (sessions == 2) {
|
||||
release_response(1);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, 1, ogs_list_count(&f->ue.sess_list));
|
||||
}
|
||||
release_response(0);
|
||||
ABTS_INT_EQUAL(tc, 0, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, 1, f->continuation);
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_pending);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_initial_context_setup));
|
||||
release_response(0);
|
||||
ABTS_INT_EQUAL(tc, 1, f->continuation);
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void cancelled_release_does_not_resume(abts_case *tc, void *data)
|
||||
{
|
||||
int sessions = *(int *)data, reason, i;
|
||||
for (reason = 0; reason < 4; reason++) {
|
||||
fixture_init(sessions);
|
||||
start_security_mode();
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
if (reason == 0)
|
||||
f->ue.registration_session_release_pending = false;
|
||||
else if (reason == 1)
|
||||
f->ran.ue_ctx_rel_action = NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE;
|
||||
else if (reason == 2)
|
||||
f->ue.ran_ue_id = OGS_INVALID_POOL_ID;
|
||||
else
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_authentication);
|
||||
for (i = sessions - 1; i >= 0; i--)
|
||||
release_response(i);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, 0, ogs_list_count(&f->ue.sess_list));
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void other_procedures_keep_sessions(abts_case *tc, void *data)
|
||||
{
|
||||
int service, sessions = *(int *)data;
|
||||
for (service = 0; service < 2; service++) {
|
||||
fixture_init(sessions);
|
||||
/* Model completion of a transferred Registration or a Service
|
||||
* Request. Neither procedure selected same-AMF re-registration
|
||||
* deletion, so passing SMC must preserve the existing sessions. */
|
||||
f->ue.nas.message_type = service ? OGS_NAS_5GS_SERVICE_REQUEST :
|
||||
OGS_NAS_5GS_REGISTRATION_REQUEST;
|
||||
f->ue.can_restore_context = false;
|
||||
f->ue.registration_session_release_after_authentication = false;
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_security_mode);
|
||||
gmm_security_mode_completed(&f->ue.sm, &f->ue);
|
||||
ABTS_INT_EQUAL(tc, sessions, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, 0, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 1, f->continuation);
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_pending);
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void eir_rejection_releases_sessions(abts_case *tc, void *data)
|
||||
{
|
||||
int sessions = *(int *)data;
|
||||
fixture_init(sessions);
|
||||
start_security_mode();
|
||||
amf_self()->eir.enabled = true;
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
assert_preserved(tc, sessions);
|
||||
equipment_status(OpenAPI_equipment_status_BLACKLISTED);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_exception));
|
||||
ABTS_TRUE(tc, !f->ue.can_restore_context);
|
||||
ABTS_INT_EQUAL(tc, 1, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, 1, f->rejects);
|
||||
fixture_final();
|
||||
}
|
||||
|
||||
static void cancel_removes_only_registration_transactions(abts_case *tc, void *data)
|
||||
{
|
||||
int i, sessions = *(int *)data;
|
||||
fixture_init(sessions);
|
||||
start_security_mode();
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
f->unrelated_xact.state = AMF_UPDATE_SM_CONTEXT_MODIFIED;
|
||||
f->unrelated_xact.sbi_object = &f->sess[0].sbi;
|
||||
ogs_list_add(&f->sess[0].sbi.xact_list, &f->unrelated_xact);
|
||||
amf_sbi_cancel_registration_session_release(&f->ue);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_initial_context_setup));
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_pending);
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_after_authentication);
|
||||
ABTS_INT_EQUAL(tc, 1, amf_sess_xact_count(&f->ue));
|
||||
ABTS_PTR_EQUAL(tc, &f->unrelated_xact,
|
||||
ogs_list_first(&f->sess[0].sbi.xact_list));
|
||||
ogs_list_remove(&f->sess[0].sbi.xact_list, &f->unrelated_xact);
|
||||
for (i = sessions - 1; i >= 0; i--)
|
||||
release_response(i);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, 0, ogs_list_count(&f->ue.sess_list));
|
||||
fixture_final();
|
||||
}
|
||||
|
||||
static void release_timeout_cancels_registration(abts_case *tc, void *data)
|
||||
{
|
||||
int present, i, sessions = *(int *)data;
|
||||
for (present = 0; present < 3; present++) {
|
||||
fixture_init(sessions);
|
||||
start_security_mode();
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
if (!present)
|
||||
f->ran_removed = true;
|
||||
else if (present == 2)
|
||||
f->ue.ran_ue_id = OGS_INVALID_POOL_ID;
|
||||
/* This is the production timeout/discovery failure handler invoked
|
||||
* synchronously; no peer timeout or artificial timer ENTRY is used. */
|
||||
amf_nnrf_handle_failed_amf_discovery(&f->xact[0]);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_initial_context_setup));
|
||||
ABTS_INT_EQUAL(tc, sessions, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, present == 1, f->ran_releases);
|
||||
ABTS_INT_EQUAL(tc, present == 1, f->rejects);
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_pending);
|
||||
ABTS_INT_EQUAL(tc, 0, amf_sess_xact_count(&f->ue));
|
||||
if (present == 1) {
|
||||
ABTS_INT_EQUAL(tc, NGAP_UE_CTX_REL_UE_CONTEXT_REMOVE,
|
||||
f->ran.ue_ctx_rel_action);
|
||||
}
|
||||
for (i = sessions - 1; i >= 0; i--)
|
||||
release_response(i);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
static void restarted_registration_retains_deferred_release(abts_case *tc, void *data)
|
||||
{
|
||||
int sessions = *(int *)data;
|
||||
fixture_init(sessions);
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_initial_context_setup);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_authentication));
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_after_authentication);
|
||||
assert_preserved(tc, sessions);
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_security_mode);
|
||||
f->ue.security_context_available = 1;
|
||||
dispatch(OGS_NAS_5GS_SECURITY_MODE_COMPLETE, true, false);
|
||||
ABTS_INT_EQUAL(tc, 1, f->releases);
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_pending);
|
||||
fixture_final();
|
||||
}
|
||||
|
||||
static void pending_activation_allows_registration(abts_case *tc, void *data)
|
||||
{
|
||||
int initial_context_setup, sessions = *(int *)data;
|
||||
for (initial_context_setup = 0; initial_context_setup < 2;
|
||||
initial_context_setup++) {
|
||||
fixture_init(sessions);
|
||||
if (initial_context_setup)
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_initial_context_setup);
|
||||
/* The N3 path can be usable before the final ACTIVATED response
|
||||
* arrives. This unrelated transaction must not discard a new
|
||||
* Registration Request or release the established sessions. */
|
||||
f->unrelated_xact.state = AMF_UPDATE_SM_CONTEXT_ACTIVATED;
|
||||
f->unrelated_xact.sbi_object = &f->sess[0].sbi;
|
||||
ogs_list_add(&f->sess[0].sbi.xact_list, &f->unrelated_xact);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_authentication));
|
||||
ABTS_INT_EQUAL(tc, 1, f->auth);
|
||||
assert_preserved(tc, sessions);
|
||||
ABTS_TRUE(tc, f->ue.registration_session_release_after_authentication);
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_pending);
|
||||
ABTS_INT_EQUAL(tc, 1, amf_sess_xact_count(&f->ue));
|
||||
ABTS_PTR_EQUAL(tc, &f->unrelated_xact,
|
||||
ogs_list_first(&f->sess[0].sbi.xact_list));
|
||||
ABTS_INT_EQUAL(tc, AMF_UPDATE_SM_CONTEXT_ACTIVATED,
|
||||
f->unrelated_xact.state);
|
||||
fixture_final();
|
||||
}
|
||||
|
||||
fixture_init(sessions);
|
||||
f->unrelated_xact.state = AMF_UPDATE_SM_CONTEXT_ACTIVATED;
|
||||
f->unrelated_xact.sbi_object = &f->sess[0].sbi;
|
||||
ogs_list_add(&f->sess[0].sbi.xact_list, &f->unrelated_xact);
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, true, false);
|
||||
ABTS_INT_EQUAL(tc, 0, f->auth);
|
||||
ABTS_INT_EQUAL(tc, 0, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 1, f->continuation);
|
||||
ABTS_INT_EQUAL(tc, sessions, ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_TRUE(tc, !f->ue.registration_session_release_after_authentication);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_initial_context_setup));
|
||||
ABTS_INT_EQUAL(tc, 1, amf_sess_xact_count(&f->ue));
|
||||
ABTS_PTR_EQUAL(tc, &f->unrelated_xact,
|
||||
ogs_list_first(&f->sess[0].sbi.xact_list));
|
||||
fixture_final();
|
||||
}
|
||||
|
||||
static void late_release_does_not_restart_authentication(abts_case *tc, void *data)
|
||||
{
|
||||
int started, i, sessions = *(int *)data;
|
||||
for (started = 0; started < 2; started++) {
|
||||
fixture_init(sessions);
|
||||
for (i = 0; i < sessions; i++) {
|
||||
f->xact[i].state = AMF_RELEASE_SM_CONTEXT_NO_STATE;
|
||||
f->xact[i].sbi_object = &f->sess[i].sbi;
|
||||
ogs_list_add(&f->sess[i].sbi.xact_list, &f->xact[i]);
|
||||
}
|
||||
if (started) {
|
||||
dispatch(OGS_NAS_5GS_REGISTRATION_REQUEST, false, false);
|
||||
assert_preserved(tc, sessions);
|
||||
} else {
|
||||
/* Existing release-driven authentication starts only after
|
||||
* the last release response. No AKA has been requested yet. */
|
||||
OGS_FSM_TRAN(&f->ue.sm, gmm_state_authentication);
|
||||
}
|
||||
ABTS_INT_EQUAL(tc, started, f->auth);
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_authentication));
|
||||
ABTS_INT_EQUAL(tc, started,
|
||||
f->ue.registration_session_release_after_authentication);
|
||||
|
||||
for (i = 0; i < sessions; i++) {
|
||||
/* amf-sm.c removes the completed SBI transaction before
|
||||
* delivering its response to the production Nsmf handler. */
|
||||
ogs_list_remove(&f->sess[i].sbi.xact_list, &f->xact[i]);
|
||||
amf_nsmf_pdusession_handle_release_sm_context(
|
||||
&f->ue, &f->ran, &f->sess[i], AMF_RELEASE_SM_CONTEXT_NO_STATE);
|
||||
ABTS_INT_EQUAL(tc, started || i == sessions - 1, f->auth);
|
||||
ABTS_INT_EQUAL(tc, sessions - i - 1,
|
||||
ogs_list_count(&f->ue.sess_list));
|
||||
ABTS_INT_EQUAL(tc, sessions - i - 1, amf_sess_xact_count(&f->ue));
|
||||
ABTS_TRUE(tc, OGS_FSM_CHECK(&f->ue.sm, gmm_state_authentication));
|
||||
ABTS_INT_EQUAL(tc, 0, f->releases);
|
||||
ABTS_INT_EQUAL(tc, 0, f->continuation);
|
||||
}
|
||||
fixture_final();
|
||||
}
|
||||
}
|
||||
|
||||
abts_suite *test_amf_registration(abts_suite *suite)
|
||||
{
|
||||
int sessions[] = { 1, 2 };
|
||||
int i;
|
||||
ogs_log_level_e saved, saved_amf, saved_core;
|
||||
suite = ADD_SUITE(suite)
|
||||
if (!ogs_log_find_domain("gmm"))
|
||||
ogs_log_install_domain(&__gmm_log_domain, "gmm", OGS_LOG_NONE);
|
||||
saved = ogs_log_get_domain_level(__gmm_log_domain);
|
||||
saved_amf = ogs_log_get_domain_level(__amf_log_domain);
|
||||
saved_core = ogs_log_get_domain_level(ogs_log_get_domain_id("core"));
|
||||
ogs_log_set_domain_level(__gmm_log_domain, OGS_LOG_NONE);
|
||||
ogs_log_set_domain_level(__amf_log_domain, OGS_LOG_NONE);
|
||||
ogs_log_set_domain_level(ogs_log_get_domain_id("core"), OGS_LOG_NONE);
|
||||
for (i = 0; i < OGS_ARRAY_SIZE(sessions); i++) {
|
||||
abts_run_test(suite, failures_preserve_sessions, &sessions[i]);
|
||||
abts_run_test(suite, unverified_nas_is_discarded, &sessions[i]);
|
||||
abts_run_test(suite, eir_and_last_release, &sessions[i]);
|
||||
abts_run_test(suite, cancelled_release_does_not_resume, &sessions[i]);
|
||||
abts_run_test(suite, other_procedures_keep_sessions, &sessions[i]);
|
||||
abts_run_test(suite, eir_rejection_releases_sessions, &sessions[i]);
|
||||
abts_run_test(suite, cancel_removes_only_registration_transactions, &sessions[i]);
|
||||
abts_run_test(suite, release_timeout_cancels_registration, &sessions[i]);
|
||||
abts_run_test(suite, restarted_registration_retains_deferred_release, &sessions[i]);
|
||||
abts_run_test(suite, pending_activation_allows_registration, &sessions[i]);
|
||||
abts_run_test(suite, late_release_does_not_restart_authentication, &sessions[i]);
|
||||
}
|
||||
ogs_log_set_domain_level(__gmm_log_domain, saved);
|
||||
ogs_log_set_domain_level(__amf_log_domain, saved_amf);
|
||||
ogs_log_set_domain_level(ogs_log_get_domain_id("core"), saved_core);
|
||||
return suite;
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ testunit_unit_sources = files('''
|
|||
mme-attach-test.c
|
||||
mme-enb-id-test.c
|
||||
amf-eir-test.c
|
||||
amf-registration-test.c
|
||||
amf-gnb-id-test.c
|
||||
eir-config-test.c
|
||||
ipfw-test.c
|
||||
|
|
|
|||
|
|
@ -36,4 +36,4 @@ testapp_volte_exe = executable('volte',
|
|||
'-DFD_EXT_DIR="@0@"'.format(build_subprojects_freeDiameter_extensions_dir)],
|
||||
dependencies : libtestepc_dep)
|
||||
|
||||
test('volte', testapp_volte_exe, is_parallel : false, suite: 'epc')
|
||||
test('volte', testapp_volte_exe, is_parallel : false, suite: ['volte', 'epc'])
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@ test5gc_vonr_exe = executable('vonr',
|
|||
test('vonr',
|
||||
test5gc_vonr_exe,
|
||||
is_parallel : false,
|
||||
suite: '5gc')
|
||||
suite: ['vonr', '5gc'])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue