Fix DNN Operator-Identifier format and refactor OI parsing for HR roaming interop

Align full-DNN construction with 3GPP TS 23.003 §9.1.2 by switching the
Operator Identifier format from "5gc.mncXXX.mccYYY.3gppnetwork.org" to
"mncXXX.mccYYY.gprs". Introduce new helper utilities to extract and build
OI (Operator Identifier) from both PLMN-ID and FQDN, and replace the
legacy `ogs_home_network_domain_from_fqdn()` usage in AMF/SMF/PCF paths.

This resolves DNN misalignment in vSMF–hSMF PDU Session Create that
caused interop issues with external 5G core vendors during HR roaming.

Includes updates across AMF/SMF/PCF, unit tests, and supporting helpers.

Issues: #4096
This commit is contained in:
Sukchan Lee 2025-12-06 22:22:44 +09:00
parent 731ecc4e1b
commit 782a97efe9
11 changed files with 102 additions and 99 deletions

View file

@ -61,6 +61,8 @@
#include <netinet/in.h>
#include <sys/socket.h>
#include <ctype.h>
#endif
#endif

View file

@ -420,3 +420,22 @@ char *ogs_trimcharacter(char *str, char to_remove)
return ogs_right_trimcharacter(
ogs_left_trimcharacter(str, to_remove), to_remove);
}
char *ogs_strrstr(const char *haystack, const char *needle)
{
char *result = NULL;
char *p = (char *)haystack;
if (!haystack || !needle)
return NULL;
if (*needle == '\0')
return (char *)haystack;
while ((p = strstr(p, needle)) != NULL) {
result = p;
p++;
}
return result;
}

View file

@ -149,6 +149,8 @@ char *ogs_left_trimcharacter(char *str, char to_remove);
char *ogs_right_trimcharacter(char *str, char to_remove);
char *ogs_trimcharacter(char *str, char to_remove);
char *ogs_strrstr(const char *haystack, const char *needle);
#ifdef __cplusplus
}
#endif

View file

