[AMF/MME] PLMN access control

These mechanisms are described in the GSMA roaming guidelines.
Chapters called Access Control.

For 4g: https://www.gsma.com/newsroom/wp-content/uploads//IR.88-v21.0.pdf
For 5g: https://www.gsma.com/newsroom/wp-content/uploads//NG.113-v6.0.pdf
This commit is contained in:
Sukchan Lee 2023-03-25 09:18:20 +09:00
parent 5f37777280
commit d469809192
10 changed files with 390 additions and 33 deletions

View File

@ -417,6 +417,41 @@ sbi:
# s_nssai:
# - sst: 1
#
#
# <Access Control>
#
# If access_control is not specified, then all networks are allowed
# If access_control is defined,
# no other networks are allowed other than matching plmn_id.
#
# default_reject_cause may be used to overwrite the default error cause #11
# for non matching plmn_id
#
# for matching plmn_id with reject_cause defined,
# the AMF rejects access with the reject_cause error cause
#
# for matching plmn_id without reject_cause defined,
# the AMF accepts the PLMN traffic
#
# o The example below only accepts 002/02 and 999/70 PLMNs.
# 001/01 is rejected with cause 15,
# and the rest of the PLMNs are rejected with default cause 13.
#
# amf:
# access_control:
# - default_reject_cause: 13
# - plmn_id:
# reject_cause: 15
# mcc: 001
# mnc: 01
# - plmn_id:
# mcc: 002
# mnc: 02
# - plmn_id:
# mcc: 999
# mnc: 70
#
#
# <Network Name>
#
# amf:

View File

@ -269,6 +269,41 @@ logger:
# mnc: 09
# tac: [70, 80]
#
#
# <Access Control>
#
# If access_control is not specified, then all networks are allowed
# If access_control is defined,
# no other networks are allowed other than matching plmn_id.
#
# default_reject_cause may be used to overwrite the default error cause #11
# for non matching plmn_id
#
# for matching plmn_id with reject_cause defined,
# the MME rejects access with the reject_cause error cause
#
# for matching plmn_id without reject_cause defined,
# the MME accepts the PLMN traffic
#
# o The example below only accepts 002/02 and 999/70 PLMNs.
# 001/01 is rejected with cause 15,
# and the rest of the PLMNs are rejected with default cause 13.
#
# mme:
# access_control:
# - default_reject_cause: 13
# - plmn_id:
# reject_cause: 15
# mcc: 001
# mnc: 01
# - plmn_id:
# mcc: 002
# mnc: 02
# - plmn_id:
# mcc: 999
# mnc: 70
#
#
# <Network Name>
# mme:
# network_name:

View File