@ -129,8 +129,9 @@ char *ogs_plmn_id_to_string(const ogs_plmn_id_t *plmn_id, char *buf)
}
#define FQDN_3GPPNETWORK_ORG ".3gppnetwork.org"
#define FQDN_5GC_MNC "5gc.mnc"
#define FQDN_GPRS ".gprs"
#define FQDN_MCC ".mcc"
#define FQDN_MNC ".mnc"
char *ogs_serving_network_name_from_plmn_id(const ogs_plmn_id_t *plmn_id)
{
@ -165,78 +166,65 @@ char *ogs_nssf_fqdn_from_plmn_id(const ogs_plmn_id_t *plmn_id)
ogs_plmn_id_mnc(plmn_id), ogs_plmn_id_mcc(plmn_id));
}
char *ogs_home_network_domain_from_fqdn(char *fqdn)
char *ogs_dnn_oi_from_plmn_id(const ogs_plmn_id_t *plmn_id)
{
char *p = NULL;
return ogs_msprintf("mnc%03d.mcc%03d" FQDN_GPRS,
ogs_plmn_id_mnc(plmn_id), ogs_plmn_id_mcc(plmn_id));
}
char *ogs_dnn_oi_from_fqdn(char *fqdn)
{
char *mnc_pos = NULL;
ogs_assert(fqdn);
if (strlen(fqdn) <
strlen(FQDN_5GC_MNC "XXX" FQDN_MCC "XXX" FQDN_3GPPNETWORK_ORG)) {
/* Find ".mnc" from right side */
mnc_pos = ogs_strrstr(fqdn, FQDN_MNC);
if (!mnc_pos)
return NULL;
}
p = fqdn + strlen(fqdn);
if (strncmp(p - strlen(FQDN_3GPPNETWORK_ORG),
FQDN_3GPPNETWORK_ORG, strlen(FQDN_3GPPNETWORK_ORG)) != 0) {
/* Ensure minimum required length for parsing */
if ((mnc_pos + strlen(FQDN_MNC) + 3 + strlen(FQDN_MCC) + 3) >
fqdn + strlen(fqdn))
return NULL;
}
p -= (strlen(FQDN_3GPPNETWORK_ORG) + 3);
if (strncmp(p - strlen(FQDN_MCC),
FQDN_MCC, strlen(FQDN_MCC)) != 0) {
/* Validate that ".mnc" is followed by 3 digits */
if (!isdigit(mnc_pos[4]) ||
!isdigit(mnc_pos[5]) ||
!isdigit(mnc_pos[6]))
return NULL;
}
p -= (strlen(FQDN_MCC) + 3);
if (strncmp(p - strlen(FQDN_5GC_MNC),
FQDN_5GC_MNC, strlen(FQDN_5GC_MNC)) != 0) {
/* Check format ".mcc" after MNC */
if (strncmp(mnc_pos + 7, FQDN_MCC, strlen(FQDN_MCC)) != 0)
return NULL;
}
return p - strlen(FQDN_5GC_MNC);
/* Validate MCC digits */
if (!isdigit(mnc_pos[11]) ||
!isdigit(mnc_pos[12]) ||
!isdigit(mnc_pos[13]))
return NULL;
return mnc_pos+1; /* caller will parse MNC, MCC from here */
}
uint16_t ogs_plmn_id_mcc_from_fqdn(char *fqdn)
{
char mcc[4];
char *p = NULL;
ogs_assert(fqdn);
p = ogs_home_network_domain_from_fqdn(fqdn);
if (p == NULL) {
char *p = ogs_dnn_oi_from_fqdn(fqdn);
if (!p) {
ogs_error("Invalid FQDN [%d:%s]", (int)strlen(fqdn), fqdn);
return 0;
}
p += strlen(FQDN_5GC_MNC) + 3 + strlen(FQDN_MCC);
memcpy(mcc, p, 3);
mcc[3] = 0;
return atoi(mcc);
return (uint16_t)atoi(p + 10); /* after ".mcc" */
}
uint16_t ogs_plmn_id_mnc_from_fqdn(char *fqdn)
{
char mnc[4];
char *p = NULL;
ogs_assert(fqdn);
p = ogs_home_network_domain_from_fqdn(fqdn);
if (p == NULL) {
char *p = ogs_dnn_oi_from_fqdn(fqdn);
if (!p) {
ogs_error("Invalid FQDN [%d:%s]", (int)strlen(fqdn), fqdn);
return 0;
}
p += strlen(FQDN_5GC_MNC);
memcpy(mnc, p, 3);
mnc[3] = 0;
return atoi(mnc);
return (uint16_t)atoi(p + 3); /* after "mnc" */
}
uint32_t ogs_amf_id_hexdump(const ogs_amf_id_t *amf_id)

View file

@ -246,7 +246,8 @@ char *ogs_home_network_domain_from_plmn_id(const ogs_plmn_id_t *plmn_id);
char *ogs_epc_domain_from_plmn_id(const ogs_plmn_id_t *plmn_id);
char *ogs_nrf_fqdn_from_plmn_id(const ogs_plmn_id_t *plmn_id);
char *ogs_nssf_fqdn_from_plmn_id(const ogs_plmn_id_t *plmn_id);
char *ogs_home_network_domain_from_fqdn(char *fqdn);
char *ogs_dnn_oi_from_plmn_id(const ogs_plmn_id_t *plmn_id);
char *ogs_dnn_oi_from_fqdn(char *fqdn);
uint16_t ogs_plmn_id_mnc_from_fqdn(char *fqdn);
uint16_t ogs_plmn_id_mcc_from_fqdn(char *fqdn);

View file

@ -2907,7 +2907,7 @@ bool ogs_sbi_fqdn_in_vplmn(char *fqdn)
return false;
}
if (ogs_home_network_domain_from_fqdn(fqdn) == NULL) {
if (ogs_dnn_oi_from_fqdn(fqdn) == NULL) {
return false;
}

View file

@ -95,15 +95,13 @@ ogs_sbi_request_t *amf_nsmf_pdusession_build_create_sm_context(
* is absent, the serving core network operator shall be assumed.
*/
if (ogs_sbi_plmn_id_in_vplmn(&amf_ue->home_plmn_id) == true) {
char *home_network_domain =
ogs_home_network_domain_from_plmn_id(&amf_ue->home_plmn_id);
ogs_assert(home_network_domain);
char *dnn_oi = ogs_dnn_oi_from_plmn_id(&amf_ue->home_plmn_id);
ogs_assert(dnn_oi);
SmContextCreateData.dnn =
ogs_msprintf("%s.%s", sess->dnn, home_network_domain);
SmContextCreateData.dnn = ogs_msprintf("%s.%s", sess->dnn, dnn_oi);
ogs_assert(SmContextCreateData.dnn);
ogs_free(home_network_domain);
ogs_free(dnn_oi);
} else {

View file

@ -234,7 +234,7 @@ bool pcf_npcf_smpolicycontrol_handle_create(pcf_sess_t *sess,
uint16_t fqdn_port = 0;
ogs_sockaddr_t *addr = NULL, *addr6 = NULL;
char *home_network_domain = NULL;
char *dnn_oi = NULL;
ogs_assert(sess);
pcf_ue_sm = pcf_ue_sm_find_by_id(sess->pcf_ue_sm_id);
@ -369,22 +369,20 @@ bool pcf_npcf_smpolicycontrol_handle_create(pcf_sess_t *sess,
* The DNN of the PDU session, a full DNN with both the Network Identifier
* and Operator Identifier, or a DNN with the Network Identifier only
*/
home_network_domain = ogs_home_network_domain_from_fqdn(
SmPolicyContextData->dnn);
dnn_oi = ogs_dnn_oi_from_fqdn(SmPolicyContextData->dnn);
if (home_network_domain) {
char dnn_network_identifer[OGS_MAX_DNN_LEN+1];
if (dnn_oi) {
char dnn_ni[OGS_MAX_DNN_LEN+1];
uint16_t mcc = 0, mnc = 0;
ogs_assert(home_network_domain > SmPolicyContextData->dnn);
ogs_assert(dnn_oi > SmPolicyContextData->dnn);
ogs_cpystrn(dnn_network_identifer, SmPolicyContextData->dnn,
ogs_min(OGS_MAX_DNN_LEN,
home_network_domain - SmPolicyContextData->dnn));
ogs_cpystrn(dnn_ni, SmPolicyContextData->dnn,
ogs_min(OGS_MAX_DNN_LEN, dnn_oi - SmPolicyContextData->dnn));
if (sess->dnn)
ogs_free(sess->dnn);
sess->dnn = ogs_strdup(dnn_network_identifer);
sess->dnn = ogs_strdup(dnn_ni);
ogs_assert(sess->dnn);
if (sess->full_dnn)

View file

@ -115,17 +115,16 @@ ogs_sbi_request_t *smf_npcf_smpolicycontrol_build_create(
* and Operator Identifier, or a DNN with the Network Identifier only
*/
if (ogs_sbi_supi_in_vplmn(smf_ue->supi) == true) {
char *home_network_domain = NULL;
char *dnn_oi = NULL;
home_network_domain =
ogs_home_network_domain_from_plmn_id(&sess->home_plmn_id);
ogs_assert(home_network_domain);
dnn_oi = ogs_dnn_oi_from_plmn_id(&sess->home_plmn_id);
ogs_assert(dnn_oi);
SmPolicyContextData.dnn =
ogs_msprintf("%s.%s", sess->session.name, home_network_domain);
ogs_msprintf("%s.%s", sess->session.name, dnn_oi);
ogs_assert(SmPolicyContextData.dnn);
ogs_free(home_network_domain);
ogs_free(dnn_oi);
} else {
SmPolicyContextData.dnn = ogs_strdup(sess->session.name);

View file

@ -43,7 +43,7 @@ bool smf_nsmf_handle_create_sm_context(
char *fqdn = NULL;
uint16_t fqdn_port = 0;
ogs_sockaddr_t *addr = NULL, *addr6 = NULL;
char *home_network_domain = NULL;
char *dnn_oi = NULL;
OpenAPI_sm_context_create_data_t *SmContextCreateData = NULL;
OpenAPI_nr_location_t *NrLocation = NULL;
@ -353,22 +353,20 @@ bool smf_nsmf_handle_create_sm_context(
* the full DNN in LBO and non-roaming scenarios. If the Operator Identifier
* is absent, the serving core network operator shall be assumed.
*/
home_network_domain =
ogs_home_network_domain_from_fqdn(SmContextCreateData->dnn);
dnn_oi = ogs_dnn_oi_from_fqdn(SmContextCreateData->dnn);
if (home_network_domain) {
char dnn_network_identifer[OGS_MAX_DNN_LEN+1];
if (dnn_oi) {
char dnn_ni[OGS_MAX_DNN_LEN+1];
uint16_t mcc = 0, mnc = 0;
ogs_assert(home_network_domain > SmContextCreateData->dnn);
ogs_assert(dnn_oi > SmContextCreateData->dnn);
ogs_cpystrn(dnn_network_identifer, SmContextCreateData->dnn,
ogs_min(OGS_MAX_DNN_LEN,
home_network_domain - SmContextCreateData->dnn));
ogs_cpystrn(dnn_ni, SmContextCreateData->dnn,
ogs_min(OGS_MAX_DNN_LEN, dnn_oi - SmContextCreateData->dnn));
if (sess->session.name)
ogs_free(sess->session.name);
sess->session.name = ogs_strdup(dnn_network_identifer);
sess->session.name = ogs_strdup(dnn_ni);
ogs_assert(sess->session.name);
if (sess->full_dnn)
@ -1255,7 +1253,7 @@ bool smf_nsmf_handle_create_data_in_hsmf(
char *fqdn = NULL;
uint16_t fqdn_port = 0;
ogs_sockaddr_t *addr = NULL, *addr6 = NULL;
char *home_network_domain = NULL;
char *dnn_oi = NULL;
OpenAPI_pdu_session_create_data_t *PduSessionCreateData = NULL;
OpenAPI_nr_location_t *NrLocation = NULL;
@ -1325,21 +1323,19 @@ bool smf_nsmf_handle_create_data_in_hsmf(
return false;
}
home_network_domain =
ogs_home_network_domain_from_fqdn(PduSessionCreateData->dnn);
dnn_oi = ogs_dnn_oi_from_fqdn(PduSessionCreateData->dnn);
if (home_network_domain) {
char dnn_network_identifer[OGS_MAX_DNN_LEN+1];
if (dnn_oi) {
char dnn_ni[OGS_MAX_DNN_LEN+1];
ogs_assert(home_network_domain > PduSessionCreateData->dnn);
ogs_assert(dnn_oi > PduSessionCreateData->dnn);
ogs_cpystrn(dnn_network_identifer, PduSessionCreateData->dnn,
ogs_min(OGS_MAX_DNN_LEN,
home_network_domain - PduSessionCreateData->dnn));
ogs_cpystrn(dnn_ni, PduSessionCreateData->dnn,
ogs_min(OGS_MAX_DNN_LEN, dnn_oi - PduSessionCreateData->dnn));
if (sess->session.name)
ogs_free(sess->session.name);
sess->session.name = ogs_strdup(dnn_network_identifer);
sess->session.name = ogs_strdup(dnn_ni);
ogs_assert(sess->session.name);
if (sess->full_dnn)

View file

@ -49,29 +49,29 @@ static void proto_message_test1(abts_case *tc, void *data)
static void proto_message_test2(abts_case *tc, void *data)
{
char *home_network_domain = NULL;
char *dnn_oi = NULL;
char *full_dnn = NULL;
char dnn_ni[OGS_MAX_DNN_LEN+1];
ogs_plmn_id_t plmn_id1, plmn_id2;
ogs_plmn_id_build(&plmn_id1, 456, 123, 3);
home_network_domain = ogs_home_network_domain_from_plmn_id(&plmn_id1);
dnn_oi = ogs_dnn_oi_from_plmn_id(&plmn_id1);
ABTS_STR_EQUAL(tc,
"5gc.mnc123.mcc456.3gppnetwork.org", home_network_domain);
full_dnn = ogs_msprintf("internet.realm.%s", home_network_domain);
"mnc123.mcc456.gprs", dnn_oi);
full_dnn = ogs_msprintf("internet.realm.%s", dnn_oi);
ABTS_STR_EQUAL(tc,
"internet.realm.5gc.mnc123.mcc456.3gppnetwork.org", full_dnn);
"internet.realm.mnc123.mcc456.gprs", full_dnn);
ABTS_STR_EQUAL(tc,
home_network_domain, ogs_home_network_domain_from_fqdn(full_dnn));
dnn_oi, ogs_dnn_oi_from_fqdn(full_dnn));
ogs_cpystrn(dnn_ni, full_dnn,
ogs_min(OGS_MAX_DNN_LEN,
ogs_home_network_domain_from_fqdn(full_dnn) - full_dnn));
ogs_dnn_oi_from_fqdn(full_dnn) - full_dnn));
ABTS_STR_EQUAL(tc, "internet.realm", dnn_ni);
ABTS_INT_EQUAL(tc, 456, ogs_plmn_id_mcc_from_fqdn(full_dnn));
ABTS_INT_EQUAL(tc, 123, ogs_plmn_id_mnc_from_fqdn(full_dnn));
ogs_free(home_network_domain);
ogs_free(dnn_oi);
ogs_free(full_dnn);
}