@ -125,30 +125,6 @@ smf:
- identity: pcrf.localdomain
addr: 127.0.0.9
#
# <For Indirect Communication with Delegated Discovery>
#
# o (Default) If you do not set Delegated Discovery as shown below,
#
# sbi:
# - addr: 127.0.0.5
# port: 7777
#
# - Use SCP if SCP avaiable. Otherwise NRF is used.
# => App fails if both NRF and SCP are unavailable.
#
# sbi:
# - addr: 127.0.0.5
# port: 7777
# discovery:
# delegated: auto
#
# o To use SCP always => App fails if no SCP available.
# delegated: yes
#
# o Don't use SCP server => App fails if no NRF available.
# delegated: no
#
amf:
sbi:
- addr: 127.0.0.5

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019-2022 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
@ -84,6 +84,7 @@ extern "C" {
#define OGS_MAX_NUM_OF_SERVED_GUAMI 8
#define OGS_MAX_NUM_OF_SERVED_TAI OGS_MAX_NUM_OF_TAI
#define OGS_MAX_NUM_OF_ACCESS_CONTROL 8
#define OGS_MAX_NUM_OF_ALGORITHM 8
#define OGS_MAX_NUM_OF_BPLMN 6

View File

@ -755,6 +755,81 @@ int amf_context_parse_config(void)
}
} while (ogs_yaml_iter_type(&plmn_support_array) ==
YAML_SEQUENCE_NODE);
} else if (!strcmp(amf_key, "access_control")) {
ogs_yaml_iter_t access_control_array, access_control_iter;
ogs_yaml_iter_recurse(&amf_iter, &access_control_array);
do {
ogs_assert(self.num_of_access_control <
OGS_MAX_NUM_OF_ACCESS_CONTROL);
if (ogs_yaml_iter_type(&access_control_array) ==
YAML_MAPPING_NODE) {
memcpy(&access_control_iter, &access_control_array,
sizeof(ogs_yaml_iter_t));
} else if (ogs_yaml_iter_type(&access_control_array) ==
YAML_SEQUENCE_NODE) {
if (!ogs_yaml_iter_next(&access_control_array))
break;
ogs_yaml_iter_recurse(&access_control_array,
&access_control_iter);
} else if (ogs_yaml_iter_type(&access_control_array) ==
YAML_SCALAR_NODE) {
break;
} else
ogs_assert_if_reached();
while (ogs_yaml_iter_next(&access_control_iter)) {
const char *mnc = NULL, *mcc = NULL;
int reject_cause = 0;
const char *access_control_key =
ogs_yaml_iter_key(&access_control_iter);
ogs_assert(access_control_key);
if (!strcmp(access_control_key,
"default_reject_cause")) {
const char *v = ogs_yaml_iter_value(
&access_control_iter);
if (v) self.default_reject_cause = atoi(v);
} else if (!strcmp(access_control_key, "plmn_id")) {
ogs_yaml_iter_t plmn_id_iter;
ogs_yaml_iter_recurse(&access_control_iter,
&plmn_id_iter);
while (ogs_yaml_iter_next(&plmn_id_iter)) {
const char *plmn_id_key =
ogs_yaml_iter_key(&plmn_id_iter);
ogs_assert(plmn_id_key);
if (!strcmp(plmn_id_key, "reject_cause")) {
const char *v = ogs_yaml_iter_value(
&plmn_id_iter);
if (v) reject_cause = atoi(v);
} else if (!strcmp(plmn_id_key, "mcc")) {
mcc = ogs_yaml_iter_value(
&plmn_id_iter);
} else if (!strcmp(plmn_id_key, "mnc")) {
mnc = ogs_yaml_iter_value(
&plmn_id_iter);
}
}
if (mcc && mnc) {
ogs_plmn_id_build(
&self.access_control[
self.num_of_access_control].
plmn_id,
atoi(mcc), atoi(mnc), strlen(mnc));
if (reject_cause)
self.access_control[
self.num_of_access_control].
reject_cause = reject_cause;
self.num_of_access_control++;
}
} else
ogs_warn("unknown key `%s`",
access_control_key);
}
} while (ogs_yaml_iter_type(&access_control_array) ==
YAML_SEQUENCE_NODE);
} else if (!strcmp(amf_key, "security")) {
ogs_yaml_iter_t security_iter;
ogs_yaml_iter_recurse(&amf_iter, &security_iter);
@ -847,7 +922,8 @@ int amf_context_parse_config(void)
} while (
ogs_yaml_iter_type(&ciphering_order_iter) ==
YAML_SEQUENCE_NODE);
}
} else
ogs_warn("unknown key `%s`", security_key);
}
} else if (!strcmp(amf_key, "network_name")) {
ogs_yaml_iter_t network_name_iter;
@ -891,7 +967,8 @@ int amf_context_parse_config(void)
network_short_name->length = size*2+1;
network_short_name->coding_scheme = 1;
network_short_name->ext = 1;
}
} else
ogs_warn("unknown key `%s`", network_name_key);
}
} else if (!strcmp(amf_key, "amf_name")) {
self.amf_name = ogs_yaml_iter_value(&amf_iter);

View File

@ -66,6 +66,14 @@ typedef struct amf_context_s {
ogs_s_nssai_t s_nssai[OGS_MAX_NUM_OF_SLICE];
} plmn_support[OGS_MAX_NUM_OF_PLMN];
/* Access Control */
int default_reject_cause;
int num_of_access_control;
struct {
int reject_cause;
ogs_plmn_id_t plmn_id;
} access_control[OGS_MAX_NUM_OF_ACCESS_CONTROL];
/* defined in 'nas_ies.h'
* #define NAS_SECURITY_ALGORITHMS_EIA0 0
* #define NAS_SECURITY_ALGORITHMS_128_EEA1 1

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019,2020 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
@ -32,11 +32,14 @@ static ogs_nas_5gmm_cause_t gmm_handle_nas_message_container(
amf_ue_t *amf_ue, uint8_t message_type,
ogs_nas_message_container_t *nas_message_container);
static uint8_t gmm_cause_from_access_control(ogs_plmn_id_t *plmn_id);
ogs_nas_5gmm_cause_t gmm_handle_registration_request(amf_ue_t *amf_ue,
ogs_nas_security_header_type_t h, NGAP_ProcedureCode_t ngap_code,
ogs_nas_5gs_registration_request_t *registration_request)
{
int served_tai_index = 0;
uint8_t gmm_cause;
ran_ue_t *ran_ue = NULL;
ogs_nas_5gs_registration_type_t *registration_type = NULL;
@ -281,6 +284,18 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_request(amf_ue_t *amf_ue,
memcpy(&amf_ue->nr_cgi, &ran_ue->saved.nr_cgi, sizeof(ogs_nr_cgi_t));
amf_ue->ue_location_timestamp = ogs_time_now();
/* Check PLMN-ID access control */
gmm_cause = gmm_cause_from_access_control(&amf_ue->nr_tai.plmn_id);
if (gmm_cause != OGS_5GMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in TAI) access control");
return gmm_cause;
}
gmm_cause = gmm_cause_from_access_control(&amf_ue->nr_cgi.plmn_id);
if (gmm_cause != OGS_5GMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in CGI) access control");
return gmm_cause;
}
/* Check TAI */
served_tai_index = amf_find_served_tai(&amf_ue->nr_tai);
if (served_tai_index < 0) {
@ -511,6 +526,7 @@ ogs_nas_5gmm_cause_t gmm_handle_service_request(amf_ue_t *amf_ue,
ogs_nas_5gs_service_request_t *service_request)
{
int served_tai_index = 0;
uint8_t gmm_cause;
ran_ue_t *ran_ue = NULL;
ogs_nas_key_set_identifier_t *ngksi = NULL;
@ -605,6 +621,18 @@ ogs_nas_5gmm_cause_t gmm_handle_service_request(amf_ue_t *amf_ue,
memcpy(&amf_ue->nr_cgi, &ran_ue->saved.nr_cgi, sizeof(ogs_nr_cgi_t));
amf_ue->ue_location_timestamp = ogs_time_now();
/* Check PLMN-ID access control */
gmm_cause = gmm_cause_from_access_control(&amf_ue->nr_tai.plmn_id);
if (gmm_cause != OGS_5GMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in TAI) access control");
return gmm_cause;
}
gmm_cause = gmm_cause_from_access_control(&amf_ue->nr_cgi.plmn_id);
if (gmm_cause != OGS_5GMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in CGI) access control");
return gmm_cause;
}
/* Check TAI */
served_tai_index = amf_find_served_tai(&amf_ue->nr_tai);
if (served_tai_index < 0) {
@ -1411,3 +1439,29 @@ static ogs_nas_5gmm_cause_t gmm_handle_nas_message_container(
ogs_pkbuf_free(nasbuf);
return gmm_cause;
}
static uint8_t gmm_cause_from_access_control(ogs_plmn_id_t *plmn_id)
{
int i;
ogs_assert(plmn_id);
/* No Access Control */
if (amf_self()->num_of_access_control == 0)
return OGS_5GMM_CAUSE_REQUEST_ACCEPTED;
for (i = 0; i < amf_self()->num_of_access_control; i++) {
if (memcmp(&amf_self()->access_control[i].plmn_id,
plmn_id, OGS_PLMN_ID_LEN) == 0) {
if (amf_self()->access_control[i].reject_cause)
return amf_self()->access_control[i].reject_cause;
else
return OGS_5GMM_CAUSE_REQUEST_ACCEPTED;
}
}
if (amf_self()->default_reject_cause)
return amf_self()->default_reject_cause;
return OGS_5GMM_CAUSE_PLMN_NOT_ALLOWED;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2023 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
@ -32,10 +32,13 @@
#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __emm_log_domain
static uint8_t emm_cause_from_access_control(ogs_plmn_id_t *plmn_id);
int emm_handle_attach_request(mme_ue_t *mme_ue,
ogs_nas_eps_attach_request_t *attach_request, ogs_pkbuf_t *pkbuf)
{
int r;
uint8_t emm_cause;
int served_tai_index = 0;
ogs_nas_eps_mobile_identity_guti_t *eps_mobile_identity_guti = NULL;
@ -132,6 +135,26 @@ int emm_handle_attach_request(mme_ue_t *mme_ue,
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
/* Check PLMN-ID access control */
emm_cause = emm_cause_from_access_control(&mme_ue->tai.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in TAI) access control");
r = nas_eps_send_attach_reject(mme_ue,
emm_cause, OGS_NAS_ESM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
emm_cause = emm_cause_from_access_control(&mme_ue->e_cgi.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in CGI) access control");
r = nas_eps_send_attach_reject(mme_ue,
emm_cause, OGS_NAS_ESM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
/* Check TAI */
served_tai_index = mme_find_served_tai(&mme_ue->tai);
if (served_tai_index < 0) {
@ -504,6 +527,7 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
{
int r;
int served_tai_index = 0;
uint8_t emm_cause;
ogs_nas_eps_mobile_identity_guti_t *eps_mobile_identity_guti = NULL;
ogs_nas_eps_guti_t nas_guti;
@ -570,6 +594,24 @@ int emm_handle_tau_request(mme_ue_t *mme_ue,
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
/* Check PLMN-ID access control */
emm_cause = emm_cause_from_access_control(&mme_ue->tai.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in TAI) access control");
r = nas_eps_send_tau_reject(mme_ue, emm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
emm_cause = emm_cause_from_access_control(&mme_ue->e_cgi.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in CGI) access control");
r = nas_eps_send_tau_reject(mme_ue, emm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
/* Check TAI */
served_tai_index = mme_find_served_tai(&mme_ue->tai);
if (served_tai_index < 0) {
@ -644,6 +686,7 @@ int emm_handle_extended_service_request(mme_ue_t *mme_ue,
{
int r;
int served_tai_index = 0;
uint8_t emm_cause;
ogs_nas_service_type_t *service_type =
&extended_service_request->service_type;
@ -690,6 +733,24 @@ int emm_handle_extended_service_request(mme_ue_t *mme_ue,
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
/* Check PLMN-ID access control */
emm_cause = emm_cause_from_access_control(&mme_ue->tai.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in TAI) access control");
r = nas_eps_send_tau_reject(mme_ue, emm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
emm_cause = emm_cause_from_access_control(&mme_ue->e_cgi.plmn_id);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_error("Rejected by PLMN-ID(in CGI) access control");
r = nas_eps_send_tau_reject(mme_ue, emm_cause);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return OGS_ERROR;
}
/* Check TAI */
served_tai_index = mme_find_served_tai(&mme_ue->tai);
if (served_tai_index < 0) {
@ -767,3 +828,29 @@ int emm_handle_security_mode_complete(mme_ue_t *mme_ue,
return OGS_OK;
}
static uint8_t emm_cause_from_access_control(ogs_plmn_id_t *plmn_id)
{
int i;
ogs_assert(plmn_id);
/* No Access Control */
if (mme_self()->num_of_access_control == 0)
return OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED;
for (i = 0; i < mme_self()->num_of_access_control; i++) {
if (memcmp(&mme_self()->access_control[i].plmn_id,
plmn_id, OGS_PLMN_ID_LEN) == 0) {
if (mme_self()->access_control[i].reject_cause)
return mme_self()->access_control[i].reject_cause;
else
return OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED;
}
}
if (mme_self()->default_reject_cause)
return mme_self()->default_reject_cause;
return OGS_NAS_EMM_CAUSE_PLMN_NOT_ALLOWED;
}

View File

@ -626,8 +626,7 @@ int mme_context_parse_config()
const char *plmn_id_key =
ogs_yaml_iter_key(&plmn_id_iter);
ogs_assert(plmn_id_key);
if (!strcmp(plmn_id_key, "mcc"))
{
if (!strcmp(plmn_id_key, "mcc")) {
mcc = ogs_yaml_iter_value(
&plmn_id_iter);
} else if (!strcmp(
@ -910,6 +909,81 @@ int mme_context_parse_config()
if (list2->num || num_of_list1 || num_of_list0) {
self.num_of_served_tai++;
}
} else if (!strcmp(mme_key, "access_control")) {
ogs_yaml_iter_t access_control_array, access_control_iter;
ogs_yaml_iter_recurse(&mme_iter, &access_control_array);
do {
ogs_assert(self.num_of_access_control <
OGS_MAX_NUM_OF_ACCESS_CONTROL);
if (ogs_yaml_iter_type(&access_control_array) ==
YAML_MAPPING_NODE) {
memcpy(&access_control_iter, &access_control_array,
sizeof(ogs_yaml_iter_t));
} else if (ogs_yaml_iter_type(&access_control_array) ==
YAML_SEQUENCE_NODE) {
if (!ogs_yaml_iter_next(&access_control_array))
break;
ogs_yaml_iter_recurse(&access_control_array,
&access_control_iter);
} else if (ogs_yaml_iter_type(&access_control_array) ==
YAML_SCALAR_NODE) {
break;
} else
ogs_assert_if_reached();
while (ogs_yaml_iter_next(&access_control_iter)) {
const char *mnc = NULL, *mcc = NULL;
int reject_cause = 0;
const char *access_control_key =
ogs_yaml_iter_key(&access_control_iter);
ogs_assert(access_control_key);
if (!strcmp(access_control_key,
"default_reject_cause")) {
const char *v = ogs_yaml_iter_value(
&access_control_iter);
if (v) self.default_reject_cause = atoi(v);
} else if (!strcmp(access_control_key, "plmn_id")) {
ogs_yaml_iter_t plmn_id_iter;
ogs_yaml_iter_recurse(&access_control_iter,
&plmn_id_iter);
while (ogs_yaml_iter_next(&plmn_id_iter)) {
const char *plmn_id_key =
ogs_yaml_iter_key(&plmn_id_iter);
ogs_assert(plmn_id_key);
if (!strcmp(plmn_id_key, "reject_cause")) {
const char *v = ogs_yaml_iter_value(
&plmn_id_iter);
if (v) reject_cause = atoi(v);
} else if (!strcmp(plmn_id_key, "mcc")) {
mcc = ogs_yaml_iter_value(
&plmn_id_iter);
} else if (!strcmp(plmn_id_key, "mnc")) {
mnc = ogs_yaml_iter_value(
&plmn_id_iter);
}
}
if (mcc && mnc) {
ogs_plmn_id_build(
&self.access_control[
self.num_of_access_control].
plmn_id,
atoi(mcc), atoi(mnc), strlen(mnc));
if (reject_cause)
self.access_control[
self.num_of_access_control].
reject_cause = reject_cause;
self.num_of_access_control++;
}
} else
ogs_warn("unknown key `%s`",
access_control_key);
}
} while (ogs_yaml_iter_type(&access_control_array) ==
YAML_SEQUENCE_NODE);
} else if (!strcmp(mme_key, "security")) {
ogs_yaml_iter_t security_iter;
ogs_yaml_iter_recurse(&mme_iter, &security_iter);
@ -1002,7 +1076,8 @@ int mme_context_parse_config()
} while (
ogs_yaml_iter_type(&ciphering_order_iter) ==
YAML_SEQUENCE_NODE);
}
} else
ogs_warn("unknown key `%s`", security_key);
}
} else if (!strcmp(mme_key, "network_name")) {
ogs_yaml_iter_t network_name_iter;
@ -1046,7 +1121,8 @@ int mme_context_parse_config()
network_short_name->length = size*2+1;
network_short_name->coding_scheme = 1;
network_short_name->ext = 1;
}
} else
ogs_warn("unknown key `%s`", network_name_key);
}
} else if (!strcmp(mme_key, "sgsap")) {
ogs_yaml_iter_t sgsap_array, sgsap_iter;

View File

@ -109,6 +109,14 @@ typedef struct mme_context_s {
ogs_eps_tai2_list_t list2;
} served_tai[OGS_MAX_NUM_OF_SERVED_TAI];
/* Access Control */
int default_reject_cause;
int num_of_access_control;
struct {
int reject_cause;
ogs_plmn_id_t plmn_id;
} access_control[OGS_MAX_NUM_OF_ACCESS_CONTROL];
/* defined in 'nas_ies.h'
* #define NAS_SECURITY_ALGORITHMS_EIA0 0
* #define NAS_SECURITY_ALGORITHMS_128_EEA1 1