merge main branch

This commit is contained in:
Sukchan Lee 2026-06-09 17:24:33 +09:00
parent 82956da7b8
commit 21ef6bd350
139 changed files with 7722 additions and 2602 deletions

42
.github/SECURITY.md vendored Normal file
View file

@ -0,0 +1,42 @@
# Security Policy
## Reporting a Vulnerability
The Open5GS project takes security vulnerabilities seriously.
If you believe you have discovered a security issue in Open5GS, please report it privately and avoid public disclosure until the issue has been reviewed and addressed.
Please report vulnerabilities privately using GitHub's "Report a vulnerability" button under the Security tab (Security → Advisories → Report a vulnerability). This opens a private advisory visible only to the maintainers.
When reporting a vulnerability, please include:
* Description of the issue
* Affected version(s)
* Steps to reproduce
* Proof of concept, if available
* Potential impact
## Scope
This policy covers security vulnerabilities in the source code of the
open5gs/open5gs repository, including the network function implementations and
the bundled WebUI.
The following are generally considered out of scope:
* Vulnerabilities in third-party software or dependencies
* Issues caused by deployment or configuration choices outside Open5GS
* Reports generated only by automated scanners without demonstrated impact
* Theoretical issues without a realistic threat model or attack scenario
## Supported Versions
Users are encouraged to use the latest stable release of Open5GS.
Security fixes are generally applied to actively maintained versions.
## Responsible Disclosure
Please allow maintainers reasonable time to investigate and address reported vulnerabilities before public disclosure.
The Open5GS project appreciates responsible and coordinated vulnerability disclosure.

View file

@ -340,6 +340,11 @@ pcf:
server:
- address: 127.0.1.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- plmn_id:
mcc: 001

View file

@ -342,6 +342,11 @@ pcf:
server:
- address: 127.0.2.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- plmn_id:
mcc: 999

View file

@ -342,6 +342,11 @@ pcf:
server:
- address: 127.0.3.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- plmn_id:
mcc: 999

View file

@ -350,6 +350,11 @@ pcf:
server:
- address: 127.0.1.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 001010000000001-001019999999999

View file

@ -351,6 +351,11 @@ pcf:
server:
- address: 127.0.2.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 999700000000001-999709999999999

View file

@ -351,6 +351,11 @@ pcf:
server:
- address: 127.0.3.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 999700000000001-999709999999999

View file

@ -355,6 +355,11 @@ pcf:
server:
- address: 127.0.1.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 001010000000001-001019999999999

View file

@ -356,6 +356,11 @@ pcf:
server:
- address: 127.0.2.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 999700000000001-999709999999999

View file

@ -356,6 +356,11 @@ pcf:
server:
- address: 127.0.3.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
policy:
- supi_range:
- 999700000000001-999709999999999

View file

@ -46,6 +46,8 @@ mme:
time:
# t3402:
# value: 720 # 12 minutes * 60 = 720 seconds
# t3396:
# value: 720 # 12 minutes * 60 = 720 seconds
# t3412:
# value: 3240 # 54 minutes * 60 = 3240 seconds
# t3423:

View file

@ -24,6 +24,26 @@ pcf:
- address: 127.0.0.13
port: 9090
################################################################################
# QoS Profile Mapping (Npcf_PolicyAuthorization / TS 29.514)
################################################################################
# Maps AF-supplied qosReference strings to configured 5QI/QoS index values.
# Required only when AFs send qosReference in MediaComponent.
# If absent, the PCF falls back to media-type mapping (AUDIO->1, VIDEO->2,
# CONTROL->5). Unknown qosReference values result in 403 Forbidden.
#
# qos_profiles:
# - reference: "conversational-voice"
# qos_index: 1
# - reference: "conversational-video"
# qos_index: 2
# - reference: "ims-signalling"
# qos_index: 5
# - reference: "standard-data"
# qos_index: 9
# - reference: "low-latency"
# qos_index: 3
################################################################################
# PCF Policy Configuration: UE Home PLMN and SUPI Range Based Policies
################################################################################

View file

@ -286,6 +286,11 @@ pcf:
server:
- address: 127.0.0.13
port: 9090
qos_profiles:
- reference: "test-audio"
qos_index: 1
- reference: "test-video"
qos_index: 2
nssf:
sbi:

View file

@ -27,6 +27,7 @@ If you have tested radio hardware from a vendor not listed with Open5GS, please
* NOKIA AEQE (SW: 5G20A)
* NOKIA AEQD (SW: 5G20A)
* NOKIA AEQP (SW: 5G21A)
* NOKIA AIRSCALE ASIK+ABIL BBU + AWHQE (n78) (SW: SBTS 25R2)
* MOSO Networks Canopy 5GID1 Indoor 2T2R (5G n48 n78)
* ZTE ITBBU ITRAN-PNF V5.65.20.20F10 (n78, n1, n3)
@ -62,6 +63,7 @@ If you have tested radio hardware from a vendor not listed with Open5GS, please
* Nokia FWGR Flexi Zone Mini-Macro Outdoor BTS, 2x20w Band 1
* Nokia FWHG Flexi Zone Indoor Pico BTS, 2x250 mW Band 7
* Nokia FW2HHD Flexi Zone Multiband Indoor Pico BTS, Band 38/41(S/W TLS18SP_ENB)
* Nokia Airscale - ASIA + ABIA + AHIB Band 66 (SW: SBTS325R2)
* Mikrotik Intercell B1+B3 IC322GC-b1D+b3D
* Ruckus Q710 and Q910
* Sercomm SCE4255W "Englewood" (band 48/CBRS, SW version DG3934v3@2308041842)

View file

@ -6,6 +6,10 @@ permalink: /docs/
head_inline: "<style> ul { padding-bottom: 1em; } </style>"
---
- Featured Community Projects
- [Open5GS Network Management System (NMS)](https://github.com/paulmataruso/open5gs-nms) - Web UI for Open5GS: config, subscribers, monitoring, and topology in one place
- [Docker Open5GS](https://github.com/herlesupreeth/docker_open5gs) - One-command Docker setup for a full 4G/5G Open5GS stack (Core, IMS, RAN, UE).
- User's Guide
- [Quickstart](guide/01-quickstart)
- [Building Open5GS from Sources](guide/02-building-open5gs-from-sources)

View file

@ -256,6 +256,9 @@ int ogs_app_parse_global_conf(ogs_yaml_iter_t *parent)
} else if (!strcmp(parameter_key, "fake_csfb")) {
global_conf.parameter.fake_csfb =
ogs_yaml_iter_bool(&parameter_iter);
} else if (!strcmp(parameter_key, "no_ims")) {
global_conf.parameter.no_ims =
ogs_yaml_iter_bool(&parameter_iter);
} else if (!strcmp(parameter_key,
"no_ipv4v6_local_addr_in_packet_filter")) {
global_conf.parameter.

View file

@ -70,6 +70,7 @@ typedef struct ogs_global_conf_s {
int use_openair;
int fake_csfb;
int no_ims;
int use_upg_vpp;
int no_ipv4v6_local_addr_in_packet_filter;

View file

@ -374,6 +374,32 @@ void *ogs_hash_get_or_set_debug(ogs_hash_t *ht,
return NULL;
}
bool ogs_hash_unset_if_owner_debug(ogs_hash_t *ht,
const void *key, int klen, const void *owner, const char *file_line)
{
void *indexed = NULL;
ogs_assert(ht);
ogs_assert(key);
ogs_assert(klen);
ogs_assert(owner);
indexed = ogs_hash_get_debug(ht, key, klen, file_line);
if (indexed == owner) {
ogs_hash_set_debug(ht, key, klen, NULL, file_line);
return true;
}
/*
* A newer context has re-claimed this key (the same identity re-attached
* while this context was still being torn down). Leave the entry alone so
* the live context stays findable; clearing it would orphan that entry.
*/
ogs_error("%s: hash unset skipped: entry not owned by expected context",
file_line);
return false;
}
unsigned int ogs_hash_count(ogs_hash_t *ht)
{
ogs_assert(ht);
@ -382,12 +408,37 @@ unsigned int ogs_hash_count(ogs_hash_t *ht)
void ogs_hash_clear(ogs_hash_t *ht)
{
ogs_hash_index_t *hi;
unsigned int i;
ogs_assert(ht);
ogs_assert(ht->array);
for (hi = ogs_hash_first(ht); hi; hi = ogs_hash_next(hi))
ogs_hash_set(ht, hi->this->key, hi->this->klen, NULL);
/*
* Recycle entries directly through the bucket array instead of
* re-locating each one with ogs_hash_set(ht, key, klen, NULL).
*
* Clearing the table only needs the entries we already hold, not their
* hash values, so re-hashing the key here is unnecessary -- and unsafe.
* During teardown a key may point at memory that an owning context has
* already freed; hashfunc_default() reads the key byte-by-byte (and
* find_entry() then memcmp()s it), which is a use-after-free read on a
* dangling key. Walking the buckets never dereferences key contents, so
* a stale entry is recycled harmlessly rather than crashing the exit
* path. (The hash never owns the key memory, so nothing is freed here.)
*/
for (i = 0; i <= ht->max; i++) {
ogs_hash_entry_t *he = ht->array[i];
ht->array[i] = NULL;
while (he) {
ogs_hash_entry_t *next_he = he->next;
he->next = ht->free;
ht->free = he;
he = next_he;
}
}
ht->count = 0;
}
/* This is basically the following...

View file

@ -67,6 +67,23 @@ void *ogs_hash_get_debug(ogs_hash_t *ht,
void *ogs_hash_get_or_set_debug(ogs_hash_t *ht,
const void *key, int klen, const void *val, const char *file_line);
/*
* Clear an entry ONLY if it still points at the expected owner.
*
* Identity-keyed tables (SUPI/SUCI/GUTI/IMSI/...) can be re-claimed by a
* freshly created context while an older context that holds the same key is
* still being torn down (re-attach races, deferred removal). A blind
* ogs_hash_set(ht, key, klen, NULL) in the old context's teardown would then
* orphan the newer entry, leaving the live context unfindable.
*
* Returns true if the entry was unset (owner matched), false if it was left
* in place because a newer context owns it now.
*/
#define ogs_hash_unset_if_owner(ht, key, klen, owner) \
ogs_hash_unset_if_owner_debug(ht, key, klen, owner, OGS_FILE_LINE)
bool ogs_hash_unset_if_owner_debug(ogs_hash_t *ht,
const void *key, int klen, const void *owner, const char *file_line);
ogs_hash_index_t *ogs_hash_first(ogs_hash_t *ht);
ogs_hash_index_t *ogs_hash_next(ogs_hash_index_t *hi);
void ogs_hash_this(ogs_hash_index_t *hi,

View file

@ -512,8 +512,8 @@ static int tlv_parse_leaf(void *msg, ogs_tlv_desc_t *desc, ogs_tlv_t *tlv)
{
ogs_tlv_uint16_t *v = (ogs_tlv_uint16_t *)msg;
if (tlv->length < 1 || tlv->length > 2) {
ogs_error("Invalid TLV length %d.", tlv->length);
if (tlv->length != 2) {
ogs_error("Invalid TLV length %d. It should be 2", tlv->length);
return OGS_ERROR;
}
v->u16 = ((((uint8_t*)tlv->value)[0]<< 8)&0xff00) |
@ -527,8 +527,8 @@ static int tlv_parse_leaf(void *msg, ogs_tlv_desc_t *desc, ogs_tlv_t *tlv)
{
ogs_tlv_uint24_t *v = (ogs_tlv_uint24_t *)msg;
if (tlv->length < 1 || tlv->length > 3) {
ogs_error("Invalid TLV length %d.", tlv->length);
if (tlv->length != 3) {
ogs_error("Invalid TLV length %d. It should be 3", tlv->length);
return OGS_ERROR;
}
v->u24 = ((((uint8_t*)tlv->value)[0]<<16)&0x00ff0000) |
@ -543,8 +543,8 @@ static int tlv_parse_leaf(void *msg, ogs_tlv_desc_t *desc, ogs_tlv_t *tlv)
{
ogs_tlv_uint32_t *v = (ogs_tlv_uint32_t *)msg;
if (tlv->length < 1 || tlv->length > 4) {
ogs_error("Invalid TLV length %d.", tlv->length);
if (tlv->length != 4) {
ogs_error("Invalid TLV length %d. It should be 4", tlv->length);
return OGS_ERROR;
}
v->u32 = ((((uint8_t*)tlv->value)[0]<<24)&0xff000000) |

View file

@ -38,7 +38,7 @@
*/
/*
* Copyright (C) 2019-2022 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2026 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
@ -78,125 +78,191 @@ static const unsigned char pr2six[256] =
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
int ogs_base64_decode_len(const char *bufcoded)
static bool ogs_base64_scan(
const char *in, size_t *out_nprbytes, size_t *out_decoded_size)
{
int nbytesdecoded;
register const unsigned char *bufin;
register size_t nprbytes;
const unsigned char *p = NULL;
size_t nprbytes = 0, padding = 0;
size_t decoded_size = 0;
bufin = (const unsigned char *) bufcoded;
while (pr2six[*(bufin++)] <= 63);
if (!in)
return false;
nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
nbytesdecoded = (((int)nprbytes + 3) / 4) * 3;
p = (const unsigned char *)in;
while (*p) {
if (pr2six[*p] <= 63) {
if (padding)
return false;
nprbytes++;
} else if (*p == '=') {
padding++;
if (padding > 2)
return false;
} else {
return false;
}
p++;
}
return nbytesdecoded + 1;
if (!nprbytes)
return false;
/* A single remaining base64 character cannot produce valid output. */
if (nprbytes % 4 == 1)
return false;
/* If padding exists, the encoded length including padding must be aligned. */
if (padding && ((nprbytes + padding) % 4))
return false;
decoded_size = ((nprbytes + 3) / 4) * 3;
decoded_size -= (4 - nprbytes) & 3;
if (!decoded_size)
return false;
if (out_nprbytes)
*out_nprbytes = nprbytes;
if (out_decoded_size)
*out_decoded_size = decoded_size;
return true;
}
int ogs_base64_decode(char *bufplain, const char *bufcoded)
int ogs_base64_decoded_size(const char *in)
{
int len;
len = ogs_base64_decode_binary((unsigned char *) bufplain, bufcoded);
bufplain[len] = '\0';
return len;
size_t decoded_size = 0;
if (!ogs_base64_scan(in, NULL, &decoded_size))
return OGS_ERROR;
return (int)decoded_size;
}
/* This is the same as ogs_base64_decode() except:
* - no \0 is appended
*/
int ogs_base64_decode_binary(unsigned char *bufplain, const char *bufcoded)
int ogs_base64_decode_to_buffer(
uint8_t *out, size_t out_size, const char *in)
{
int nbytesdecoded;
register const unsigned char *bufin;
register unsigned char *bufout;
register size_t nprbytes;
const unsigned char *bufin = NULL;
unsigned char *bufout = NULL;
size_t nprbytes = 0, decoded_size = 0;
bufin = (const unsigned char *) bufcoded;
while (pr2six[*(bufin++)] <= 63);
nprbytes = (bufin - (const unsigned char *) bufcoded) - 1;
nbytesdecoded = (((int)nprbytes + 3) / 4) * 3;
if (!out || !out_size)
return OGS_ERROR;
bufout = (unsigned char *) bufplain;
bufin = (const unsigned char *) bufcoded;
if (!ogs_base64_scan(in, &nprbytes, &decoded_size))
return OGS_ERROR;
if (out_size < decoded_size)
return OGS_ERROR;
bufout = out;
bufin = (const unsigned char *)in;
while (nprbytes > 4) {
*(bufout++) =
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
*(bufout++) =
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
*(bufout++) =
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
bufin += 4;
nprbytes -= 4;
*(bufout++) =
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
*(bufout++) =
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
*(bufout++) =
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
bufin += 4;
nprbytes -= 4;
}
/* Note: (nprbytes == 1) would be an error, so just ignore that case */
if (nprbytes > 1) {
*(bufout++) =
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
*(bufout++) =
(unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4);
}
if (nprbytes > 2) {
*(bufout++) =
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
*(bufout++) =
(unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2);
}
if (nprbytes > 3) {
*(bufout++) =
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
*(bufout++) =
(unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]);
}
nbytesdecoded -= (4 - (int)nprbytes) & 3;
return nbytesdecoded;
return (int)decoded_size;
}
int ogs_base64_decode_to_string(char *out, size_t out_size, const char *in)
{
int len;
if (!out || out_size <= 1)
return OGS_ERROR;
len = ogs_base64_decode_to_buffer((uint8_t *)out, out_size - 1, in);
if (len <= 0)
return OGS_ERROR;
out[len] = '\0';
return len;
}
static const char basis_64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int ogs_base64_encode_len(int len)
int ogs_base64_encoded_size(size_t in_size)
{
return ((len + 2) / 3 * 4) + 1;
size_t encoded_size;
if (in_size > (SIZE_MAX - 1) / 4 * 3)
return OGS_ERROR;
encoded_size = ((in_size + 2) / 3 * 4) + 1;
return (int)encoded_size;
}
int ogs_base64_encode(char *encoded, const char *string, int len)
int ogs_base64_encode_from_buffer(
char *out, size_t out_size, const uint8_t *in, size_t in_size)
{
return ogs_base64_encode_binary(
encoded, (const unsigned char *) string, len);
}
size_t i;
int needed;
char *p = NULL;
/* This is the same as ogs_base64_encode() except on EBCDIC machines, where
* the conversion of the input to ascii is left out.
*/
int ogs_base64_encode_binary(
char *encoded, const unsigned char *string, int len)
{
int i;
char *p;
if (!out || !out_size || (!in && in_size))
return OGS_ERROR;
p = encoded;
for (i = 0; i < len - 2; i += 3) {
*p++ = basis_64[(string[i] >> 2) & 0x3F];
*p++ = basis_64[((string[i] & 0x3) << 4) |
((int) (string[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((string[i + 1] & 0xF) << 2) |
((int) (string[i + 2] & 0xC0) >> 6)];
*p++ = basis_64[string[i + 2] & 0x3F];
needed = ogs_base64_encoded_size(in_size);
if (needed <= 0 || out_size < (size_t)needed)
return OGS_ERROR;
p = out;
for (i = 0; i + 2 < in_size; i += 3) {
*p++ = basis_64[(in[i] >> 2) & 0x3F];
*p++ = basis_64[((in[i] & 0x3) << 4) |
((int) (in[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((in[i + 1] & 0xF) << 2) |
((int) (in[i + 2] & 0xC0) >> 6)];
*p++ = basis_64[in[i + 2] & 0x3F];
}
if (i < len) {
*p++ = basis_64[(string[i] >> 2) & 0x3F];
if (i == (len - 1)) {
*p++ = basis_64[((string[i] & 0x3) << 4)];
*p++ = '=';
}
else {
*p++ = basis_64[((string[i] & 0x3) << 4) |
((int) (string[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((string[i + 1] & 0xF) << 2)];
}
*p++ = '=';
if (i < in_size) {
*p++ = basis_64[(in[i] >> 2) & 0x3F];
if (i == (in_size - 1)) {
*p++ = basis_64[((in[i] & 0x3) << 4)];
*p++ = '=';
} else {
*p++ = basis_64[((in[i] & 0x3) << 4) |
((int) (in[i + 1] & 0xF0) >> 4)];
*p++ = basis_64[((in[i + 1] & 0xF) << 2)];
}
*p++ = '=';
}
*p++ = '\0';
return (int)(p - encoded);
return (int)(p - out);
}
int ogs_base64_encode_from_string(
char *out, size_t out_size, const char *in)
{
if (!in)
return OGS_ERROR;
return ogs_base64_encode_from_buffer(
out, out_size, (const uint8_t *)in, strlen(in));
}
/* copies data to result but removes newlines and <CR>
@ -246,7 +312,7 @@ int ogs_fbase64_decode(const char *header,
static const char top[] = "-----BEGIN ";
static const char bottom[] = "-----END ";
uint8_t *rdata, *kdata;
int rdata_size;
int rdata_size, decoded_size;
#define MAX_PEM_HEADER_LEN 128
char pem_header[MAX_PEM_HEADER_LEN];
@ -323,18 +389,28 @@ int ogs_fbase64_decode(const char *header,
return OGS_ERROR;
}
result->data = ogs_calloc(1, bufcoded_len);
if (result->data == NULL) {
ogs_error("ogs_calloc() failed [%d]", bufcoded_len);
decoded_size = ogs_base64_decoded_size((const char *)bufcoded);
if (decoded_size <= 0) {
ogs_error("ogs_base64_decoded_size() failed");
ogs_log_hexdump(OGS_LOG_ERROR, bufcoded, bufcoded_len);
ogs_free(bufcoded);
return OGS_ERROR;
}
result->size = ogs_base64_decode_binary(
result->data, (const char *)bufcoded);
if (result->size == 0) {
ogs_error("ogs_base64_decode_binary() failed");
result->data = ogs_calloc(1, decoded_size);
if (result->data == NULL) {
ogs_error("ogs_calloc() failed [%d]", decoded_size);
ogs_free(bufcoded);
return OGS_ERROR;
}
result->size = ogs_base64_decode_to_buffer(
result->data, decoded_size, (const char *)bufcoded);
if (result->size <= 0) {
ogs_error("ogs_base64_decode_to_buffer() failed");
ogs_log_hexdump(OGS_LOG_ERROR, bufcoded, bufcoded_len);
ogs_free(result->data);
result->data = NULL;
ogs_free(bufcoded);
return OGS_ERROR;
}

View file

@ -37,7 +37,7 @@
*/
/*
* Copyright (C) 2019-2022 by Sukchan Lee <acetcom@gmail.com>
* Copyright (C) 2019-2026 by Sukchan Lee <acetcom@gmail.com>
*
* This file is part of Open5GS.
*
@ -65,16 +65,17 @@
extern "C" {
#endif
int ogs_base64_decode_len(const char *bufcoded);
int ogs_base64_decode(char *bufplain, const char *bufcoded);
int ogs_base64_decode_binary(
unsigned char *bufplain, const char *bufcoded);
int ogs_base64_decoded_size(const char *in);
int ogs_base64_decode_to_buffer(
uint8_t *out, size_t out_size, const char *in);
int ogs_base64_decode_to_string(
char *out, size_t out_size, const char *in);
int ogs_base64_encode_len(int len);
int ogs_base64_encode(
char *encoded, const char *string, int len);
int ogs_base64_encode_binary(
char *encoded, const unsigned char *string, int len);
int ogs_base64_encoded_size(size_t in_size);
int ogs_base64_encode_from_buffer(
char *out, size_t out_size, const uint8_t *in, size_t in_size);
int ogs_base64_encode_from_string(
char *out, size_t out_size, const char *in);
int ogs_fbase64_decode(const char *header,
const uint8_t *data, size_t data_size, ogs_datum_t *result);

View file

@ -133,10 +133,10 @@ int ogs_dbi_ims_data(char *supi, ogs_ims_data_t *ims_data)
memset(ims_data, 0, sizeof(*ims_data));
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
query = BCON_NEW(supi_type, BCON_UTF8(supi_id));
#if MONGOC_CHECK_VERSION(1, 5, 0)

View file

@ -45,10 +45,10 @@ int ogs_dbi_session_data(
ogs_assert(dnn);
ogs_assert(session_data);
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
query = BCON_NEW(supi_type, BCON_UTF8(supi_id));
#if MONGOC_CHECK_VERSION(1, 5, 0)

View file

@ -138,10 +138,10 @@ int ogs_dbi_update_sqn(char *supi, uint64_t sqn)
ogs_assert(supi);
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
query = BCON_NEW(supi_type, BCON_UTF8(supi_id));
update = BCON_NEW("$set",
@ -177,10 +177,10 @@ int ogs_dbi_update_imeisv(char *supi, char *imeisv)
ogs_assert(supi);
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
ogs_debug("SUPI type: %s, SUPI id: %s, imeisv: %s",
supi_type, supi_id, imeisv);
@ -219,10 +219,10 @@ int ogs_dbi_update_mme(char *supi, char *mme_host, char *mme_realm,
ogs_assert(supi);
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
ogs_debug("SUPI type: %s, SUPI id: %s, mme_host: %s, mme_realm: %s",
supi_type, supi_id, mme_host, mme_realm);
@ -264,10 +264,10 @@ int ogs_dbi_increment_sqn(char *supi)
ogs_assert(supi);
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
query = BCON_NEW(supi_type, BCON_UTF8(supi_id));
update = BCON_NEW("$inc",
@ -327,10 +327,10 @@ int ogs_dbi_subscription_data(char *supi,
memset(subscription_data, 0, sizeof(*subscription_data));
supi_type = ogs_id_get_type(supi);
ogs_assert(supi_type);
supi_id = ogs_id_get_value(supi);
ogs_assert(supi_id);
if (ogs_id_get_type_value(supi, &supi_type, &supi_id) == false) {
ogs_error("Invalid supi=%s", supi);
return OGS_ERROR;
}
query = BCON_NEW(supi_type, BCON_UTF8(supi_id));
#if MONGOC_CHECK_VERSION(1, 5, 0)

View file

@ -187,7 +187,8 @@ void ogs_gtp2_fill_header(
gtp_hlen = OGS_GTPV1U_HEADER_LEN+OGS_GTPV1U_EXTENSION_HEADER_LEN;
i = 0;
while(ext_hdesc->array[i].len) {
while (i < OGS_GTP2_NUM_OF_EXTENSION_HEADER &&
ext_hdesc->array[i].len) {
gtp_hlen += (ext_hdesc->array[i].len*4);
i++;
}
@ -243,6 +244,7 @@ void ogs_gtp2_fill_header(
i = 0;
while (i < OGS_GTP2_NUM_OF_EXTENSION_HEADER &&
ext_hdesc->array[i].len &&
(ext_h - pkbuf->data) < gtp_hlen) {
int len = ext_hdesc->array[i].len*4;
@ -250,7 +252,8 @@ void ogs_gtp2_fill_header(
memcpy(ext_h, &ext_hdesc->array[i].len, len-1);
/* Check if Next Header is Available */
if (ext_hdesc->array[i+1].len)
if ((i + 1) < OGS_GTP2_NUM_OF_EXTENSION_HEADER &&
ext_hdesc->array[i+1].len)
ext_h[len-1] = ext_hdesc->array[i+1].type;
else
ext_h[len-1] =

View file

@ -3281,26 +3281,65 @@ add_mactype(ipfw_insn *cmd, char *av, int cblen)
return NULL;
}
static int
ipfw_proto_by_name(const char *name)
{
struct protoent *pe;
ogs_assert(name);
pe = getprotobyname(name);
if (pe)
return pe->p_proto;
/*
* /etc/protocols lookup failed. Warn so the operator knows the
* environment is degraded, then try the built-in fallback.
* Protocol names follow lowercase 3GPP IPFilterRule convention
* (TS 29.212 / TS 29.514).
*/
ogs_error("getprotobyname('%s') failed; falling back to built-in table. "
"Check /etc/protocols (install netbase or iana-etc)", name);
/*
* SDF filters commonly use protocol names such as udp/tcp.
* Do not make rule compilation depend entirely on /etc/protocols
* or NSS, and never abort SMF on a lookup failure.
*/
if (strcmp(name, "tcp") == 0)
return IPPROTO_TCP;
if (strcmp(name, "udp") == 0)
return IPPROTO_UDP;
if (strcmp(name, "icmp") == 0)
return IPPROTO_ICMP;
#ifdef IPPROTO_ICMPV6
if (strcmp(name, "icmp6") == 0 ||
strcmp(name, "ipv6-icmp") == 0)
return IPPROTO_ICMPV6;
#endif
#ifdef IPPROTO_SCTP
if (strcmp(name, "sctp") == 0)
return IPPROTO_SCTP;
#endif
return -1;
}
static ipfw_insn *
add_proto0(ipfw_insn *cmd, char *av, u_char *protop)
{
struct protoent *pe;
char *ep;
int proto;
proto = strtol(av, &ep, 10);
if (*ep != '\0' || proto <= 0) {
#if 0 /* modified by acetcom */
if ((pe = getprotobyname(av)) == NULL)
proto = ipfw_proto_by_name(av);
if (proto < 0) {
ogs_error("Unknown protocol '%s' in flow description "
"(getprotobyname() failed; "
"check /etc/protocols)", av);
return NULL;
#else
if ((pe = getprotobyname(av)) == NULL) {
ogs_fatal("getprotobyname('%s') failed", av);
ogs_assert_if_reached();
return NULL;
}
#endif
proto = pe->p_proto;
}
}
fill_cmd(cmd, O_PROTO, 0, proto);

View file

@ -76,6 +76,10 @@ char *ogs_nas_5gs_suci_from_mobile_identity(
char *scheme_output_string_or_bcd = NULL;
ogs_assert(mobile_identity);
ogs_assert(mobile_identity->buffer);
ogs_assert(mobile_identity->length >=
(OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1));
mobile_identity_suci =
(ogs_nas_5gs_mobile_identity_suci_t *)mobile_identity->buffer;
@ -154,14 +158,9 @@ char *ogs_nas_5gs_suci_from_mobile_identity(
scheme_output =
(uint8_t *)mobile_identity->buffer +
OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE;
if (mobile_identity->length < OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE) {
ogs_error("The length of Mobile Identity(%d) is less then the min(%d)",
mobile_identity->length, OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE);
ogs_free(suci);
return NULL;
}
scheme_output_size =
mobile_identity->length - OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE;
ogs_assert(scheme_output_size);
scheme_output_string_or_bcd = ogs_calloc(1, scheme_output_size*2+1);
ogs_assert(scheme_output_string_or_bcd);

View file

@ -504,6 +504,12 @@ int ogs_nas_parse_qos_flow_descriptions(
size = 0;
while (size < length) {
if ((description - first) >=
OGS_NAS_MAX_NUM_OF_QOS_FLOW_DESCRIPTION) {
ogs_error("Too many QoS flow descriptions");
goto cleanup;
}
memset(description, 0, sizeof(*description));
if (size+3 > length) {
@ -513,8 +519,14 @@ int ogs_nas_parse_qos_flow_descriptions(
memcpy(description, buffer+size, 3);
size += 3;
for (i = 0; i < description->num_of_parameter &&
i < OGS_NAS_MAX_NUM_OF_QOS_FLOW_PARAMETER; i++) {
if (description->num_of_parameter >
OGS_NAS_MAX_NUM_OF_QOS_FLOW_PARAMETER) {
ogs_error("Too many QoS flow parameters [%d]",
description->num_of_parameter);
goto cleanup;
}
for (i = 0; i < description->num_of_parameter; i++) {
if (size+sizeof(description->param[i].identifier) > length) {
ogs_error("Overflow : size[%d] length[%d]", size, length);
goto cleanup;
@ -564,6 +576,12 @@ int ogs_nas_parse_qos_flow_descriptions(
buffer+size, description->param[i].len);
description->param[i].br.value =
be16toh(description->param[i].br.value);
if (!ogs_nas_bitrate_unit_is_valid(
&description->param[i].br)) {
ogs_error("Invalid QoS flow bitrate unit [%d]",
description->param[i].br.unit);
goto cleanup;
}
size += description->param[i].len;
break;
default:
@ -576,9 +594,10 @@ int ogs_nas_parse_qos_flow_descriptions(
description++;
}
cleanup:
return (int)(description-first);
cleanup:
return 0;
}
int ogs_nas_build_qos_rules(ogs_nas_qos_rules_t *rules,
@ -806,11 +825,44 @@ int ogs_nas_build_qos_rules(ogs_nas_qos_rules_t *rules,
}
/* Parse "Packet filter list", 3GPP TS 24.501 Figure 9.11.4.13.3 and Figure 9.11.4.13. */
static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const uint8_t *buffer, uint16_t length) {
static int parse_qos_rules_packet_filter_list(
ogs_nas_qos_rule_t *rule, const uint8_t *buffer, uint16_t length)
{
uint16_t size = 0;
int i, j, len = 0;
for (i = 0; i < rule->num_of_packet_filter && i < OGS_MAX_NUM_OF_FLOW_IN_GTP; i++) {
/*
* Check that 'needed' more bytes can be read into the current packet filter
* content without exceeding either the declared content length of this PF
* (rule->pf[i].content.length) or the overall buffer length.
*
* Both bounds are enforced. Previously only the buffer length was checked,
* which allowed a malformed PF declaring content.length=N to read past N
* into the next PF's bytes as long as the overall buffer was large enough.
*/
#define CHECK_PF_CONTENT_ROOM(needed) \
do { \
if (len + (int)(needed) > rule->pf[i].content.length || \
size + len + (needed) > length) { \
ogs_error("RuleId[%u] PF[%d] Overflow: " \
"size[%d] len[%d] need[%d] " \
"content[%d] length[%d]", \
rule->identifier, i, size, len, (int)(needed), \
rule->pf[i].content.length, length); \
goto cleanup; \
} \
} while (0)
ogs_assert(rule);
ogs_assert(buffer);
if (rule->num_of_packet_filter > OGS_MAX_NUM_OF_FLOW_IN_NAS) {
ogs_error("RuleId[%u] Too many packet filters [%d]",
rule->identifier, rule->num_of_packet_filter);
goto cleanup;
}
for (i = 0; i < rule->num_of_packet_filter; i++) {
if (size+sizeof(rule->pf[i].flags) > length) {
ogs_error("PF[%d] Overflow: size[%d] length[%d]", i, size, length);
goto cleanup;
@ -830,14 +882,23 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
sizeof(rule->pf[i].content.length));
size += sizeof(rule->pf[i].content.length);
j = 0; len = 0;
if (rule->pf[i].content.length == 0) {
ogs_error("RuleId[%u] PF[%d] Empty packet filter content",
rule->identifier, i);
goto cleanup;
}
j = 0;
len = 0;
while(len < rule->pf[i].content.length) {
if (size+len+
sizeof(rule->pf[i].content.component[j].type) > length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
if (j >= OGS_NAS_MAX_NUM_OF_PACKET_FILTER_COMPONENT) {
ogs_error("RuleId[%u] PF[%d] Too many packet filter "
"components", rule->identifier, i);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].type));
memcpy(&rule->pf[i].content.component[j].type,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].type));
@ -846,13 +907,8 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
case OGS_PACKET_FILTER_MATCH_ALL:
break;
case OGS_PACKET_FILTER_PROTOCOL_IDENTIFIER_NEXT_HEADER_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].proto) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].proto));
memcpy(&rule->pf[i].content.component[j].proto,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].proto));
@ -860,25 +916,15 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
break;
case OGS_PACKET_FILTER_IPV4_REMOTE_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV4_LOCAL_ADDRESS_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv4.addr));
memcpy(&rule->pf[i].content.component[j].ipv4.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.addr));
len += sizeof(rule->pf[i].content.component[j].ipv4.addr);
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv4.mask) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv4.mask));
memcpy(&rule->pf[i].content.component[j].ipv4.mask,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv4.mask));
@ -886,74 +932,40 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_PREFIX_LENGTH_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_PREFIX_LENGTH_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].ipv6.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv6.addr));
memcpy(&rule->pf[i].content.component[j].ipv6.addr,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].ipv6.addr));
len += sizeof(rule->pf[i].content.component[j].ipv6.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv6.prefixlen));
memcpy(&rule->pf[i].content.component[j].ipv6.prefixlen,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen));
len += sizeof(
rule->pf[i].content.component[j].ipv6.prefixlen);
sizeof(rule->pf[i].content.component[j].ipv6.prefixlen));
len += sizeof(rule->pf[i].content.component[j].ipv6.prefixlen);
break;
case OGS_PACKET_FILTER_IPV6_LOCAL_ADDRESS_TYPE:
case OGS_PACKET_FILTER_IPV6_REMOTE_ADDRESS_TYPE:
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv6_mask.addr));
memcpy(&rule->pf[i].content.component[j].ipv6_mask.addr,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.addr);
sizeof(rule->pf[i].content.component[j].ipv6_mask.addr));
len += sizeof(rule->pf[i].content.component[j].ipv6_mask.addr);
if (size+len+
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].ipv6_mask.mask));
memcpy(&rule->pf[i].content.component[j].ipv6_mask.mask,
buffer+size+len,
sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask));
len += sizeof(
rule->pf[i].content.component[j].ipv6_mask.mask);
sizeof(rule->pf[i].content.component[j].ipv6_mask.mask));
len += sizeof(rule->pf[i].content.component[j].ipv6_mask.mask);
break;
case OGS_PACKET_FILTER_SINGLE_LOCAL_PORT_TYPE:
case OGS_PACKET_FILTER_SINGLE_REMOTE_PORT_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].port.low));
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
@ -963,13 +975,8 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
break;
case OGS_PACKET_FILTER_LOCAL_PORT_RANGE_TYPE:
case OGS_PACKET_FILTER_REMOTE_PORT_RANGE_TYPE:
if (size+len+
sizeof(rule->pf[i].content.component[j].port.low) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].port.low));
memcpy(&rule->pf[i].content.component[j].port.low,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.low));
@ -977,13 +984,8 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
be16toh(rule->pf[i].content.component[j].port.low);
len += sizeof(rule->pf[i].content.component[j].port.low);
if (size+len+
sizeof(rule->pf[i].content.component[j].port.high) >
length) {
ogs_error("PF[%d] Overflow: size[%d] len[%d] length[%d]",
i, size, len, length);
goto cleanup;
}
CHECK_PF_CONTENT_ROOM(
sizeof(rule->pf[i].content.component[j].port.high));
memcpy(&rule->pf[i].content.component[j].port.high,
buffer+size+len,
sizeof(rule->pf[i].content.component[j].port.high));
@ -1001,8 +1003,13 @@ static int parse_qos_rules_packet_filter_list(ogs_nas_qos_rule_t *rule, const ui
rule->pf[i].content.num_of_component = j;
size += len;
}
#undef CHECK_PF_CONTENT_ROOM
return size;
cleanup:
#undef CHECK_PF_CONTENT_ROOM
return -1;
}
@ -1034,6 +1041,12 @@ int ogs_nas_parse_qos_rules(
while (size < length) {
bool have_octet_m1, have_octet_m2;
if ((rule - first) >= OGS_NAS_MAX_NUM_OF_QOS_RULE) {
ogs_error("Too many QoS rules");
goto cleanup;
}
memset(rule, 0, sizeof(*rule));
if (size+sizeof(rule->identifier) > length) {
@ -1073,7 +1086,6 @@ int ogs_nas_parse_qos_rules(
if (rule->num_of_packet_filter != 0) {
ogs_error("RuleId[%u] Invalid QoS rule code[%d] and number of packet filter[%d]",
rule->identifier, rule->code, rule->num_of_packet_filter);
rule->num_of_packet_filter = 0;
goto cleanup;
}
}
@ -1149,7 +1161,8 @@ int ogs_nas_parse_qos_rules(
rule++;
}
cleanup:
return (int)(rule-first);
cleanup:
return 0;
}

View file

@ -444,10 +444,23 @@ void ogs_nas_bitrate_from_uint64(ogs_nas_bitrate_t *nas, uint64_t bitrate)
nas->value = bitrate;
}
bool ogs_nas_bitrate_unit_is_valid(ogs_nas_bitrate_t *nas_bitrate)
{
ogs_assert(nas_bitrate);
return nas_bitrate->unit >= OGS_NAS_BR_UNIT_1K &&
nas_bitrate->unit <= OGS_NAS_BR_UNIT_256P;
}
uint64_t ogs_nas_bitrate_to_uint64(ogs_nas_bitrate_t *nas_bitrate)
{
ogs_assert(nas_bitrate);
if (!ogs_nas_bitrate_unit_is_valid(nas_bitrate)) {
ogs_error("Invalid bitrate unit [%d]", nas_bitrate->unit);
return 0;
}
if (nas_bitrate->unit == OGS_NAS_BR_UNIT_1K)
return nas_bitrate->value * 1000;
if (nas_bitrate->unit == OGS_NAS_BR_UNIT_4K)
@ -499,8 +512,5 @@ uint64_t ogs_nas_bitrate_to_uint64(ogs_nas_bitrate_t *nas_bitrate)
if (nas_bitrate->unit == OGS_NAS_BR_UNIT_256P)
return nas_bitrate->value * 256 * 1000 * 1000 * 1000 * 1000 * 1000;
ogs_fatal("Unknown unit [%d]", nas_bitrate->unit);
ogs_assert_if_reached();
return 0;
}

View file

@ -979,6 +979,7 @@ typedef struct ogs_nas_bitrate_s {
void ogs_nas_bitrate_from_uint64(
ogs_nas_bitrate_t *nas_bitrate, uint64_t bitrate);
bool ogs_nas_bitrate_unit_is_valid(ogs_nas_bitrate_t *nas_bitrate);
uint64_t ogs_nas_bitrate_to_uint64(ogs_nas_bitrate_t *nas_bitrate);
typedef struct ogs_nas_session_ambr_s {

View file

@ -544,7 +544,8 @@ ogs_pfcp_pdr_t *ogs_pfcp_handle_create_pdr(ogs_pfcp_sess_t *sess,
ogs_pfcp_rule_remove_all(pdr);
for (i = 0; i < OGS_MAX_NUM_OF_FLOW_IN_PDR; i++) {
for (i = 0; i < ogs_min(OGS_ARRAY_SIZE(message->pdi.sdf_filter),
OGS_MAX_NUM_OF_FLOW_IN_PDR); i++) {
ogs_pfcp_sdf_filter_t sdf_filter;
ogs_pfcp_rule_t *rule = NULL;
ogs_pfcp_rule_t *oppsite_direction_rule = NULL;
@ -933,7 +934,8 @@ ogs_pfcp_pdr_t *ogs_pfcp_handle_update_pdr(ogs_pfcp_sess_t *sess,
ogs_pfcp_rule_remove_all(pdr);
for (i = 0; i < OGS_MAX_NUM_OF_FLOW_IN_PDR; i++) {
for (i = 0; i < ogs_min(OGS_ARRAY_SIZE(message->pdi.sdf_filter),
OGS_MAX_NUM_OF_FLOW_IN_PDR); i++) {
ogs_pfcp_sdf_filter_t sdf_filter;
ogs_pfcp_rule_t *rule = NULL;
ogs_pfcp_rule_t *oppsite_direction_rule = NULL;
@ -1189,8 +1191,15 @@ ogs_pfcp_far_t *ogs_pfcp_handle_create_far(ogs_pfcp_sess_t *sess,
ogs_pfcp_tlv_outer_header_creation_t *outer_header_creation =
&message->forwarding_parameters.outer_header_creation;
ogs_assert(outer_header_creation->data);
ogs_assert(outer_header_creation->len);
if (!outer_header_creation->data ||
outer_header_creation->len == 0) {
ogs_error("Invalid Outer Header Creation [data:%p,len:%d]",
outer_header_creation->data,
outer_header_creation->len);
*cause_value = OGS_PFCP_CAUSE_INVALID_LENGTH;
*offending_ie_value = OGS_PFCP_OUTER_HEADER_CREATION_TYPE;
return NULL;
}
memcpy(&far->outer_header_creation, outer_header_creation->data,
ogs_min(sizeof(far->outer_header_creation),
@ -1307,8 +1316,15 @@ ogs_pfcp_far_t *ogs_pfcp_handle_update_far(ogs_pfcp_sess_t *sess,
ogs_pfcp_tlv_outer_header_creation_t *outer_header_creation =
&message->update_forwarding_parameters.outer_header_creation;
ogs_assert(outer_header_creation->data);
ogs_assert(outer_header_creation->len);
if (!outer_header_creation->data ||
outer_header_creation->len == 0) {
ogs_error("Invalid Outer Header Creation [data:%p,len:%d]",
outer_header_creation->data,
outer_header_creation->len);
*cause_value = OGS_PFCP_CAUSE_INVALID_LENGTH;
*offending_ie_value = OGS_PFCP_OUTER_HEADER_CREATION_TYPE;
return NULL;
}
memcpy(&far->outer_header_creation, outer_header_creation->data,
ogs_min(sizeof(far->outer_header_creation),

View file

@ -35,49 +35,146 @@
#include <netinet/tcp.h>
#endif
static int decode_ipv6_header(
struct ip6_hdr *ip6_h, uint8_t *proto, uint16_t *hlen)
static int decode_ipv6_header(struct ip6_hdr *ip6_h, size_t buflen,
uint8_t *proto, uint16_t *hlen, size_t *plen)
{
int done = 0;
uint8_t *p, *jp, *endp;
uint8_t *start, *p, *endp, *bufend;
uint8_t nxt; /* Next Header */
ogs_assert(ip6_h);
ogs_assert(proto);
ogs_assert(hlen);
ogs_assert(plen);
if (buflen < sizeof(*ip6_h)) {
ogs_error("Invalid IPv6 packet: too short [len:%zu]", buflen);
return OGS_ERROR;
}
start = (uint8_t *)ip6_h;
bufend = start + buflen;
nxt = ip6_h->ip6_nxt;
p = (uint8_t *)ip6_h + sizeof(*ip6_h);
endp = p + be16toh(ip6_h->ip6_plen);
p = start + sizeof(*ip6_h);
jp = p + sizeof(struct ip6_hbh);
while (p == endp) { /* Jumbo Frame */
uint32_t jp_len = 0;
struct ip6_opt_jumbo *jumbo = NULL;
if (be16toh(ip6_h->ip6_plen) == 0) {
uint8_t *jp, *optend;
size_t ext_len;
int jumbo_found = 0;
struct ip6_ext *ext = NULL;
if (nxt != 0) {
ogs_error("Invalid IPv6 jumbo: plen=0 but NextHeader=%u", nxt);
return OGS_ERROR; /* Drop packet safely */
if (nxt != IPPROTO_HOPOPTS) {
ogs_error("Invalid IPv6 jumbo packet: missing hop-by-hop header "
"[nxt:%u len:%zu]", nxt, buflen);
return OGS_ERROR;
}
jumbo = (struct ip6_opt_jumbo *)jp;
memcpy(&jp_len, jumbo->ip6oj_jumbo_len, sizeof(jp_len));
jp_len = be32toh(jp_len);
switch (jumbo->ip6oj_type) {
case IP6OPT_JUMBO:
endp = p + jp_len;
break;
case 0:
jp++;
break;
default:
jp += (sizeof(struct ip6_opt) + jp_len);
break;
if ((size_t)(bufend - p) < sizeof(struct ip6_ext)) {
ogs_error("Invalid IPv6 jumbo packet: truncated hop-by-hop "
"header [remaining:%zu len:%zu]",
(size_t)(bufend - p), buflen);
return OGS_ERROR;
}
ext = (struct ip6_ext *)p;
ext_len = ((size_t)ext->ip6e_len << 3) + 8;
if (ext_len > (size_t)(bufend - p)) {
ogs_error("Invalid IPv6 jumbo packet: hop-by-hop header exceeds "
"packet [ext_len:%zu remaining:%zu len:%zu]",
ext_len, (size_t)(bufend - p), buflen);
return OGS_ERROR;
}
jp = p + sizeof(struct ip6_hbh);
optend = p + ext_len;
while (jp < optend) {
uint8_t opt_type = *jp;
struct ip6_opt *opt = NULL;
size_t opt_len;
if (opt_type == 0) { /* Pad1 */
jp++;
continue;
}
if ((size_t)(optend - jp) < sizeof(struct ip6_opt)) {
ogs_error("Invalid IPv6 jumbo packet: truncated option "
"[remaining:%zu len:%zu]",
(size_t)(optend - jp), buflen);
return OGS_ERROR;
}
opt = (struct ip6_opt *)jp;
opt_len = sizeof(struct ip6_opt) + opt->ip6o_len;
if (opt_len > (size_t)(optend - jp)) {
ogs_error("Invalid IPv6 jumbo packet: option exceeds "
"hop-by-hop header [type:%u opt_len:%zu "
"remaining:%zu len:%zu]",
opt->ip6o_type, opt_len, (size_t)(optend - jp),
buflen);
return OGS_ERROR;
}
if (opt->ip6o_type == IP6OPT_JUMBO) {
uint32_t jp_len = 0;
struct ip6_opt_jumbo *jumbo = NULL;
if (opt->ip6o_len != sizeof(jp_len)) {
ogs_error("Invalid IPv6 jumbo packet: invalid jumbo "
"option length [opt_len:%u len:%zu]",
opt->ip6o_len, buflen);
return OGS_ERROR;
}
jumbo = (struct ip6_opt_jumbo *)jp;
memcpy(&jp_len, jumbo->ip6oj_jumbo_len, sizeof(jp_len));
jp_len = be32toh(jp_len);
if (jp_len <= 0xffff) {
ogs_error("Invalid IPv6 jumbo packet: jumbo payload "
"length is too small [jumbo_len:%u len:%zu]",
jp_len, buflen);
return OGS_ERROR;
}
if (jp_len > (size_t)(bufend - p)) {
ogs_error("Invalid IPv6 jumbo packet: jumbo payload "
"exceeds packet [jumbo_len:%u remaining:%zu "
"len:%zu]", jp_len, (size_t)(bufend - p),
buflen);
return OGS_ERROR;
}
endp = p + jp_len;
jumbo_found = 1;
break;
}
jp += opt_len;
}
if (!jumbo_found) {
ogs_error("Invalid IPv6 jumbo packet: jumbo option not found "
"[len:%zu]", buflen);
return OGS_ERROR;
}
} else {
size_t payload_len = be16toh(ip6_h->ip6_plen);
if (payload_len > (size_t)(bufend - p)) {
ogs_error("Invalid IPv6 packet: payload length exceeds packet "
"[payload_len:%zu remaining:%zu len:%zu]",
payload_len, (size_t)(bufend - p), buflen);
return OGS_ERROR;
}
endp = p + payload_len;
}
while (p < endp) {
struct ip6_ext *ext = (struct ip6_ext *)p;
struct ip6_ext *ext = NULL;
size_t ext_len;
switch (nxt) {
case IPPROTO_HOPOPTS:
case IPPROTO_ROUTING:
@ -87,27 +184,68 @@ static int decode_ipv6_header(
case 140: /* shim6 */
case 253: /* testing, experimental */
case 254: /* testing, experimental */
p += ((ext->ip6e_len << 3) + 8);
if ((size_t)(endp - p) < sizeof(struct ip6_ext)) {
ogs_error("Invalid IPv6 packet: truncated extension header "
"[nxt:%u remaining:%zu len:%zu]",
nxt, (size_t)(endp - p), buflen);
return OGS_ERROR;
}
ext = (struct ip6_ext *)p;
ext_len = ((size_t)ext->ip6e_len << 3) + 8;
if (ext_len > (size_t)(endp - p)) {
ogs_error("Invalid IPv6 packet: extension header exceeds "
"payload [nxt:%u ext_len:%zu remaining:%zu "
"len:%zu]", nxt, ext_len, (size_t)(endp - p),
buflen);
return OGS_ERROR;
}
nxt = ext->ip6e_nxt;
p += ext_len;
break;
case IPPROTO_FRAGMENT:
if ((size_t)(endp - p) < sizeof(struct ip6_frag)) {
ogs_error("Invalid IPv6 packet: truncated fragment header "
"[remaining:%zu len:%zu]",
(size_t)(endp - p), buflen);
return OGS_ERROR;
}
ext = (struct ip6_ext *)p;
nxt = ext->ip6e_nxt;
p += sizeof(struct ip6_frag);
break;
case IPPROTO_AH:
p += ((ext->ip6e_len + 2) << 2);
if ((size_t)(endp - p) < sizeof(struct ip6_ext)) {
ogs_error("Invalid IPv6 packet: truncated AH header "
"[remaining:%zu len:%zu]",
(size_t)(endp - p), buflen);
return OGS_ERROR;
}
ext = (struct ip6_ext *)p;
ext_len = ((size_t)ext->ip6e_len + 2) << 2;
if (ext_len > (size_t)(endp - p)) {
ogs_error("Invalid IPv6 packet: AH header exceeds payload "
"[ext_len:%zu remaining:%zu len:%zu]",
ext_len, (size_t)(endp - p), buflen);
return OGS_ERROR;
}
nxt = ext->ip6e_nxt;
p += ext_len;
break;
default: /* Upper Layer */
done = 1;
break;
goto done;
}
if (done)
break;
}
nxt = ext->ip6e_nxt;
done:
if ((size_t)(p - start) > 0xffff) {
ogs_error("Invalid IPv6 packet: header length too large "
"[hlen:%zu len:%zu]", (size_t)(p - start), buflen);
return OGS_ERROR;
}
*proto = nxt;
*hlen = p - (uint8_t *)ip6_h;
*hlen = p - start;
*plen = endp - start;
return OGS_OK;
}
@ -117,11 +255,13 @@ ogs_pfcp_rule_t *ogs_pfcp_pdr_rule_find_by_packet(
{
struct ip *ip_h = NULL;
struct ip6_hdr *ip6_h = NULL;
uint32_t *src_addr = NULL;
uint32_t *dst_addr = NULL;
uint32_t src_addr[4] = {0, };
uint32_t dst_addr[4] = {0, };
int addr_len = 0;
uint8_t proto = 0;
uint16_t ip_hlen = 0;
size_t pkt_len = 0;
uint8_t ip_v;
ogs_pfcp_rule_t *rule = NULL;
@ -129,6 +269,77 @@ ogs_pfcp_rule_t *ogs_pfcp_pdr_rule_find_by_packet(
ogs_assert(pkbuf->len);
ogs_assert(pkbuf->data);
pkt_len = pkbuf->len;
ip_v = (*(uint8_t *)pkbuf->data) >> 4;
if (ip_v == 4) {
size_t ip_len;
if (pkt_len < sizeof(struct ip)) {
ogs_error("Invalid IPv4 packet: too short [len:%zu]", pkt_len);
return NULL;
}
ip_h = (struct ip *)pkbuf->data;
ip_hlen = ip_h->ip_hl * 4;
ip_len = be16toh(ip_h->ip_len);
if (ip_hlen < sizeof(struct ip) || ip_hlen > ip_len) {
ogs_error("Invalid IPv4 packet: invalid header length "
"[hlen:%u ip_len:%zu len:%zu]",
ip_hlen, ip_len, pkt_len);
return NULL;
}
if (ip_len > pkt_len) {
ogs_error("Invalid IPv4 packet: total length exceeds packet "
"[ip_len:%zu len:%zu]", ip_len, pkt_len);
return NULL;
}
pkt_len = ip_len;
proto = ip_h->ip_p;
src_addr[0] = ip_h->ip_src.s_addr;
dst_addr[0] = ip_h->ip_dst.s_addr;
addr_len = OGS_IPV4_LEN;
} else if (ip_v == 6) {
if (pkt_len < sizeof(struct ip6_hdr)) {
ogs_error("Invalid IPv6 packet: too short [len:%zu]", pkt_len);
return NULL;
}
ip6_h = (struct ip6_hdr *)pkbuf->data;
if (OGS_OK != decode_ipv6_header(ip6_h, pkt_len,
&proto, &ip_hlen, &pkt_len))
return NULL;
memcpy(src_addr, ip6_h->ip6_src.s6_addr, OGS_IPV6_LEN);
memcpy(dst_addr, ip6_h->ip6_dst.s6_addr, OGS_IPV6_LEN);
addr_len = OGS_IPV6_LEN;
} else {
ogs_error("Invalid IP packet: unsupported version "
"[version:%u len:%zu]", ip_v, pkt_len);
return NULL;
}
if (proto == IPPROTO_TCP) {
if ((size_t)ip_hlen > pkt_len ||
pkt_len - ip_hlen < sizeof(struct tcphdr)) {
ogs_error("Invalid TCP packet: truncated header "
"[hlen:%u len:%zu]", ip_hlen, pkt_len);
return NULL;
}
} else if (proto == IPPROTO_UDP) {
if ((size_t)ip_hlen > pkt_len ||
pkt_len - ip_hlen < sizeof(struct udphdr)) {
ogs_error("Invalid UDP packet: truncated header "
"[hlen:%u len:%zu]", ip_hlen, pkt_len);
return NULL;
}
}
ogs_list_for_each(&pdr->rule_list, rule) {
int k;
uint32_t src_mask[4];
@ -138,39 +349,6 @@ ogs_pfcp_rule_t *ogs_pfcp_pdr_rule_find_by_packet(
ipfw = &rule->ipfw;
ogs_assert(ipfw);
ip_h = (struct ip *)pkbuf->data;
if (ip_h->ip_v == 4) {
ip_h = (struct ip *)pkbuf->data;
ip6_h = NULL;
proto = ip_h->ip_p;
ip_hlen = (ip_h->ip_hl)*4;
src_addr = (void *)&ip_h->ip_src.s_addr;
dst_addr = (void *)&ip_h->ip_dst.s_addr;
addr_len = OGS_IPV4_LEN;
} else if (ip_h->ip_v == 6) {
ip_h = NULL;
ip6_h = (struct ip6_hdr *)pkbuf->data;
if (OGS_OK != decode_ipv6_header(ip6_h, &proto, &ip_hlen)) {
/* Drop malformed IPv6 packet gracefully */
ogs_error("Malformed IPv6 packet while matching PDR [plen:%d]",
pkbuf->len);
ogs_log_hexdump(OGS_LOG_ERROR, pkbuf->data, pkbuf->len);
continue;
}
src_addr = (void *)ip6_h->ip6_src.s6_addr;
dst_addr = (void *)ip6_h->ip6_dst.s6_addr;
addr_len = OGS_IPV6_LEN;
} else {
ogs_error("Invalid packet [IP version:%d, Packet Length:%d]",
ip_h->ip_v, pkbuf->len);
ogs_log_hexdump(OGS_LOG_ERROR, pkbuf->data, pkbuf->len);
continue;
}
ogs_trace("PROTO:%d SRC:%08x %08x %08x %08x",
proto, be32toh(src_addr[0]), be32toh(src_addr[1]),
be32toh(src_addr[2]), be32toh(src_addr[3]));

View file

@ -365,6 +365,129 @@ cleanup:
return ueid;
}
bool ogs_id_get_type_value(const char *str, char **type, char **value)
{
ogs_assert(str);
ogs_assert(type);
ogs_assert(value);
*type = ogs_id_get_type(str);
*value = ogs_id_get_value(str);
if (!*type || !*value || !strlen(*type) || !strlen(*value))
goto cleanup;
/* Reject extra '-' components that ogs_id_get_value() ignores. */
if (strlen(str) != strlen(*type) + 1 + strlen(*value))
goto cleanup;
return true;
cleanup:
if (*type) {
ogs_free(*type);
*type = NULL;
}
if (*value) {
ogs_free(*value);
*value = NULL;
}
return false;
}
bool ogs_bcd_string_is_valid(const char *bcd, int max_len)
{
int i, len;
ogs_assert(bcd);
ogs_assert(max_len > 0);
len = strlen(bcd);
if (len == 0 || len > max_len) {
ogs_error("Invalid BCD length [%d:%s]", len, bcd);
return false;
}
for (i = 0; i < len; i++) {
if (bcd[i] < '0' || bcd[i] > '9') {
ogs_error("Invalid BCD digit [%d:%c:%s]", i, bcd[i], bcd);
return false;
}
}
return true;
}
bool ogs_pdu_session_id_is_valid(int psi)
{
return psi > OGS_NAS_PDU_SESSION_IDENTITY_UNASSIGNED &&
psi <= OGS_NAS_PDU_SESSION_IDENTITY_MAX;
}
/*
* ogs_bcd_to_buffer() only converts bytes. It does not validate that
* the input is a bounded decimal IMSI string, so check it first.
*/
bool ogs_imsi_bcd_is_valid(const char *imsi_bcd)
{
return ogs_bcd_string_is_valid(imsi_bcd, OGS_MAX_IMSI_BCD_LEN);
}
bool ogs_imeisv_bcd_is_valid(const char *imeisv_bcd)
{
return ogs_bcd_string_is_valid(imeisv_bcd, OGS_MAX_IMEISV_BCD_LEN);
}
/*
* Return the decimal IMSI string only for IMSI-type SUPI. Other SUPI
* types cannot be used as EPC IMSI keys and remain SUPI-only.
*
* Return OGS_ERROR only when the SUPI claims to be IMSI-type but the
* IMSI payload is malformed. That must not fall back to SUPI-only.
*/
int ogs_supi_to_imsi_bcd(
const char *supi, char *imsi_bcd, bool *imsi_supi)
{
int rv = OGS_ERROR;
char *type = NULL;
char *value = NULL;
ogs_assert(supi);
ogs_assert(imsi_bcd);
ogs_assert(imsi_supi);
*imsi_supi = false;
if (ogs_id_get_type_value(supi, &type, &value) == false) {
ogs_error("Invalid SUPI [%s]", supi);
goto cleanup;
}
/* Non-IMSI SUPI is valid, but has no EPC IMSI alias. */
if (strcmp(type, OGS_ID_SUPI_TYPE_IMSI) != 0) {
rv = OGS_OK;
goto cleanup;
}
if (ogs_imsi_bcd_is_valid(value) == false) {
ogs_error("Invalid IMSI SUPI [%s]", supi);
goto cleanup;
}
ogs_cpystrn(imsi_bcd, value, OGS_MAX_IMSI_BCD_LEN+1);
*imsi_supi = true;
rv = OGS_OK;
cleanup:
if (type)
ogs_free(type);
if (value)
ogs_free(value);
return rv;
}
char *ogs_s_nssai_sd_to_string(const ogs_uint24_t sd)
{
char *string = NULL;
@ -918,6 +1041,11 @@ static int flow_rx_to_gx(ogs_flow_t *rx_flow, ogs_flow_t *gx_flow)
ogs_assert(rx_flow);
ogs_assert(gx_flow);
if (!rx_flow->description) {
ogs_error("No Flow Description");
return OGS_ERROR;
}
if (!strncmp(rx_flow->description,
"permit out", strlen("permit out"))) {
gx_flow->direction = OGS_FLOW_DOWNLINK_ONLY;
@ -934,10 +1062,35 @@ static int flow_rx_to_gx(ogs_flow_t *rx_flow, ogs_flow_t *gx_flow)
gx_flow->description = ogs_calloc(1, len);
ogs_assert(gx_flow->description);
strcpy(gx_flow->description, "permit out");
from_str = strstr(&rx_flow->description[strlen("permit in")], "from");
ogs_assert(from_str);
to_str = strstr(&rx_flow->description[strlen("permit in")], "to");
ogs_assert(to_str);
/*
* Match "from" and "to" as space-padded tokens, and ensure
* "to" follows "from" before using pointer-length arithmetic.
*/
from_str = strstr(&rx_flow->description[strlen("permit in")],
" from ");
if (!from_str) {
ogs_error("Invalid Flow Description : [%s] (missing 'from')",
rx_flow->description);
ogs_free(gx_flow->description);
gx_flow->description = NULL;
return OGS_ERROR;
}
to_str = strstr(&rx_flow->description[strlen("permit in")],
" to ");
if (!to_str || to_str <= from_str) {
ogs_error("Invalid Flow Description : [%s] "
"(missing 'to' or 'to' precedes 'from')",
rx_flow->description);
ogs_free(gx_flow->description);
gx_flow->description = NULL;
return OGS_ERROR;
}
from_str++;
to_str++;
strncat(gx_flow->description,
&rx_flow->description[strlen("permit in")],
strlen(rx_flow->description) -

View file

@ -149,6 +149,9 @@ extern "C" {
#define OGS_NAS_PROCEDURE_TRANSACTION_IDENTITY_UNASSIGNED 0
/* 3GPP TS 24.007 Table 11.2.3.1c.1: */
#define OGS_NAS_PDU_SESSION_IDENTITY_UNASSIGNED 0
#define OGS_NAS_PDU_SESSION_IDENTITY_MAX 255
bool ogs_pdu_session_id_is_valid(int psi);
#define OGS_ACCESS_TYPE_3GPP 1
#define OGS_ACCESS_TYPE_NON_3GPP 2
@ -308,6 +311,12 @@ ogs_amf_id_t *ogs_amf_id_build(ogs_amf_id_t *amf_id,
#define OGS_ID_5G_GUTI_TYPE "5g-guti"
char *ogs_id_get_type(const char *str);
char *ogs_id_get_value(const char *str);
bool ogs_id_get_type_value(const char *str, char **type, char **value);
bool ogs_bcd_string_is_valid(const char *bcd, int max_len);
bool ogs_imsi_bcd_is_valid(const char *imsi_bcd);
bool ogs_imeisv_bcd_is_valid(const char *imeisv_bcd);
int ogs_supi_to_imsi_bcd(
const char *supi, char *imsi_bcd, bool *imsi_supi);
/************************************
* TAI Structure */

View file

@ -834,6 +834,11 @@ bool ogs_sbi_client_send_via_scp_or_sepp(
ogs_assert(apiroot);
rc = ogs_sbi_getpath_from_uri(&path, request->h.uri);
if (rc == false) {
ogs_error("Cannot extract path from URI [%s]", request->h.uri);
ogs_free(apiroot);
return false;
}
ogs_assert(path);
request->h.uri = ogs_msprintf("%s/%s", apiroot, path);

View file

@ -1240,7 +1240,11 @@ ogs_sbi_nf_instance_t *ogs_sbi_nf_instance_add(void)
ogs_sbi_nf_instance_t *nf_instance = NULL;
ogs_pool_alloc(&nf_instance_pool, &nf_instance);
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("OVERFLOW nf_instance_pool [pool:%llu]",
(unsigned long long)ogs_app()->pool.nf);
return NULL;
}
memset(nf_instance, 0, sizeof(ogs_sbi_nf_instance_t));
nf_instance->time.heartbeat_interval =
@ -1346,6 +1350,9 @@ void ogs_sbi_nf_instance_clear(ogs_sbi_nf_instance_t *nf_instance)
nf_instance->num_of_ipv6 = 0;
nf_instance->num_of_allowed_nf_type = 0;
nf_instance->num_of_s_nssai = 0;
nf_instance->num_of_allowed_nssai = 0;
}
void ogs_sbi_nf_instance_remove(ogs_sbi_nf_instance_t *nf_instance)
@ -1462,10 +1469,6 @@ ogs_sbi_nf_instance_t *ogs_sbi_nf_instance_find_by_service(
return nf_instance;
}
bool ogs_sbi_nf_instance_maximum_number_is_reached(void)
{
return nf_instance_pool.avail <= 0;
}
ogs_sbi_nf_service_t *ogs_sbi_nf_service_add(
ogs_sbi_nf_instance_t *nf_instance,
@ -1480,12 +1483,26 @@ ogs_sbi_nf_service_t *ogs_sbi_nf_service_add(
ogs_assert(name);
ogs_pool_alloc(&nf_service_pool, &nf_service);
ogs_assert(nf_service);
if (!nf_service) {
ogs_error("OVERFLOW nf_service_pool [pool:%llu]",
(unsigned long long)ogs_app()->pool.nf_service);
return NULL;
}
memset(nf_service, 0, sizeof(ogs_sbi_nf_service_t));
nf_service->id = ogs_strdup(id);
ogs_assert(nf_service->id);
if (!nf_service->id) {
ogs_error("ogs_strdup() failed for nf_service->id");
ogs_pool_free(&nf_service_pool, nf_service);
return NULL;
}
nf_service->name = name;
if (!nf_service->name) {
ogs_error("ogs_strdup() failed for nf_service->name");
ogs_free(nf_service->id);
ogs_pool_free(&nf_service_pool, nf_service);
return NULL;
}
nf_service->scheme = scheme;
ogs_assert(nf_service->scheme);
@ -1673,7 +1690,9 @@ ogs_sbi_nf_info_t *ogs_sbi_nf_info_add(
ogs_pool_alloc(&nf_info_pool, &nf_info);
if (!nf_info) {
ogs_fatal("ogs_pool_alloc() failed");
ogs_error("OVERFLOW nf_info_pool [pool:%llu]",
(unsigned long long)(ogs_app()->pool.nf *
OGS_MAX_NUM_OF_NF_INFO));
return NULL;
}
memset(nf_info, 0, sizeof(*nf_info));
@ -1934,7 +1953,15 @@ ogs_sbi_nf_service_t *ogs_sbi_nf_service_build_default(
ogs_assert(scheme);
nf_service = ogs_sbi_nf_service_add(nf_instance, id, name, scheme);
ogs_assert(nf_service);
if (!nf_service) {
ogs_error("Cannot build default NF service [%s]: "
"nf_service_pool exhausted at startup. "
"Increase 'max.peer' (current pool capacity = "
"max.peer * 16 = %llu).",
OpenAPI_service_name_ToString(name),
(unsigned long long)ogs_app()->pool.nf_service);
return NULL;
}
hostname = NULL;
for (server = ogs_sbi_server_first();
@ -2166,6 +2193,34 @@ bool ogs_sbi_discovery_option_is_matched(
nf_instance, discovery_option) == false)
return false;
/*
* TS 33.518 4.2.2.2.1 - Target allowed_nssai filtering
*
* If the target NF registered with allowedNssais, it may only be
* discovered for queries whose S-NSSAI falls within that set.
*/
if (nf_instance->num_of_allowed_nssai &&
discovery_option->num_of_snssais) {
bool nssai_allowed = false;
for (i = 0; i < discovery_option->num_of_snssais; i++) {
int j;
for (j = 0; j < nf_instance->num_of_allowed_nssai; j++) {
if (discovery_option->snssais[i].sst ==
nf_instance->allowed_nssai[j].sst &&
discovery_option->snssais[i].sd.v ==
nf_instance->allowed_nssai[j].sd.v) {
nssai_allowed = true;
break;
}
}
if (nssai_allowed) break;
}
if (!nssai_allowed)
return false;
}
/* Determine which SMF filters are requested */
if (nf_instance->nf_type == OpenAPI_nf_type_SMF) {
need_smf_slice = (discovery_option->num_of_snssais &&
@ -2679,6 +2734,30 @@ void ogs_sbi_xact_remove(ogs_sbi_xact_t *xact)
if (xact->target_apiroot)
ogs_free(xact->target_apiroot);
/*
* Detach from the originating stream's xact_list, if attached.
*
* Two paths reach here:
*
* (a) Normal completion: a response arrived (or the send
* failed early) and the NF calls ogs_sbi_xact_remove().
* xact_detach() unlinks via the cached to_stream_list
* head in O(1), no stream lookup needed.
*
* (b) Stream close: stream_remove_xact_all() (or its MHD
* counterpart) walks the stream's xact_list and calls
* ogs_sbi_xact_remove() on each entry. This detach runs
* first and unlinks the node before the for_each_safe
* iterator advances; the iterator's cached "next" pointer
* keeps the loop sound.
*
* Idempotent for transactions that never had an inbound stream
* (NRF discovery initiated by the NF itself, status
* notifications): to_stream_list stays NULL and the helper is
* a no-op.
*/
ogs_sbi_server_detach_xact(xact);
/*
* Release optional user context attached to the transaction.
* The transaction owns this memory and is responsible for
@ -2759,7 +2838,12 @@ ogs_sbi_subscription_data_t *ogs_sbi_subscription_data_add(void)
ogs_sbi_subscription_data_t *subscription_data = NULL;
ogs_pool_alloc(&subscription_data_pool, &subscription_data);
ogs_assert(subscription_data);
if (!subscription_data) {
ogs_error("OVERFLOW subscription_data_pool [pool:%llu]",
(unsigned long long)ogs_app()->pool.subscription);
return NULL;
}
memset(subscription_data, 0, sizeof(ogs_sbi_subscription_data_t));
ogs_list_add(&ogs_sbi_self()->subscription_data_list, subscription_data);

View file

@ -173,6 +173,22 @@ typedef struct ogs_sbi_nf_instance_s {
#define OGS_SBI_MAX_NUM_OF_NF_TYPE 128
OpenAPI_nf_type_e allowed_nf_type[OGS_SBI_MAX_NUM_OF_NF_TYPE];
/*
* TS 33.518 4.2.2.2.1 - NF discovery authorization for specific slice
*
* s_nssai[]: S-NSSAIs this NF instance serves (from NFProfile.sNssais).
* Used to authorize the requester during NF discovery.
*
* allowed_nssai[]: S-NSSAIs for which this NF may be discovered
* (from NFProfile.allowedNssais).
* Used to filter target NFs during NF discovery.
*/
int num_of_s_nssai;
ogs_s_nssai_t s_nssai[OGS_MAX_NUM_OF_SLICE];
int num_of_allowed_nssai;
ogs_s_nssai_t allowed_nssai[OGS_MAX_NUM_OF_SLICE];
#define OGS_SBI_DEFAULT_PRIORITY 0
#define OGS_SBI_DEFAULT_CAPACITY 100
#define OGS_SBI_DEFAULT_LOAD 0
@ -270,6 +286,42 @@ typedef struct ogs_sbi_xact_s {
ogs_pool_id_t assoc_stream_id;
/*
* Linkage into the server-side stream that triggered this
* outbound transaction.
*
* When the inbound HTTP/2 stream is closed by the peer (e.g.
* RST_STREAM or connection close) before the response from the
* upstream NF arrives, every transaction registered on the
* stream's xact_list is cancelled. This releases the response
* timer back to the pool immediately instead of holding a slot
* until the SBI client wait timeout expires, which is the
* accumulation that leads to timer-pool exhaustion when a peer
* resets streams rapidly while the upstream NF is slow or
* unresponsive (issues #4472 and #4473).
*
* The linkage is managed entirely by lib/sbi:
* - ogs_sbi_discover_and_send() attaches when assoc_stream_id
* is set;
* - ogs_sbi_xact_remove() detaches automatically;
* - stream/session close drains the list and removes every
* attached transaction.
*
* to_stream_list is both the attachment flag and the cached
* list head:
* to_stream_list != NULL
* <=> to_stream_node is on *to_stream_list
* <=> assoc_stream_id refers to the owning stream
*
* Caching the list head address lets detach unlink in O(1)
* without re-resolving the stream from assoc_stream_id.
* Transactions with no associated inbound stream (e.g. NRF
* discovery initiated by the NF itself, status notifications)
* leave the whole linkage group zero-initialised.
*/
ogs_lnode_t to_stream_node;
ogs_list_t *to_stream_list;
/*
* Optional user context attached to this SBI transaction.
*
@ -485,7 +537,6 @@ ogs_sbi_nf_instance_t *ogs_sbi_nf_instance_find_by_discovery_param(
ogs_sbi_nf_instance_t *ogs_sbi_nf_instance_find_by_service(
OpenAPI_service_name_e service_name,
OpenAPI_nf_type_e requester_nf_type);
bool ogs_sbi_nf_instance_maximum_number_is_reached(void);
ogs_sbi_nf_service_t *ogs_sbi_nf_service_add(
ogs_sbi_nf_instance_t *nf_instance,

View file

@ -150,9 +150,9 @@ char *ogs_supi_from_suci(char *suci)
if (ogs_sbi_self()->hnet[home_network_pki_value].scheme
!= protection_scheme_id) {
ogs_error("Scheme Not Matched [%d != %s]",
ogs_sbi_self()->hnet[protection_scheme_id].scheme,
array[5]);
ogs_error("Scheme Not Matched [%d != %d]",
ogs_sbi_self()->hnet[home_network_pki_value].scheme,
protection_scheme_id);
break;
}
@ -882,9 +882,17 @@ bool ogs_sbi_time_from_string(ogs_time_t *timestamp, char *str)
(str[i-3] == '+' || str[i-3] == '-')) {
/* remove ':' character in timezone string range */
} else {
if (j >= MAX_TIMESTR_LEN - 1) {
ogs_error("Too long time string [%d]", (int)strlen(str));
return false;
}
seconds[j++] = str[i];
}
} else {
if (k >= MAX_TIMESTR_LEN - 1) {
ogs_error("Too long time string [%d]", (int)strlen(str));
return false;
}
subsecs[k++] = str[i];
}

View file

@ -1002,20 +1002,31 @@ int ogs_sbi_parse_request(
*/
char *v = ogs_hash_this_val(hi);
if (v) {
ogs_sbi_discovery_option_parse_service_names(
discovery_option, v);
if (ogs_sbi_discovery_option_parse_service_names(
discovery_option, v) != OGS_OK) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi), OGS_SBI_PARAM_SNSSAIS)) {
char *v = ogs_hash_this_val(hi);
if (v) {
ogs_sbi_discovery_option_parse_snssais(discovery_option, v);
if (ogs_sbi_discovery_option_parse_snssais(
discovery_option, v) != OGS_OK) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi), OGS_SBI_PARAM_GUAMI)) {
char *v = ogs_hash_this_val(hi);
if (v) {
ogs_sbi_discovery_option_parse_guami(discovery_option, v);
if (ogs_sbi_discovery_option_parse_guami(
discovery_option, v) != OGS_OK) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi), OGS_SBI_PARAM_DNN)) {
@ -1027,25 +1038,37 @@ int ogs_sbi_parse_request(
} else if (!strcmp(ogs_hash_this_key(hi), OGS_SBI_PARAM_TAI)) {
char *v = ogs_hash_this_val(hi);
if (v) {
ogs_sbi_discovery_option_parse_tai(discovery_option, v);
if (ogs_sbi_discovery_option_parse_tai(
discovery_option, v) != OGS_OK) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi),
OGS_SBI_PARAM_TARGET_PLMN_LIST)) {
char *v = ogs_hash_this_val(hi);
if (v) {
discovery_option->num_of_target_plmn_list =
ogs_sbi_discovery_option_parse_plmn_list(
int n = ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->target_plmn_list, v);
if (n < 0) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option->num_of_target_plmn_list = n;
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi),
OGS_SBI_PARAM_REQUESTER_PLMN_LIST)) {
char *v = ogs_hash_this_val(hi);
if (v) {
discovery_option->num_of_requester_plmn_list =
ogs_sbi_discovery_option_parse_plmn_list(
int n = ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->requester_plmn_list, v);
if (n < 0) {
ogs_sbi_discovery_option_free(discovery_option);
return OGS_ERROR;
}
discovery_option->num_of_requester_plmn_list = n;
discovery_option_presence = true;
}
} else if (!strcmp(ogs_hash_this_key(hi), OGS_SBI_PARAM_HNRF_URI)) {
@ -3172,12 +3195,23 @@ static int on_header_field(
data = multipart_parser_get_data(parser);
ogs_assert(data);
if (at && length) {
if (data->header_field)
ogs_free(data->header_field);
data->header_field = ogs_strndup(at, length);
ogs_assert(data->header_field);
/*
* The multipart state machine emits a zero-length field name when a
* header line begins with ':' (e.g. ":application/json"). Such input is
* malformed; rejecting it here prevents a NULL data->header_field from
* propagating into on_header_value(). See Issues #4608
*/
if (!at || !length) {
ogs_error("Invalid multipart header field");
data->parse_error = true;
return 0;
}
if (data->header_field)
ogs_free(data->header_field);
data->header_field = ogs_strndup(at, length);
ogs_assert(data->header_field);
return 0;
}
@ -3190,6 +3224,18 @@ static int on_header_value(
data = multipart_parser_get_data(parser);
ogs_assert(data);
/*
* Defense in depth: a header value must be preceded by a valid field
* name. on_header_field() already rejects empty names, but guard here as
* well so ogs_strcasecmp() (plain strcasecmp, not NULL-safe) is never
* called with a NULL data->header_field.
*/
if (!data->header_field) {
ogs_error("Multipart header value without field name");
data->parse_error = true;
return 0;
}
if (data->num_of_part < OGS_SBI_MAX_NUM_OF_PART && at && length) {
if (!ogs_strcasecmp(data->header_field, OGS_SBI_CONTENT_TYPE)) {
ogs_assert(data->part[data->num_of_part].content_type == NULL);
@ -3471,7 +3517,8 @@ static bool build_multipart(
ogs_random(digest, 16);
strcpy(boundary, "=-");
ogs_base64_encode_binary(boundary + 2, digest, 16);
ogs_assert(ogs_base64_encode_from_buffer(
boundary + 2, sizeof(boundary) - 2, digest, sizeof(digest)) > 0);
p = http->content = ogs_calloc(1, OGS_MAX_SDU_LEN);
if (!p) {
@ -3685,7 +3732,7 @@ char *ogs_sbi_discovery_option_build_service_names(
return service_names;
}
void ogs_sbi_discovery_option_parse_service_names(
int ogs_sbi_discovery_option_parse_service_names(
ogs_sbi_discovery_option_t *discovery_option,
char *service_names)
{
@ -3700,7 +3747,7 @@ void ogs_sbi_discovery_option_parse_service_names(
if (!v) {
ogs_error("ogs_sbi_url_decode() failed : service_names[%s]",
service_names);
return;
return OGS_ERROR;
}
/*
@ -3721,7 +3768,18 @@ void ogs_sbi_discovery_option_parse_service_names(
*/
token = ogs_strtok_r(v, ",", &saveptr);
while (token != NULL) {
OpenAPI_service_name_e name = OpenAPI_service_name_FromString(token);
OpenAPI_service_name_e name = OpenAPI_service_name_NULL;
if (discovery_option->num_of_service_names >=
OGS_SBI_MAX_NUM_OF_SERVICE_NAME) {
ogs_error("Too many service-names [%d:%d]",
discovery_option->num_of_service_names + 1,
OGS_SBI_MAX_NUM_OF_SERVICE_NAME);
ogs_free(v);
return OGS_ERROR;
}
name = OpenAPI_service_name_FromString(token);
if (name)
ogs_sbi_discovery_option_add_service_names(discovery_option, name);
else
@ -3731,6 +3789,7 @@ void ogs_sbi_discovery_option_parse_service_names(
}
ogs_free(v);
return OGS_OK;
}
void ogs_sbi_discovery_option_add_snssais(
@ -3783,7 +3842,7 @@ char *ogs_sbi_discovery_option_build_snssais(
return v;
}
void ogs_sbi_discovery_option_parse_snssais(
int ogs_sbi_discovery_option_parse_snssais(
ogs_sbi_discovery_option_t *discovery_option, char *snssais)
{
cJSON *item = NULL;
@ -3796,41 +3855,62 @@ void ogs_sbi_discovery_option_parse_snssais(
v = ogs_sbi_url_decode(snssais);
if (!v) {
ogs_error("ogs_sbi_url_decode() failed : snssais[%s]", snssais);
return;
return OGS_ERROR;
}
item = cJSON_Parse(v);
if (!item) {
ogs_error("Cannot parse snssais[%s]", snssais);
ogs_free(v);
return;
return OGS_ERROR;
}
if (!cJSON_IsArray(item)) {
ogs_error("snssais is not a JSON array [%s]", snssais);
cJSON_Delete(item);
ogs_free(v);
return OGS_ERROR;
}
cJSON_ArrayForEach(snssaiItem, item) {
if (cJSON_IsObject(snssaiItem)) {
OpenAPI_snssai_t *sNSSAI = OpenAPI_snssai_parseFromJSON(snssaiItem);
OpenAPI_snssai_t *sNSSAI = NULL;
ogs_s_nssai_t s_nssai;
if (sNSSAI) {
ogs_s_nssai_t s_nssai;
s_nssai.sst = sNSSAI->sst;
s_nssai.sd = ogs_s_nssai_sd_from_string(sNSSAI->sd);
ogs_sbi_discovery_option_add_snssais(
discovery_option, &s_nssai);
OpenAPI_snssai_free(sNSSAI);
} else {
ogs_error("OpenAPI_snssai_parseFromJSON() failed : snssais[%s]",
snssais);
}
} else {
ogs_error("Invalid cJSON Type in snssias[%s]", snssais);
if (discovery_option->num_of_snssais >= OGS_MAX_NUM_OF_SLICE) {
ogs_error("Too many snssais [%d:%d]",
discovery_option->num_of_snssais + 1,
OGS_MAX_NUM_OF_SLICE);
cJSON_Delete(item);
ogs_free(v);
return OGS_ERROR;
}
if (!cJSON_IsObject(snssaiItem)) {
ogs_error("Invalid cJSON Type in snssais[%s]", snssais);
cJSON_Delete(item);
ogs_free(v);
return OGS_ERROR;
}
sNSSAI = OpenAPI_snssai_parseFromJSON(snssaiItem);
if (!sNSSAI) {
ogs_error("OpenAPI_snssai_parseFromJSON() failed : snssais[%s]",
snssais);
cJSON_Delete(item);
ogs_free(v);
return OGS_ERROR;
}
s_nssai.sst = sNSSAI->sst;
s_nssai.sd = ogs_s_nssai_sd_from_string(sNSSAI->sd);
ogs_sbi_discovery_option_add_snssais(discovery_option, &s_nssai);
OpenAPI_snssai_free(sNSSAI);
}
cJSON_Delete(item);
ogs_free(v);
return OGS_OK;
}
void ogs_sbi_discovery_option_set_guami(
@ -3868,10 +3948,11 @@ char *ogs_sbi_discovery_option_build_guami(
return v;
}
void ogs_sbi_discovery_option_parse_guami(
int ogs_sbi_discovery_option_parse_guami(
ogs_sbi_discovery_option_t *discovery_option, char *guami)
{
OpenAPI_guami_t *Guami = NULL;
ogs_guami_t parsed_guami;
cJSON *guamItem = NULL;
char *v = NULL;
@ -3881,30 +3962,38 @@ void ogs_sbi_discovery_option_parse_guami(
v = ogs_sbi_url_decode(guami);
if (!v) {
ogs_error("ogs_sbi_url_decode() failed : guami[%s]", guami);
return;
return OGS_ERROR;
}
guamItem = cJSON_Parse(v);
if (!guamItem) {
ogs_error("Cannot parse guami[%s]", guami);
ogs_free(v);
return;
return OGS_ERROR;
}
if (!cJSON_IsObject(guamItem)) {
ogs_error("guami is not a JSON object [%s]", guami);
cJSON_Delete(guamItem);
ogs_free(v);
return OGS_ERROR;
}
Guami = OpenAPI_guami_parseFromJSON(guamItem);
if (Guami) {
ogs_guami_t guami;
ogs_sbi_parse_guami(&guami, Guami);
ogs_sbi_discovery_option_set_guami(discovery_option, &guami);
OpenAPI_guami_free(Guami);
} else {
ogs_error("OpenAPI_guami_parseFromJSON() failed : guami[%s]",
guami);
if (!Guami) {
ogs_error("OpenAPI_guami_parseFromJSON() failed : guami[%s]", guami);
cJSON_Delete(guamItem);
ogs_free(v);
return OGS_ERROR;
}
ogs_sbi_parse_guami(&parsed_guami, Guami);
ogs_sbi_discovery_option_set_guami(discovery_option, &parsed_guami);
OpenAPI_guami_free(Guami);
cJSON_Delete(guamItem);
ogs_free(v);
return OGS_OK;
}
void ogs_sbi_discovery_option_set_tai(
@ -3947,9 +4036,11 @@ char *ogs_sbi_discovery_option_build_tai(
return v;
}
void ogs_sbi_discovery_option_parse_tai(
int ogs_sbi_discovery_option_parse_tai(
ogs_sbi_discovery_option_t *discovery_option, char *tai)
{
OpenAPI_tai_t *Tai = NULL;
ogs_5gs_tai_t parsed_tai;
cJSON *taiItem = NULL;
char *v = NULL;
@ -3959,42 +4050,45 @@ void ogs_sbi_discovery_option_parse_tai(
v = ogs_sbi_url_decode(tai);
if (!v) {
ogs_error("ogs_sbi_url_decode() failed : tai[%s]", tai);
return;
return OGS_ERROR;
}
taiItem = cJSON_Parse(v);
if (!taiItem) {
ogs_error("Cannot parse tai[%s]", tai);
ogs_free(v);
return;
return OGS_ERROR;
}
if (cJSON_IsObject(taiItem)) {
OpenAPI_tai_t *Tai = OpenAPI_tai_parseFromJSON(taiItem);
if (Tai) {
ogs_5gs_tai_t tai;
memset(&tai, 0, sizeof(tai));
if (Tai->plmn_id)
ogs_sbi_parse_plmn_id(&tai.plmn_id, Tai->plmn_id);
if (Tai->tac)
tai.tac = ogs_uint24_from_string_hexadecimal(Tai->tac);
ogs_sbi_discovery_option_set_tai(discovery_option, &tai);
OpenAPI_tai_free(Tai);
} else {
ogs_error("OpenAPI_snssai_parseFromJSON() failed : tai[%s]",
tai);
}
} else {
ogs_error("Invalid cJSON Type in snssias[%s]", tai);
if (!cJSON_IsObject(taiItem)) {
ogs_error("tai is not a JSON object [%s]", tai);
cJSON_Delete(taiItem);
ogs_free(v);
return OGS_ERROR;
}
Tai = OpenAPI_tai_parseFromJSON(taiItem);
if (!Tai) {
ogs_error("OpenAPI_tai_parseFromJSON() failed : tai[%s]", tai);
cJSON_Delete(taiItem);
ogs_free(v);
return OGS_ERROR;
}
memset(&parsed_tai, 0, sizeof(parsed_tai));
if (Tai->plmn_id)
ogs_sbi_parse_plmn_id(&parsed_tai.plmn_id, Tai->plmn_id);
if (Tai->tac)
parsed_tai.tac = ogs_uint24_from_string_hexadecimal(Tai->tac);
ogs_sbi_discovery_option_set_tai(discovery_option, &parsed_tai);
OpenAPI_tai_free(Tai);
cJSON_Delete(taiItem);
ogs_free(v);
return OGS_OK;
}
void ogs_sbi_discovery_option_add_target_plmn_list(
@ -4074,35 +4168,51 @@ int ogs_sbi_discovery_option_parse_plmn_list(
cJSON *PlmnIdJSON = NULL;
OpenAPI_list_t *PlmnList = NULL;
OpenAPI_plmn_id_t *PlmnId = NULL;
int num_of_plmn_list = 0;
int num_of_plmn_list = -1;
ogs_assert(v);
ogs_assert(plmn_list);
item = cJSON_Parse(v);
if (item) {
PlmnList = OpenAPI_list_create();
ogs_assert(PlmnList);
cJSON_ArrayForEach(PlmnIdJSON, item) {
if (!cJSON_IsObject(PlmnIdJSON)) {
ogs_error("Unknown JSON");
goto cleanup;
}
if (!item) {
ogs_error("Cannot parse PLMN list [%s]", v);
return -1;
}
PlmnId = OpenAPI_plmn_id_parseFromJSON(PlmnIdJSON);
if (!PlmnId) {
ogs_error("No PlmnId");
goto cleanup;
}
if (!cJSON_IsArray(item)) {
ogs_error("PLMN list is not a JSON array [%s]", v);
goto cleanup;
}
OpenAPI_list_add(PlmnList, PlmnId);
if (cJSON_GetArraySize(item) > OGS_MAX_NUM_OF_PLMN) {
ogs_error("Too many PLMN IDs in list [%d:%d]",
cJSON_GetArraySize(item), OGS_MAX_NUM_OF_PLMN);
goto cleanup;
}
PlmnList = OpenAPI_list_create();
ogs_assert(PlmnList);
cJSON_ArrayForEach(PlmnIdJSON, item) {
if (!cJSON_IsObject(PlmnIdJSON)) {
ogs_error("Invalid cJSON Type in PLMN list [%s]", v);
goto cleanup;
}
PlmnId = OpenAPI_plmn_id_parseFromJSON(PlmnIdJSON);
if (!PlmnId) {
ogs_error("OpenAPI_plmn_id_parseFromJSON() failed [%s]", v);
goto cleanup;
}
OpenAPI_list_add(PlmnList, PlmnId);
}
num_of_plmn_list = ogs_sbi_parse_plmn_list(plmn_list, PlmnList);
cleanup:
ogs_sbi_free_plmn_list(PlmnList);
if (PlmnList)
ogs_sbi_free_plmn_list(PlmnList);
cJSON_Delete(item);
return num_of_plmn_list;

View file

@ -700,7 +700,7 @@ void ogs_sbi_discovery_option_add_service_names(
OpenAPI_service_name_e service_name);
char *ogs_sbi_discovery_option_build_service_names(
ogs_sbi_discovery_option_t *discovery_option);
void ogs_sbi_discovery_option_parse_service_names(
int ogs_sbi_discovery_option_parse_service_names(
ogs_sbi_discovery_option_t *discovery_option,
char *service_names);
@ -708,21 +708,21 @@ void ogs_sbi_discovery_option_add_snssais(
ogs_sbi_discovery_option_t *discovery_option, ogs_s_nssai_t *s_nssai);
char *ogs_sbi_discovery_option_build_snssais(
ogs_sbi_discovery_option_t *discovery_option);
void ogs_sbi_discovery_option_parse_snssais(
int ogs_sbi_discovery_option_parse_snssais(
ogs_sbi_discovery_option_t *discovery_option, char *snssais);
void ogs_sbi_discovery_option_set_guami(
ogs_sbi_discovery_option_t *discovery_option, ogs_guami_t *guami);
char *ogs_sbi_discovery_option_build_guami(
ogs_sbi_discovery_option_t *discovery_option);
void ogs_sbi_discovery_option_parse_guami(
int ogs_sbi_discovery_option_parse_guami(
ogs_sbi_discovery_option_t *discovery_option, char *guami);
void ogs_sbi_discovery_option_set_tai(
ogs_sbi_discovery_option_t *discovery_option, ogs_5gs_tai_t *tai);
char *ogs_sbi_discovery_option_build_tai(
ogs_sbi_discovery_option_t *discovery_option);
void ogs_sbi_discovery_option_parse_tai(
int ogs_sbi_discovery_option_parse_tai(
ogs_sbi_discovery_option_t *discovery_option, char *tai);
void ogs_sbi_discovery_option_add_target_plmn_list(

View file

@ -45,6 +45,9 @@ static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *stream);
static ogs_pool_id_t id_from_stream(ogs_sbi_stream_t *stream);
static void *stream_find_by_id(ogs_pool_id_t id);
static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
static void xact_detach(ogs_sbi_xact_t *xact);
const ogs_sbi_server_actions_t ogs_mhd_server_actions = {
server_init,
server_final,
@ -59,6 +62,9 @@ const ogs_sbi_server_actions_t ogs_mhd_server_actions = {
server_from_stream,
id_from_stream,
stream_find_by_id,
xact_attach,
xact_detach,
};
static void run(short when, ogs_socket_t fd, void *data);
@ -110,6 +116,15 @@ typedef struct ogs_sbi_session_s {
*/
ogs_timer_t *timer;
/*
* Outbound SBI transactions originated by this session. Drained
* on session_remove() so response timers are returned to the
* pool immediately when the inbound HTTP connection goes away.
* See the matching xact_list on the HTTP/2 backend (stream_s in
* lib/sbi/nghttp2-server.c).
*/
ogs_list_t xact_list;
void *data;
} ogs_sbi_session_t;
@ -141,6 +156,8 @@ static ogs_sbi_session_t *session_add(ogs_sbi_server_t *server,
sbi_sess->request = request;
sbi_sess->connection = connection;
ogs_list_init(&sbi_sess->xact_list);
sbi_sess->timer = ogs_timer_add(
ogs_app()->timer_mgr, session_timer_expired,
OGS_UINT_TO_POINTER(sbi_sess->id));
@ -160,6 +177,73 @@ static ogs_sbi_session_t *session_add(ogs_sbi_server_t *server,
return sbi_sess;
}
static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact)
{
ogs_sbi_session_t *sbi_sess = (ogs_sbi_session_t *)stream;
ogs_assert(sbi_sess);
ogs_assert(xact);
/*
* Invariant: the server-layer wrapper (ogs_sbi_server_attach_xact)
* already filtered out the already-attached case. Reaching the
* backend with to_stream_list set is a programming error.
*/
ogs_assert(!xact->to_stream_list);
/*
* Cache the list head so xact_detach() can unlink in O(1).
* to_stream_list serves as both attachment flag and cached head.
*/
ogs_list_add(&sbi_sess->xact_list, &xact->to_stream_node);
xact->to_stream_list = &sbi_sess->xact_list;
}
static void xact_detach(ogs_sbi_xact_t *xact)
{
ogs_assert(xact);
/*
* Invariant: the server-layer wrapper (ogs_sbi_server_detach_xact)
* already filtered out the not-attached case. Reaching the
* backend with to_stream_list cleared is a programming error.
*/
ogs_assert(xact->to_stream_list);
ogs_list_remove(xact->to_stream_list, &xact->to_stream_node);
xact->to_stream_list = NULL;
xact->assoc_stream_id = OGS_INVALID_POOL_ID;
}
/*
* Cancel outbound SBI transactions originated by this session before
* tearing it down. Same rationale as stream_remove_xact_all() on the
* HTTP/2 backend: release response timers immediately instead of
* holding pool slots until the SBI client wait timeout.
*/
static void session_remove_xact_all(ogs_sbi_session_t *sbi_sess)
{
ogs_sbi_xact_t *xact = NULL, *next_xact = NULL;
ogs_assert(sbi_sess);
ogs_list_for_each_entry_safe(
&sbi_sess->xact_list, next_xact, xact, to_stream_node) {
/*
* Logged at error level so the cancellation shows up in
* production traces alongside the upstream NF activity that
* was abandoned. See the matching log in stream_remove() on
* the HTTP/2 backend.
*/
ogs_error("Canceling pending outbound SBI transaction "
"on MHD session close [xact:%d,session:%d,service:%s]",
(int)xact->id, (int)sbi_sess->id,
OpenAPI_service_name_ToString(xact->service_name));
ogs_sbi_xact_remove(xact);
}
}
static void session_remove(ogs_sbi_session_t *sbi_sess)
{
struct MHD_Connection *connection;
@ -171,6 +255,8 @@ static void session_remove(ogs_sbi_session_t *sbi_sess)
ogs_list_remove(&server->session_list, sbi_sess);
session_remove_xact_all(sbi_sess);
ogs_assert(sbi_sess->timer);
ogs_timer_delete(sbi_sess->timer);

View file

@ -30,6 +30,21 @@ static void handle_nf_profile_retrieval(
ogs_assert(nf_instance_id);
ogs_assert(NFProfile);
if (!NFProfile->nf_instance_id) {
ogs_error("No NFProfile.NFInstanceId");
return;
}
if (!NFProfile->nf_type) {
ogs_error("No NFProfile.NFType");
return;
}
if (!NFProfile->nf_status) {
ogs_error("No NFProfile.NFStatus");
return;
}
nf_instance = ogs_sbi_nf_instance_find(nf_instance_id);
if (nf_instance) {
/* already have this nf_instance; done */
@ -42,11 +57,19 @@ static void handle_nf_profile_retrieval(
}
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("Can't add retrieved NF instance [%s] "
"due to insufficient space", nf_instance_id);
return;
}
ogs_sbi_nf_instance_set_id(nf_instance, nf_instance_id);
ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile);
if (ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile) == false) {
ogs_error("[%s] (NRF-profile-get) Invalid NFProfile", nf_instance_id);
ogs_sbi_nf_instance_remove(nf_instance);
return;
}
/* verify against our subscription list that we want to save this
* nf instance to our context */

View file

@ -43,6 +43,9 @@ static ogs_sbi_server_t *server_from_stream(ogs_sbi_stream_t *stream);
static ogs_pool_id_t id_from_stream(ogs_sbi_stream_t *stream);
static void *stream_find_by_id(ogs_pool_id_t id);
static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
static void xact_detach(ogs_sbi_xact_t *xact);
const ogs_sbi_server_actions_t ogs_nghttp2_server_actions = {
server_init,
server_final,
@ -58,6 +61,9 @@ const ogs_sbi_server_actions_t ogs_nghttp2_server_actions = {
id_from_stream,
stream_find_by_id,
xact_attach,
xact_detach,
};
struct h2_settings {
@ -96,6 +102,15 @@ typedef struct ogs_sbi_stream_s {
bool memory_overflow;
ogs_sbi_session_t *session;
/*
* Outbound SBI transactions originated by this inbound stream.
* Populated automatically when ogs_sbi_discover_and_send() sees
* xact->assoc_stream_id pointing at this stream, and drained at
* stream close so response timers are freed promptly rather
* than lingering until the SBI client wait timeout.
*/
ogs_list_t xact_list;
} ogs_sbi_stream_t;
static void session_remove(ogs_sbi_session_t *sbi_sess);
@ -771,11 +786,87 @@ static ogs_sbi_stream_t *stream_add(
stream->session = sbi_sess;
ogs_list_init(&stream->xact_list);
ogs_list_add(&sbi_sess->stream_list, stream);
return stream;
}
static void xact_attach(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact)
{
ogs_assert(stream);
ogs_assert(xact);
/*
* Invariant: the server-layer wrapper (ogs_sbi_server_attach_xact)
* already filtered out the already-attached case. Reaching the
* backend with to_stream_list set is a programming error.
*/
ogs_assert(!xact->to_stream_list);
/*
* Cache the list head so xact_detach() can unlink in O(1)
* without re-resolving the stream from assoc_stream_id.
* to_stream_list serves as both attachment flag and cached head.
*/
ogs_list_add(&stream->xact_list, &xact->to_stream_node);
xact->to_stream_list = &stream->xact_list;
}
static void xact_detach(ogs_sbi_xact_t *xact)
{
ogs_assert(xact);
/*
* Invariant: the server-layer wrapper (ogs_sbi_server_detach_xact)
* already filtered out the not-attached case. Reaching the
* backend with to_stream_list cleared is a programming error.
*/
ogs_assert(xact->to_stream_list);
ogs_list_remove(xact->to_stream_list, &xact->to_stream_node);
xact->to_stream_list = NULL;
xact->assoc_stream_id = OGS_INVALID_POOL_ID;
}
/*
* Cancel every outbound SBI transaction that was triggered by this
* stream. Without this, ogs_sbi_xact_remove() and its response timer
* would only be reached when the upstream NF responds or the SBI
* client wait timer expires; a peer that rapidly resets streams
* while the upstream NF stalls would pile up those timers until the
* pool is exhausted (the crash signature in issues #4472 / #4473).
*
* ogs_sbi_xact_remove() invokes xact_detach() internally, which is
* what unlinks the node from this list. ogs_list_for_each_entry_safe
* caches the next pointer before the body runs, so detach-during-
* iteration is well-defined.
*/
static void stream_remove_xact_all(ogs_sbi_stream_t *stream)
{
ogs_sbi_xact_t *xact = NULL, *next_xact = NULL;
ogs_assert(stream);
ogs_list_for_each_entry_safe(
&stream->xact_list, next_xact, xact, to_stream_node) {
/*
* Logged at error level so the cancellation shows up in
* production traces alongside the upstream NF activity that
* was abandoned. Useful for diagnosing #4472/#4473 style
* patterns and any future regression where a peer resets
* streams while upstream NFs are slow.
*/
ogs_error("Canceling pending outbound SBI transaction "
"on HTTP/2 stream close [xact:%d,stream:%d,service:%s]",
(int)xact->id, stream->stream_id,
OpenAPI_service_name_ToString(xact->service_name));
ogs_sbi_xact_remove(xact);
}
}
static void stream_remove(ogs_sbi_stream_t *stream)
{
ogs_sbi_session_t *sbi_sess = NULL;
@ -786,6 +877,8 @@ static void stream_remove(ogs_sbi_stream_t *stream)
ogs_list_remove(&sbi_sess->stream_list, stream);
stream_remove_xact_all(stream);
ogs_assert(stream->request);
ogs_sbi_request_free(stream->request);
@ -1615,6 +1708,7 @@ static int on_begin_headers(nghttp2_session *session,
{
ogs_sbi_session_t *sbi_sess = user_data;
ogs_sbi_stream_t *stream = NULL;
int rv;
ogs_assert(sbi_sess);
ogs_assert(session);
@ -1626,7 +1720,20 @@ static int on_begin_headers(nghttp2_session *session,
}
stream = stream_add(sbi_sess, frame->hd.stream_id);
ogs_assert(stream);
if (!stream) {
ogs_error("stream_add() failed for stream [%d]",
frame->hd.stream_id);
rv = nghttp2_submit_rst_stream(
session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_REFUSED_STREAM);
if (rv != 0)
ogs_error("nghttp2_submit_rst_stream() failed (%d:%s)",
rv, nghttp2_strerror(rv));
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
ogs_debug("STREAM added [%d]", frame->hd.stream_id);
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream);

View file

@ -256,6 +256,45 @@ OpenAPI_nf_profile_t *ogs_nnrf_nfm_build_nf_profile(
else
OpenAPI_list_free(AllowedNfTypeList);
if (nf_instance->num_of_s_nssai) {
OpenAPI_list_t *sNssaiList = OpenAPI_list_create();
ogs_assert(sNssaiList);
for (i = 0; i < nf_instance->num_of_s_nssai; i++) {
OpenAPI_ext_snssai_t *sNssai = OpenAPI_ext_snssai_create(
nf_instance->s_nssai[i].sst,
ogs_s_nssai_sd_to_string(nf_instance->s_nssai[i].sd),
NULL, false, 0);
ogs_assert(sNssai);
OpenAPI_list_add(sNssaiList, sNssai);
}
if (sNssaiList->count)
NFProfile->s_nssais = sNssaiList;
else
OpenAPI_list_free(sNssaiList);
}
if (nf_instance->num_of_allowed_nssai) {
OpenAPI_list_t *allowedNssaiList = OpenAPI_list_create();
ogs_assert(allowedNssaiList);
for (i = 0; i < nf_instance->num_of_allowed_nssai; i++) {
OpenAPI_ext_snssai_t *sNssai = OpenAPI_ext_snssai_create(
nf_instance->allowed_nssai[i].sst,
ogs_s_nssai_sd_to_string(
nf_instance->allowed_nssai[i].sd),
NULL, false, 0);
ogs_assert(sNssai);
OpenAPI_list_add(allowedNssaiList, sNssai);
}
if (allowedNssaiList->count)
NFProfile->allowed_nssais = allowedNssaiList;
else
OpenAPI_list_free(allowedNssaiList);
}
NFServiceList = OpenAPI_list_create();
if (!NFServiceList) {
ogs_error("No nf_service_list");
@ -441,6 +480,14 @@ void ogs_nnrf_nfm_free_nf_profile(OpenAPI_nf_profile_t *NFProfile)
OpenAPI_list_free(NFProfile->allowed_nf_types);
OpenAPI_list_for_each(NFProfile->s_nssais, node)
OpenAPI_ext_snssai_free(node->data);
OpenAPI_list_free(NFProfile->s_nssais);
OpenAPI_list_for_each(NFProfile->allowed_nssais, node)
OpenAPI_ext_snssai_free(node->data);
OpenAPI_list_free(NFProfile->allowed_nssais);
OpenAPI_list_for_each(NFProfile->nf_services, node) {
NFService = node->data;
ogs_assert(NFService);

View file

@ -21,13 +21,13 @@
static void handle_nf_service(
ogs_sbi_nf_service_t *nf_service, OpenAPI_nf_service_t *NFService);
static void handle_smf_info(
static bool handle_smf_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_smf_info_t *SmfInfo);
static void handle_scp_info(
static bool handle_scp_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_scp_info_t *ScpInfo);
static void handle_sepp_info(
static bool handle_sepp_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_sepp_info_t *SeppInfo);
static void handle_amf_info(
static bool handle_amf_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_amf_info_t *AmfInfo);
void ogs_nnrf_nfm_handle_nf_register(
@ -72,7 +72,7 @@ void ogs_nnrf_nfm_handle_nf_register(
}
}
void ogs_nnrf_nfm_handle_nf_profile(
bool ogs_nnrf_nfm_handle_nf_profile(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_nf_profile_t *NFProfile)
{
int rv;
@ -181,6 +181,45 @@ void ogs_nnrf_nfm_handle_nf_profile(
}
}
/*
* TS 33.518 4.2.2.2.1 - Store S-NSSAIs for slice-based discovery
* authorization. sNssais identifies the slices this NF serves;
* allowedNssais restricts for which slices the NF may be discovered.
*/
nf_instance->num_of_s_nssai = 0;
OpenAPI_list_for_each(NFProfile->s_nssais, node) {
OpenAPI_ext_snssai_t *sNssai = node->data;
if (sNssai) {
if (nf_instance->num_of_s_nssai >= OGS_MAX_NUM_OF_SLICE) {
ogs_warn("Overflow: NFProfile.sNssais exceeds %d",
OGS_MAX_NUM_OF_SLICE);
break;
}
nf_instance->s_nssai[nf_instance->num_of_s_nssai].sst =
sNssai->sst;
nf_instance->s_nssai[nf_instance->num_of_s_nssai].sd =
ogs_s_nssai_sd_from_string(sNssai->sd);
nf_instance->num_of_s_nssai++;
}
}
nf_instance->num_of_allowed_nssai = 0;
OpenAPI_list_for_each(NFProfile->allowed_nssais, node) {
OpenAPI_ext_snssai_t *sNssai = node->data;
if (sNssai) {
if (nf_instance->num_of_allowed_nssai >= OGS_MAX_NUM_OF_SLICE) {
ogs_warn("Overflow: NFProfile.allowedNssais exceeds %d",
OGS_MAX_NUM_OF_SLICE);
break;
}
nf_instance->allowed_nssai[nf_instance->num_of_allowed_nssai].sst =
sNssai->sst;
nf_instance->allowed_nssai[nf_instance->num_of_allowed_nssai].sd =
ogs_s_nssai_sd_from_string(sNssai->sd);
nf_instance->num_of_allowed_nssai++;
}
}
OpenAPI_list_for_each(NFProfile->nf_services, node) {
ogs_sbi_nf_service_t *nf_service = NULL;
OpenAPI_nf_service_t *NFService = node->data;
@ -212,7 +251,11 @@ void ogs_nnrf_nfm_handle_nf_profile(
nf_instance,
NFService->service_instance_id,
NFService->service_name, NFService->scheme);
ogs_assert(nf_service);
if (!nf_service) {
ogs_error("Failed to add NFService [%s]",
NFService->service_instance_id);
return false;
}
}
ogs_sbi_nf_service_clear(nf_service);
@ -255,7 +298,11 @@ void ogs_nnrf_nfm_handle_nf_profile(
nf_instance,
NFService->service_instance_id,
NFService->service_name, NFService->scheme);
ogs_assert(nf_service);
if (!nf_service) {
ogs_error("Failed to add NFService [%s]",
NFService->service_instance_id);
return false;
}
}
ogs_sbi_nf_service_clear(nf_service);
@ -266,26 +313,34 @@ void ogs_nnrf_nfm_handle_nf_profile(
ogs_sbi_nf_info_remove_all(&nf_instance->nf_info_list);
if (NFProfile->smf_info)
handle_smf_info(nf_instance, NFProfile->smf_info);
if (NFProfile->smf_info &&
handle_smf_info(nf_instance, NFProfile->smf_info) == false)
return false;
OpenAPI_list_for_each(NFProfile->smf_info_list, node) {
OpenAPI_map_t *SmfInfoMap = node->data;
if (SmfInfoMap && SmfInfoMap->value)
handle_smf_info(nf_instance, SmfInfoMap->value);
if (SmfInfoMap && SmfInfoMap->value &&
handle_smf_info(nf_instance, SmfInfoMap->value) == false)
return false;
}
if (NFProfile->amf_info)
handle_amf_info(nf_instance, NFProfile->amf_info);
if (NFProfile->amf_info &&
handle_amf_info(nf_instance, NFProfile->amf_info) == false)
return false;
OpenAPI_list_for_each(NFProfile->amf_info_list, node) {
OpenAPI_map_t *AmfInfoMap = node->data;
if (AmfInfoMap && AmfInfoMap->value)
handle_amf_info(nf_instance, AmfInfoMap->value);
if (AmfInfoMap && AmfInfoMap->value &&
handle_amf_info(nf_instance, AmfInfoMap->value) == false)
return false;
}
if (NFProfile->scp_info)
handle_scp_info(nf_instance, NFProfile->scp_info);
if (NFProfile->sepp_info)
handle_sepp_info(nf_instance, NFProfile->sepp_info);
if (NFProfile->scp_info &&
handle_scp_info(nf_instance, NFProfile->scp_info) == false)
return false;
if (NFProfile->sepp_info &&
handle_sepp_info(nf_instance, NFProfile->sepp_info) == false)
return false;
return true;
}
static void handle_nf_service(
@ -387,9 +442,10 @@ static void handle_nf_service(
nf_service->load = NFService->load;
}
static void handle_smf_info(
static bool handle_smf_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_smf_info_t *SmfInfo)
{
bool rv = true;
ogs_sbi_nf_info_t *nf_info = NULL;
OpenAPI_list_t *sNssaiSmfInfoList = NULL;
@ -412,7 +468,10 @@ static void handle_smf_info(
nf_info = ogs_sbi_nf_info_add(
&nf_instance->nf_info_list, OpenAPI_nf_type_SMF);
ogs_assert(nf_info);
if (!nf_info) {
ogs_error("Failed to add SMF nfInfo");
return false;
}
sNssaiSmfInfoList = SmfInfo->s_nssai_smf_info_list;
OpenAPI_list_for_each(sNssaiSmfInfoList, node) {
@ -421,6 +480,7 @@ static void handle_smf_info(
if (nf_info->smf.num_of_slice >= OGS_MAX_NUM_OF_SLICE) {
ogs_error("OVERFLOW Slice [%d:%d]",
nf_info->smf.num_of_slice, OGS_MAX_NUM_OF_SLICE);
rv = false;
break;
}
@ -431,17 +491,28 @@ static void handle_smf_info(
int dnn_index = nf_info->smf.slice
[nf_info->smf.num_of_slice].num_of_dnn;
ogs_assert(dnn_index < OGS_MAX_NUM_OF_DNN);
if (dnn_index >= OGS_MAX_NUM_OF_DNN) {
ogs_error("OVERFLOW DNN [%d:%d]",
dnn_index, OGS_MAX_NUM_OF_DNN);
rv = false;
break;
}
nf_info->smf.slice[nf_info->smf.num_of_slice].
dnn[dnn_index] = ogs_strdup(DnnSmfInfoItem->dnn);
ogs_assert(
nf_info->smf.slice[nf_info->smf.num_of_slice].
dnn[dnn_index]);
if (!nf_info->smf.slice[nf_info->smf.num_of_slice].
dnn[dnn_index]) {
ogs_error("No memory for DNN");
rv = false;
break;
}
nf_info->smf.slice[nf_info->smf.num_of_slice].
num_of_dnn++;
}
}
if (rv == false)
break;
if (!nf_info->smf.slice[nf_info->smf.num_of_slice].num_of_dnn) {
ogs_error("No DNN");
continue;
@ -460,10 +531,12 @@ static void handle_smf_info(
}
}
if (rv == false) goto out;
if (nf_info->smf.num_of_slice == 0) {
ogs_error("No S-NSSAI(DNN) in smfInfo");
ogs_sbi_nf_info_remove(&nf_instance->nf_info_list, nf_info);
return;
return true;
}
TaiList = SmfInfo->tai_list;
@ -474,6 +547,7 @@ static void handle_smf_info(
if (nf_info->smf.num_of_nr_tai >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TAI [%d:%d]",
nf_info->smf.num_of_nr_tai, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
@ -486,6 +560,8 @@ static void handle_smf_info(
}
}
if (rv == false) goto out;
TaiRangeList = SmfInfo->tai_range_list;
OpenAPI_list_for_each(TaiRangeList, node) {
TaiRangeItem = node->data;
@ -495,6 +571,7 @@ static void handle_smf_info(
if (nf_info->smf.num_of_nr_tai_range >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TaiRangeItem [%d:%d]",
nf_info->smf.num_of_nr_tai_range, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
@ -513,6 +590,7 @@ static void handle_smf_info(
if (tac_index >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TAI [%d:%d]",
tac_index, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
@ -533,12 +611,36 @@ static void handle_smf_info(
}
}
if (rv == false)
break;
nf_info->smf.num_of_nr_tai_range++;
}
}
out:
if (rv == false) {
ogs_sbi_nf_info_remove(&nf_instance->nf_info_list, nf_info);
return false;
}
return true;
}
static void handle_scp_info(
static void scp_info_free(ogs_sbi_scp_info_t *scp_info)
{
int i;
ogs_assert(scp_info);
for (i = 0; i < scp_info->num_of_domain; i++) {
ogs_free(scp_info->domain[i].name);
ogs_free(scp_info->domain[i].fqdn);
}
memset(scp_info, 0, sizeof(*scp_info));
}
static bool handle_scp_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_scp_info_t *ScpInfo)
{
ogs_sbi_nf_info_t *nf_info = NULL;
@ -636,13 +738,19 @@ static void handle_scp_info(
scp_info.num_of_domain) {
nf_info = ogs_sbi_nf_info_add(
&nf_instance->nf_info_list, OpenAPI_nf_type_SCP);
ogs_assert(nf_info);
if (!nf_info) {
ogs_error("Failed to add SCP nfInfo");
scp_info_free(&scp_info);
return false;
}
memcpy(&nf_info->scp, &scp_info, sizeof(scp_info));
}
return true;
}
static void handle_sepp_info(
static bool handle_sepp_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_sepp_info_t *SeppInfo)
{
ogs_sbi_nf_info_t *nf_info = NULL;
@ -692,7 +800,10 @@ static void handle_sepp_info(
if (http.presence || https.presence) {
nf_info = ogs_sbi_nf_info_add(
&nf_instance->nf_info_list, OpenAPI_nf_type_SEPP);
ogs_assert(nf_info);
if (!nf_info) {
ogs_error("Failed to add SEPP nfInfo");
return false;
}
nf_info->sepp.http.presence = http.presence;
nf_info->sepp.http.port = http.port;
@ -700,11 +811,14 @@ static void handle_sepp_info(
nf_info->sepp.https.presence = https.presence;
nf_info->sepp.https.port = https.port;
}
return true;
}
static void handle_amf_info(
static bool handle_amf_info(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_amf_info_t *AmfInfo)
{
bool rv = true;
ogs_sbi_nf_info_t *nf_info = NULL;
OpenAPI_list_t *GuamiList = NULL;
OpenAPI_guami_t *GuamiAmfInfoItem = NULL;
@ -721,7 +835,10 @@ static void handle_amf_info(
nf_info = ogs_sbi_nf_info_add(
&nf_instance->nf_info_list, OpenAPI_nf_type_AMF);
ogs_assert(nf_info);
if (!nf_info) {
ogs_error("Failed to add AMF nfInfo");
return false;
}
nf_info->amf.amf_set_id = ogs_uint64_from_string_hexadecimal(
AmfInfo->amf_set_id);
@ -735,6 +852,7 @@ static void handle_amf_info(
if (nf_info->amf.num_of_guami >= OGS_MAX_NUM_OF_SERVED_GUAMI) {
ogs_error("OVERFLOW Guami [%d:%d]",
nf_info->amf.num_of_guami, OGS_MAX_NUM_OF_SERVED_GUAMI);
rv = false;
break;
}
@ -749,6 +867,7 @@ static void handle_amf_info(
}
}
}
if (rv == false) goto out;
TaiList = AmfInfo->tai_list;
OpenAPI_list_for_each(TaiList, node) {
@ -759,6 +878,7 @@ static void handle_amf_info(
if (nf_info->amf.num_of_nr_tai >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TaiItem [%d:%d]",
nf_info->amf.num_of_nr_tai, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
@ -769,6 +889,7 @@ static void handle_amf_info(
nf_info->amf.num_of_nr_tai++;
}
}
if (rv == false) goto out;
TaiRangeList = AmfInfo->tai_range_list;
OpenAPI_list_for_each(TaiRangeList, node) {
@ -778,6 +899,7 @@ static void handle_amf_info(
if (nf_info->amf.num_of_nr_tai_range >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TaiRangeItem [%d:%d]",
nf_info->amf.num_of_nr_tai_range, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
@ -793,7 +915,12 @@ static void handle_amf_info(
TacRangeItem->start && TacRangeItem->end) {
int tac_index = nf_info->amf.nr_tai_range
[nf_info->amf.num_of_nr_tai_range].num_of_tac_range;
ogs_assert(tac_index < OGS_MAX_NUM_OF_TAI);
if (tac_index >= OGS_MAX_NUM_OF_TAI) {
ogs_error("OVERFLOW TAI [%d:%d]",
tac_index, OGS_MAX_NUM_OF_TAI);
rv = false;
break;
}
nf_info->amf.nr_tai_range
[nf_info->amf.num_of_nr_tai_range].start[tac_index] =
@ -808,9 +935,21 @@ static void handle_amf_info(
[nf_info->amf.num_of_nr_tai_range].num_of_tac_range++;
}
}
if (rv == false)
break;
nf_info->amf.num_of_nr_tai_range++;
}
}
out:
if (rv == false) {
ogs_sbi_nf_info_remove(&nf_instance->nf_info_list, nf_info);
return false;
}
return true;
}
static void handle_validity_time(
@ -1105,6 +1244,7 @@ bool ogs_nnrf_nfm_handle_nf_status_notify(
OpenAPI_notification_event_type_NF_REGISTERED) {
OpenAPI_nf_profile_t *NFProfile = NULL;
bool nf_instance_created = false;
NFProfile = NotificationData->nf_profile;
if (!NFProfile) {
@ -1150,11 +1290,22 @@ bool ogs_nnrf_nfm_handle_nf_status_notify(
nf_instance = ogs_sbi_nf_instance_find(message.h.resource.component[1]);
if (!nf_instance) {
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("Can't add notified NF instance [%s] "
"due to insufficient space",
message.h.resource.component[1]);
ogs_assert(true == ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_PAYLOAD_TOO_LARGE,
recvmsg, "Insufficient space",
message.h.resource.component[1], NULL));
ogs_sbi_header_free(&header);
return false;
}
ogs_sbi_nf_instance_set_id(
nf_instance, message.h.resource.component[1]);
ogs_sbi_nf_fsm_init(nf_instance);
nf_instance_created = true;
ogs_info("[%s] (NRF-notify) NF registered", nf_instance->id);
} else {
@ -1168,7 +1319,31 @@ bool ogs_nnrf_nfm_handle_nf_status_notify(
}
}
ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile);
if (ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile) == false) {
ogs_error("[%s] (NRF-notify) Invalid NFProfile [type:%s]",
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
/*
* ogs_nnrf_nfm_handle_nf_profile() rebuilds the NF profile in
* place. If parsing fails for a newly created cache entry, remove
* it because it may contain a partial profile. If the entry
* already existed, keep it in the registry to avoid deleting a
* previously usable local cache entry because of one bad notify.
*/
if (nf_instance_created == true) {
ogs_sbi_nf_fsm_fini(nf_instance);
ogs_sbi_nf_instance_remove(nf_instance);
}
ogs_assert(true ==
ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST,
recvmsg, "Invalid NFProfile",
NFProfile->nf_instance_id, NULL));
ogs_sbi_header_free(&header);
return false;
}
ogs_info("[%s] (NRF-notify) NF Profile updated [type:%s]",
nf_instance->id,
@ -1237,6 +1412,7 @@ void ogs_nnrf_disc_handle_nf_discover_search_result(
OpenAPI_list_for_each(SearchResult->nf_instances, node) {
OpenAPI_nf_profile_t *NFProfile = NULL;
bool nf_instance_created = false;
if (!node->data) continue;
@ -1265,27 +1441,52 @@ void ogs_nnrf_disc_handle_nf_discover_search_result(
nf_instance = ogs_sbi_nf_instance_find(NFProfile->nf_instance_id);
if (!nf_instance) {
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("Can't add discovered NF instance [%s:%s] "
"due to insufficient space",
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
continue;
}
ogs_sbi_nf_instance_set_id(nf_instance, NFProfile->nf_instance_id);
ogs_sbi_nf_fsm_init(nf_instance);
nf_instance_created = true;
ogs_info("[%s] (NRF-discover) NF registered [type:%s]",
nf_instance->id,
OpenAPI_nf_type_ToString(nf_instance->nf_type));
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
} else {
ogs_warn("[%s] (NRF-discover) NF has already been added [type:%s]",
nf_instance->id,
OpenAPI_nf_type_ToString(nf_instance->nf_type));
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
if (!OGS_FSM_CHECK(&nf_instance->sm, ogs_sbi_nf_state_registered)) {
ogs_error("[%s] (NRF-notify) NF invalid state [type:%s]",
nf_instance->id,
OpenAPI_nf_type_ToString(nf_instance->nf_type));
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
}
}
if (NF_INSTANCE_ID_IS_OTHERS(nf_instance->id)) {
ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile);
if (ogs_nnrf_nfm_handle_nf_profile(
nf_instance, NFProfile) == false) {
ogs_error("[%s] (NRF-discover) Invalid NFProfile [type:%s]",
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
/*
* Do not leave a newly created partial NFProfile in the
* registry. If the entry already existed, keep it to avoid
* deleting a previously usable local cache entry because of
* one bad discovery result.
*/
if (nf_instance_created == true) {
ogs_sbi_nf_fsm_fini(nf_instance);
ogs_sbi_nf_instance_remove(nf_instance);
}
continue;
}
ogs_sbi_client_associate(nf_instance);
@ -1306,8 +1507,8 @@ void ogs_nnrf_disc_handle_nf_discover_search_result(
ogs_info("[%s] (NF-discover) NF Profile updated "
"[type:%s validity:%ds]",
nf_instance->id,
OpenAPI_nf_type_ToString(nf_instance->nf_type),
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type),
nf_instance->time.validity_duration);
}
}

View file

@ -26,7 +26,7 @@ extern "C" {
void ogs_nnrf_nfm_handle_nf_register(
ogs_sbi_nf_instance_t *nf_instance, ogs_sbi_message_t *recvmsg);
void ogs_nnrf_nfm_handle_nf_profile(
bool ogs_nnrf_nfm_handle_nf_profile(
ogs_sbi_nf_instance_t *nf_instance, OpenAPI_nf_profile_t *NFProfile);
void ogs_nnrf_nfm_handle_nf_status_subscribe(

View file

@ -101,11 +101,16 @@ bool ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_assert(!subscr_cond_nf_type || !subscr_cond_service_name);
subscription_data = ogs_sbi_subscription_data_add();
ogs_assert(subscription_data);
if (!subscription_data) {
ogs_error("ogs_sbi_subscription_data_add() failed");
return false;
}
subscription_data->req_nf_type = req_nf_type;
if (req_nf_instance_id)
if (req_nf_instance_id) {
subscription_data->req_nf_instance_id = ogs_strdup(req_nf_instance_id);
ogs_assert(subscription_data->req_nf_instance_id);
}
if (subscr_cond_nf_type)
subscription_data->subscr_cond.nf_type = subscr_cond_nf_type;
@ -119,6 +124,7 @@ bool ogs_nnrf_nfm_send_nf_status_subscribe(
request = ogs_nnrf_nfm_build_status_subscribe(subscription_data);
if (!request) {
ogs_error("No Request");
ogs_sbi_subscription_data_remove(subscription_data);
return false;
}
@ -129,6 +135,54 @@ bool ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_request_free(request);
if (rc != true)
ogs_sbi_subscription_data_remove(subscription_data);
return rc;
}
bool ogs_nnrf_nfm_send_nf_status_subscribe_renew(
ogs_sbi_subscription_data_t *subscription_data)
{
char *req_nf_instance_id = NULL;
OpenAPI_service_name_e subscr_cond_service_name = OpenAPI_service_name_NULL;
OpenAPI_nf_type_e req_nf_type;
OpenAPI_nf_type_e subscr_cond_nf_type;
bool rc;
ogs_assert(subscription_data);
/*
* The old subscription still occupies subscription_data_pool here.
* Keep local copies of the fields needed to rebuild the request,
* remove the old subscription first so the pool has a free slot,
* then resubscribe. Without this, a full pool would make renewal
* fail forever even though the renewal itself does not change the
* net occupancy.
*/
req_nf_type = subscription_data->req_nf_type;
subscr_cond_nf_type = subscription_data->subscr_cond.nf_type;
if (subscription_data->req_nf_instance_id) {
req_nf_instance_id =
ogs_strdup(subscription_data->req_nf_instance_id);
ogs_assert(req_nf_instance_id);
}
if (subscription_data->subscr_cond.service_name)
subscr_cond_service_name = subscription_data->subscr_cond.service_name;
ogs_sbi_subscription_data_remove(subscription_data);
/* subscription_data is invalid past this point */
rc = ogs_nnrf_nfm_send_nf_status_subscribe(
req_nf_type, req_nf_instance_id,
subscr_cond_nf_type, subscr_cond_service_name);
if (rc != true)
ogs_error("NF status subscription renewal failed");
ogs_free(req_nf_instance_id);
return rc;
}

View file

@ -33,6 +33,8 @@ bool ogs_nnrf_nfm_send_nf_status_subscribe(
OpenAPI_nf_type_e req_nf_type, char *req_nf_instance_id,
OpenAPI_nf_type_e subscr_cond_nf_type,
OpenAPI_service_name_e subscr_cond_service_name);
bool ogs_nnrf_nfm_send_nf_status_subscribe_renew(
ogs_sbi_subscription_data_t *subscription_data);
bool ogs_nnrf_nfm_send_nf_status_update(
ogs_sbi_subscription_data_t *subscription_data);
bool ogs_nnrf_nfm_send_nf_status_unsubscribe(

View file

@ -270,6 +270,48 @@ int ogs_sbi_discover_and_send(ogs_sbi_xact_t *xact)
request = xact->request;
ogs_assert(request);
/*
* If the NF associated this transaction with an inbound server
* stream (by assigning xact->assoc_stream_id before calling us),
* register the transaction on that stream's outbound xact list.
*
* When the inbound stream is later closed by the peer (HTTP/2
* RST_STREAM, connection drop) before the upstream NF response
* arrives, stream_remove() walks the list and cancels every
* outstanding transaction. The response timer is returned to
* the pool immediately rather than holding a slot until the
* SBI client wait timeout, so a burst of short-lived inbound
* requests cannot pile up enough pending timers to exhaust the
* pool (issues #4472 and #4473).
*
* Transactions with no inbound stream (e.g. NRF discovery
* initiated by the NF itself, status notifications) leave
* assoc_stream_id at OGS_INVALID_POOL_ID and the helper is a
* no-op (returns OGS_OK).
*
* If attach reports failure (originating stream already
* closed), we DO NOT abort the upstream send and we DO NOT
* propagate the failure to the NF caller: most NF wrappers
* treat any non-OK return as a hard send failure and remove
* the transaction, which would orphan an upstream request
* already on the wire. The diagnostic context (xact id,
* stream id, service type, file:line) is already emitted by
* ogs_sbi_server_attach_xact() itself and by the ogs_error()
* below, so the situation is observable without changing the
* caller-visible return code. If a response later arrives,
* the NF response handler will see no stream and drop it
* through the existing "STREAM has already been removed"
* path.
*/
if (ogs_sbi_server_attach_xact(xact) != OGS_OK) {
ogs_error("ogs_sbi_discover_and_send: attach failed, "
"proceeding with upstream send "
"[xact:%d,assoc_stream_id:%d,service:%s]",
(int)xact->id, (int)xact->assoc_stream_id,
OpenAPI_service_name_ToString(service_name));
/* fall through — upstream send must still happen */
}
discovery_option = xact->discovery_option;
/* SCP Availability */

View file

@ -262,6 +262,97 @@ void *ogs_sbi_stream_find_by_id(ogs_pool_id_t id)
return ogs_sbi_server_actions.stream_find_by_id(id);
}
int ogs_sbi_server_attach_xact(ogs_sbi_xact_t *xact)
{
ogs_sbi_stream_t *stream = NULL;
ogs_assert(xact);
/*
* Idempotent. NFs may set xact->assoc_stream_id and reach the
* SBI path multiple times for retries; attaching twice would
* corrupt the list, so we skip when already attached. This is
* not an error in itself return OK so the caller continues.
*/
if (xact->to_stream_list) {
ogs_error("ogs_sbi_server_attach_xact: already attached "
"[xact:%d,assoc_stream_id:%d,service:%s]",
(int)xact->id, (int)xact->assoc_stream_id,
OpenAPI_service_name_ToString(xact->service_name));
return OGS_OK;
}
/*
* No associated stream is normal for self-initiated transactions
* (NRF discovery from the NF itself, status notifications, etc.).
* Silent skip, OK.
*/
if (xact->assoc_stream_id < OGS_MIN_POOL_ID ||
xact->assoc_stream_id > OGS_MAX_POOL_ID)
return OGS_OK;
/*
* xact->assoc_stream_id is a "best-effort" linkage: it means
* "if a response arrives and this stream is still alive,
* deliver the response there." The stream is not required to
* be alive at the moment the outbound transaction is sent.
*
* NFs are allowed to pass the same stream to discover_and_send()
* even after that stream has already been used to answer the
* original peer (and is therefore being closed). The common
* case is a "respond then start follow-up" pattern, e.g. SMF
* answering the AMF with HTTP 201 Created and then kicking off
* Npcf_SMPolicyControl_Create.
*
* Two outcomes:
*
* - Stream still alive: attach to its xact_list so the
* transaction is cancelled (and its response timer freed)
* if the peer later resets the stream. This is the
* #4472/#4473 protection path. Returns OGS_OK.
*
* - Stream already gone: nothing to attach. Returns
* OGS_ERROR so the caller can log with its own context
* (NF name, UE/session id, etc.) and decide what to do.
* The caller may legitimately ignore the error and let
* the transaction proceed; the response (if any) will be
* dropped through the existing "STREAM has already been
* removed" path in the response handler.
*/
stream = ogs_sbi_stream_find_by_id(xact->assoc_stream_id);
if (!stream) {
ogs_error("ogs_sbi_server_attach_xact: originating SBI stream "
"already closed "
"[xact:%d,assoc_stream_id:%d,service:%s]",
(int)xact->id, (int)xact->assoc_stream_id,
OpenAPI_service_name_ToString(xact->service_name));
return OGS_NOTFOUND;
}
ogs_assert(ogs_sbi_server_actions.xact_attach);
ogs_sbi_server_actions.xact_attach(stream, xact);
return OGS_OK;
}
void ogs_sbi_server_detach_xact(ogs_sbi_xact_t *xact)
{
ogs_assert(xact);
/*
* Idempotent. No-op when not attached, including transactions
* that never had an inbound stream (NRF discovery, status
* notifications) where to_stream_list stays NULL. This is
* invoked unconditionally from ogs_sbi_xact_remove(), so
* streamless transactions reach here on every cleanup.
*/
if (!xact->to_stream_list)
return;
ogs_assert(ogs_sbi_server_actions.xact_detach);
ogs_sbi_server_actions.xact_detach(xact);
}
static ogs_sbi_server_t *ogs_sbi_server_find_by_interface(
ogs_sbi_server_t *current, const char *interface)
{

View file

@ -32,6 +32,7 @@ extern "C" {
#include <openssl/err.h>
typedef struct ogs_sbi_stream_s ogs_sbi_stream_t;
typedef struct ogs_sbi_xact_s ogs_sbi_xact_t;
typedef struct ogs_sbi_server_s {
ogs_socknode_t node;
@ -71,6 +72,15 @@ typedef struct ogs_sbi_server_actions_s {
ogs_pool_id_t (*id_from_stream)(ogs_sbi_stream_t *stream);
void *(*stream_find_by_id)(ogs_pool_id_t id);
/*
* Per-backend hooks linking an outbound SBI transaction into the
* inbound server stream that triggered it. The list head lives
* inside the backend-private stream/session struct, so each
* backend (HTTP/2 vs MHD) provides its own attach/detach.
*/
void (*xact_attach)(ogs_sbi_stream_t *stream, ogs_sbi_xact_t *xact);
void (*xact_detach)(ogs_sbi_xact_t *xact);
} ogs_sbi_server_actions_t;
void ogs_sbi_server_init(int num_of_session_pool, int num_of_stream_pool);
@ -107,6 +117,36 @@ ogs_sbi_server_t *ogs_sbi_server_from_stream(ogs_sbi_stream_t *stream);
ogs_pool_id_t ogs_sbi_id_from_stream(ogs_sbi_stream_t *stream);
void *ogs_sbi_stream_find_by_id(ogs_pool_id_t id);
/*
* Helpers used by lib/sbi to link an outbound SBI transaction into
* the inbound stream that triggered it. NFs do not call these
* directly: ogs_sbi_discover_and_send() attaches when
* xact->assoc_stream_id is set, and ogs_sbi_xact_remove() /
* stream close detach automatically.
*
* Both helpers are idempotent and consult xact->to_stream_list as
* the ground-truth attachment flag.
*
* ogs_sbi_server_attach_xact() returns OGS_OK when the transaction
* is either freshly attached, already attached, or has no stream
* to attach to (streamless self-initiated xact). It returns
* OGS_NOTFOUND when the originating stream id is valid but the
* stream itself is already gone.
*
* ogs_sbi_discover_and_send() does not propagate this OGS_NOTFOUND
* to the NF caller. Some NF procedures send the original HTTP response
* first and then continue with follow-up SBI transactions while still
* passing the original stream pointer for convenience. Treating this
* as a hard send failure would make those wrappers call their normal
* error path, which may try to send another response on a stream that
* has already been answered.
*
* Therefore, this condition is logged here, stream attachment is skipped,
* and the upstream send continues.
*/
int ogs_sbi_server_attach_xact(ogs_sbi_xact_t *xact);
void ogs_sbi_server_detach_xact(ogs_sbi_xact_t *xact);
ogs_sbi_server_t *ogs_sbi_server_first(void);
ogs_sbi_server_t *ogs_sbi_server_next(ogs_sbi_server_t *current);
ogs_sbi_server_t *ogs_sbi_server_first_by_interface(const char *interface);

View file

@ -64,7 +64,6 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
amf_ue_t *amf_ue = NULL;
amf_sess_t *sess = NULL;
ogs_sbi_object_t *sbi_object = NULL;
ogs_pool_id_t sbi_object_id = OGS_INVALID_POOL_ID;
ogs_sbi_xact_t *sbi_xact = NULL;
ogs_pool_id_t sbi_xact_id = OGS_INVALID_POOL_ID;
@ -73,15 +72,12 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
ogs_sbi_stream_t *stream = NULL;
ogs_pool_id_t stream_id = OGS_INVALID_POOL_ID;
ogs_sbi_request_t *sbi_request = NULL;
OpenAPI_service_name_e service_name = OpenAPI_service_name_NULL;
ogs_sbi_nf_instance_t *nf_instance = NULL;
ogs_sbi_subscription_data_t *subscription_data = NULL;
ogs_sbi_response_t *sbi_response = NULL;
ogs_sbi_message_t sbi_message;
OpenAPI_nf_type_e requester_nf_type = OpenAPI_nf_type_NULL;
ogs_sbi_discovery_option_t *discovery_option = NULL;
int service_name_id = OpenAPI_service_name_NULL;
amf_sm_debug(e);
@ -740,24 +736,26 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);
@ -802,145 +800,7 @@ void amf_state_operational(ogs_fsm_t *s, amf_event_t *e)
break;
}
sbi_object = sbi_xact->sbi_object;
ogs_assert(sbi_object);
sbi_object_id = sbi_xact->sbi_object_id;
ogs_assert(sbi_object_id >= OGS_MIN_POOL_ID &&
sbi_object_id <= OGS_MAX_POOL_ID);
if (sbi_xact->user_data) {
amf_sbi_xact_ctx_t *ctx = sbi_xact->user_data;
if (ctx->ran_ue_id != OGS_INVALID_POOL_ID)
ran_ue_id = ctx->ran_ue_id;
}
service_name = sbi_xact->service_name;
requester_nf_type = sbi_xact->requester_nf_type;
discovery_option = sbi_xact->discovery_option;
ogs_sbi_xact_remove(sbi_xact);
ogs_assert(sbi_object->type > OGS_SBI_OBJ_BASE &&
sbi_object->type < OGS_SBI_OBJ_TOP);
switch(sbi_object->type) {
case OGS_SBI_OBJ_UE_TYPE:
amf_ue = amf_ue_find_by_id(sbi_object_id);
if (!amf_ue) {
ogs_error("UE(amf_ue) Context has already been removed");
break;
}
ogs_error("[%s:%s] Cannot receive SBI message "
"[type:%d,value:%d]", amf_ue->supi, amf_ue->suci,
amf_ue->nas.message_type,
amf_ue->nas.registration.value);
/*
* TS 23.502
* 4.2.2.2.2 General Registration
* If the SUCI is not provided by the UE nor retrieved from the old AMF the Identity Request
* procedure is initiated by AMF sending an Identity Request message to the UE requesting the SUCI.
*/
if (amf_ue->nas.message_type == OGS_NAS_5GS_REGISTRATION_REQUEST &&
amf_ue->nas.registration.value == OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL &&
requester_nf_type == OpenAPI_nf_type_AMF &&
discovery_option->guami_presence) {
amf_ue->amf_ue_context_transfer_state =
UE_CONTEXT_INITIAL_STATE;
if (!(AMF_UE_HAVE_SUCI(amf_ue) ||
AMF_UE_HAVE_SUPI(amf_ue))) {
CLEAR_AMF_UE_TIMER(amf_ue->t3570);
rv = nas_5gs_send_identity_request(amf_ue);
ogs_expect(rv == OGS_OK);
ogs_assert(rv != OGS_ERROR);
break;
}
} else if (amf_ue->nas.message_type ==
OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE) {
ogs_error("T3522 expired");
break;
}
r = nas_5gs_send_gmm_reject_from_sbi(amf_ue,
OGS_SBI_HTTP_STATUS_GATEWAY_TIMEOUT);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
break;
case OGS_SBI_OBJ_SESS_TYPE:
sess = amf_sess_find_by_id(sbi_object_id);
if (!sess) {
ogs_error("Session has already been removed");
break;
}
amf_ue = amf_ue_find_by_id(sess->amf_ue_id);
if (!amf_ue) {
ogs_error("UE(amf_ue) Context has already been removed");
break;
}
ogs_error("[%s:%s:%d:%d] Cannot receive SBI message",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
if (ran_ue_id < OGS_MIN_POOL_ID ||
ran_ue_id > OGS_MAX_POOL_ID) {
ogs_error("No assoc RAN-UE id [%d]", ran_ue_id);
break;
}
ran_ue = ran_ue_find_by_id(ran_ue_id);
if (!ran_ue) {
ogs_error("[%s:%s:%d:%d] "
"NG Context has already been removed",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
break;
}
if (ran_ue->amf_ue_id == OGS_INVALID_POOL_ID) {
ogs_error("[%s:%s:%d:%d] "
"RAN-UE has already been deassociated",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
break;
}
if (amf_ue->id != ran_ue->amf_ue_id) {
ogs_error("[%s:%s:%d:%d] AMF-UE mismatched [%d!=%d]",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti,
amf_ue->id, ran_ue->amf_ue_id);
break;
}
if (sess->payload_container_type) {
r = nas_5gs_send_back_gsm_message(
ran_ue, sess,
OGS_5GMM_CAUSE_PAYLOAD_WAS_NOT_FORWARDED,
AMF_NAS_BACKOFF_TIME);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
} else {
r = ngap_send_error_indication2(
ran_ue,
NGAP_Cause_PR_transport,
NGAP_CauseTransport_transport_resource_unavailable);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
break;
default:
ogs_fatal("Not implemented [%s:%d]",
OpenAPI_service_name_ToString(service_name),
sbi_object->type);
ogs_assert_if_reached();
}
amf_nnrf_handle_failed_amf_discovery(sbi_xact);
break;
default:

View file

@ -1328,14 +1328,20 @@ amf_gnb_t *amf_gnb_find_by_gnb_id(uint32_t gnb_id)
return (amf_gnb_t *)ogs_hash_get(self.gnb_id_hash, &gnb_id, sizeof(gnb_id));
}
int amf_gnb_set_gnb_id(amf_gnb_t *gnb, uint32_t gnb_id)
int amf_gnb_set_gnb_id(amf_gnb_t *gnb, uint32_t gnb_id, uint8_t gnb_id_length)
{
ogs_assert(gnb);
if (gnb_id_length < 22 || gnb_id_length > 32) {
ogs_error("Invalid gNB-ID length[%d]", gnb_id_length);
return OGS_ERROR;
}
if (gnb->gnb_id_presence == true)
ogs_hash_set(self.gnb_id_hash, &gnb->gnb_id, sizeof(gnb->gnb_id), NULL);
gnb->gnb_id = gnb_id;
gnb->gnb_id_length = gnb_id_length;
ogs_hash_set(self.gnb_id_hash, &gnb->gnb_id, sizeof(gnb->gnb_id), gnb);
gnb->gnb_id_presence = true;
@ -1676,6 +1682,11 @@ amf_ue_t *amf_ue_add(ran_ue_t *ran_ue)
ogs_list_init(&amf_ue->sess_list);
/* Initialization */
amf_ue->gnb_ostream_id = ran_ue->gnb_ostream_id;
memcpy(&amf_ue->nr_tai, &ran_ue->saved.nr_tai, sizeof(ogs_5gs_tai_t));
memcpy(&amf_ue->nr_cgi, &ran_ue->saved.nr_cgi, sizeof(ogs_nr_cgi_t));
amf_ue->nr_cgi_gnb_id_length = ran_ue->saved.nr_cgi_gnb_id_length;
amf_ue->guami = &amf_self()->served_guami[0];
amf_ue->nas.access_type = OGS_ACCESS_TYPE_3GPP;
amf_ue->nas.amf.ksi = OGS_NAS_KSI_NO_KEY_IS_AVAILABLE;
@ -1717,19 +1728,21 @@ void amf_ue_remove(amf_ue_t *amf_ue)
amf_sess_remove_all(amf_ue);
if (amf_ue->current.m_tmsi) {
ogs_hash_set(self.guti_ue_hash,
&amf_ue->current.guti, sizeof(ogs_nas_5gs_guti_t), NULL);
ogs_hash_unset_if_owner(self.guti_ue_hash,
&amf_ue->current.guti, sizeof(ogs_nas_5gs_guti_t), amf_ue);
ogs_assert(amf_m_tmsi_free(amf_ue->current.m_tmsi) == OGS_OK);
}
if (amf_ue->next.m_tmsi) {
ogs_assert(amf_m_tmsi_free(amf_ue->next.m_tmsi) == OGS_OK);
}
if (amf_ue->suci) {
ogs_hash_set(self.suci_hash, amf_ue->suci, strlen(amf_ue->suci), NULL);
ogs_hash_unset_if_owner(self.suci_hash,
amf_ue->suci, strlen(amf_ue->suci), amf_ue);
ogs_free(amf_ue->suci);
}
if (amf_ue->supi) {
ogs_hash_set(self.supi_hash, amf_ue->supi, strlen(amf_ue->supi), NULL);
ogs_hash_unset_if_owner(self.supi_hash,
amf_ue->supi, strlen(amf_ue->supi), amf_ue);
ogs_free(amf_ue->supi);
}
@ -1877,8 +1890,17 @@ amf_ue_t *amf_ue_find_by_message(ogs_nas_5gs_message_t *message)
switch (mobile_identity_header->type) {
case OGS_NAS_5GS_MOBILE_IDENTITY_SUCI:
if (mobile_identity->length <
(OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1)) {
ogs_error("Too short SUCI Mobile Identity [%d:%d]",
mobile_identity->length,
OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1);
return NULL;
}
mobile_identity_suci =
(ogs_nas_5gs_mobile_identity_suci_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_suci);
if (mobile_identity_suci->h.supi_format !=
OGS_NAS_5GS_SUPI_FORMAT_IMSI) {
@ -1914,6 +1936,14 @@ amf_ue_t *amf_ue_find_by_message(ogs_nas_5gs_message_t *message)
ogs_free(suci);
break;
case OGS_NAS_5GS_MOBILE_IDENTITY_GUTI:
if (mobile_identity->length <
sizeof(ogs_nas_5gs_mobile_identity_guti_t)) {
ogs_error("Too short 5G-GUTI Mobile Identity [%d:%d]",
mobile_identity->length,
(int)sizeof(ogs_nas_5gs_mobile_identity_guti_t));
return NULL;
}
mobile_identity_guti =
(ogs_nas_5gs_mobile_identity_guti_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_guti);
@ -1952,6 +1982,14 @@ amf_ue_t *amf_ue_find_by_message(ogs_nas_5gs_message_t *message)
switch (mobile_identity_header->type) {
case OGS_NAS_5GS_MOBILE_IDENTITY_S_TMSI:
if (mobile_identity->length <
sizeof(ogs_nas_5gs_mobile_identity_s_tmsi_t)) {
ogs_error("Too short 5G-S-TMSI Mobile Identity [%d:%d]",
mobile_identity->length,
(int)sizeof(ogs_nas_5gs_mobile_identity_s_tmsi_t));
return NULL;
}
mobile_identity_s_tmsi =
(ogs_nas_5gs_mobile_identity_s_tmsi_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_s_tmsi);
@ -1999,6 +2037,14 @@ amf_ue_t *amf_ue_find_by_message(ogs_nas_5gs_message_t *message)
switch (mobile_identity_header->type) {
case OGS_NAS_5GS_MOBILE_IDENTITY_GUTI:
if (mobile_identity->length <
sizeof(ogs_nas_5gs_mobile_identity_guti_t)) {
ogs_error("Too short 5G-GUTI Mobile Identity [%d:%d]",
mobile_identity->length,
(int)sizeof(ogs_nas_5gs_mobile_identity_guti_t));
return NULL;
}
mobile_identity_guti =
(ogs_nas_5gs_mobile_identity_guti_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_guti);
@ -2122,15 +2168,145 @@ amf_ue_t *amf_ue_find_by_ue_context_id(char *ue_context_id)
return amf_ue;
}
/*
* Release an OLD amf_ue that collides with a freshly registering amf_ue on
* one of the UE indexes, moving any session context to the NEW amf_ue.
*
* Reached from two complementary detection points:
*
* - amf_ue_set_suci(): the UE re-attached re-using the SAME SUCI, so the
* old context is found via suci_hash (delete-path collision).
*
* - amf_ue_set_supi(): the UE re-attached with a FRESH SUCI (USIM-toggle,
* post-deregistration timer, flight-mode, push-driven re-registration,
* PoC radio attach loop), so amf_ue_set_suci() could NOT detect it
* (suci_hash miss). The old context is only discovered later, once
* AKA-Auth reveals the SUPI and supi_hash still points at the stale
* slot. Without this the slot leaks until the mobile-reachable timer
* expires (create-path collision).
*
* Unifying the two paths here makes them behave identically, mirroring the
* SMF-side unification of the IMSI/SUPI indexes (commit eeeef3d1b).
*/
static void amf_ue_release_old_context(
amf_ue_t *amf_ue, amf_ue_t *old_amf_ue, const char *display)
{
amf_sess_t *old_sess = NULL;
ogs_assert(amf_ue);
ogs_assert(old_amf_ue);
ogs_assert(amf_ue != old_amf_ue);
ogs_assert(display);
ogs_warn("[%s] OLD UE Context Release", display);
if (CM_CONNECTED(old_amf_ue)) {
ran_ue_t *ran_ue = ran_ue_find_by_id(old_amf_ue->ran_ue_id);
ran_ue_t *ran_ue_holding = NULL;
/*
* Keep the old NG context until the new registration is
* authenticated. CLEAR_NG_CONTEXT(amf_ue) will then send
* UEContextReleaseCommand to the old NG-RAN context.
*
* Do not use HOLDING_NG_CONTEXT(old_amf_ue) here: that macro
* stores the holding id in old_amf_ue, but this function
* removes old_amf_ue below after moving the session context
* to the new amf_ue.
*/
ogs_warn("[%s] Holding old NG context", display);
if (ran_ue) {
int r;
ran_ue_holding =
ran_ue_find_by_id(amf_ue->ran_ue_holding_id);
if (ran_ue_holding) {
ogs_error("[%s] Holding NG context already exists",
display);
ogs_error("[%s] RAN_UE_NGAP_ID[%lld] "
"AMF_UE_NGAP_ID[%lld]",
display,
(long long)ran_ue_holding->ran_ue_ngap_id,
(long long)ran_ue_holding->amf_ue_ngap_id);
r = ngap_send_ran_ue_context_release_command(
ran_ue_holding,
NGAP_Cause_PR_nas,
NGAP_CauseNas_normal_release,
NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
} else if (amf_ue->ran_ue_holding_id !=
OGS_INVALID_POOL_ID) {
ogs_error("[%s] Holding NG context has already "
"been removed", display);
}
amf_ue->ran_ue_holding_id = OGS_INVALID_POOL_ID;
ran_ue->amf_ue_id = OGS_INVALID_POOL_ID;
ogs_warn("[%s] RAN_UE_NGAP_ID[%lld] "
"AMF_UE_NGAP_ID[%lld]",
old_amf_ue->suci,
(long long)ran_ue->ran_ue_ngap_id,
(long long)ran_ue->amf_ue_ngap_id);
ran_ue->ue_ctx_rel_action =
NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE;
ogs_timer_start(ran_ue->t_ng_holding,
amf_timer_cfg(AMF_TIMER_NG_HOLDING)->duration);
amf_ue->ran_ue_holding_id = old_amf_ue->ran_ue_id;
old_amf_ue->ran_ue_id = OGS_INVALID_POOL_ID;
} else {
ogs_error("[%s] RAN-NG Context has already been removed",
old_amf_ue->suci);
}
}
/*
* We should delete the AMF-Session Context in the AMF-UE Context.
* Otherwise, all unnecessary SESSIONs remain in SMF/UPF.
*
* In order to do this, AMF-Session Context should be moved from OLD
* AMF-UE Context to NEW AMF-UE Context. The stale sessions are then
* released towards the SMF through the NEW context's registration flow
* (AMF_RELEASE_SM_CONTEXT_REGISTRATION_ACCEPT), each release-completion
* draining one session via AMF_SESS_CLEAR().
*
* Note that we should NOT send Session-Release to the SMF at this
* point, and we must NOT amf_ue_remove() old_amf_ue synchronously
* after a release dispatch: another SBI Transaction can cause fatal
* errors, and a synchronous removal would free the sessions (and their
* in-flight sess->sbi.xact_list entries) out from under the SBI layer.
* Moving the session list keeps every session object alive and owned
* by exactly one amf_ue at all times.
*/
ogs_assert(ogs_list_empty(&amf_ue->sess_list));
/* Phase-1 : Change AMF-UE Context in Session Context */
ogs_list_for_each(&old_amf_ue->sess_list, old_sess)
old_sess->amf_ue_id = amf_ue->id;
/* Phase-2 : Move Session Context from OLD to NEW AMF-UE Context */
memcpy(&amf_ue->sess_list,
&old_amf_ue->sess_list, sizeof(amf_ue->sess_list));
/* Phase-3 : Clear Session Context in OLD AMF-UE Context */
memset(&old_amf_ue->sess_list, 0, sizeof(old_amf_ue->sess_list));
amf_ue_remove(old_amf_ue);
}
void amf_ue_set_suci(amf_ue_t *amf_ue,
ogs_nas_5gs_mobile_identity_t *mobile_identity)
{
amf_ue_t *old_amf_ue = NULL;
amf_sess_t *old_sess = NULL;
char *suci = NULL;
ogs_assert(amf_ue);
ogs_assert(mobile_identity);
ogs_assert(mobile_identity->buffer);
ogs_assert(mobile_identity->length >=
(OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1));
suci = ogs_nas_5gs_suci_from_mobile_identity(mobile_identity);
ogs_assert(suci);
@ -2141,54 +2317,14 @@ void amf_ue_set_suci(amf_ue_t *amf_ue,
/* Check if OLD amf_ue_t is different with NEW amf_ue_t */
if (ogs_pool_index(&amf_ue_pool, amf_ue) !=
ogs_pool_index(&amf_ue_pool, old_amf_ue)) {
ogs_warn("[%s] OLD UE Context Release", suci);
if (CM_CONNECTED(old_amf_ue)) {
ran_ue_t *ran_ue = ran_ue_find_by_id(old_amf_ue->ran_ue_id);
/* Implcit NG release */
ogs_warn("[%s] Implicit NG release", suci);
if (ran_ue) {
ogs_warn("[%s] RAN_UE_NGAP_ID[%lld] "
"AMF_UE_NGAP_ID[%lld]",
old_amf_ue->suci,
(long long)ran_ue->ran_ue_ngap_id,
(long long)ran_ue->amf_ue_ngap_id);
ran_ue_remove(ran_ue);
} else {
ogs_error("[%s] RAN-NG Context has already been removed",
old_amf_ue->suci);
}
}
/*
* We should delete the AMF-Session Context in the AMF-UE Context.
* Otherwise, all unnecessary SESSIONs remain in SMF/UPF.
*
* In order to do this, AMF-Session Context should be moved
* from OLD AMF-UE Context to NEW AMF-UE Context.
*
* If needed, The Session deletion process in NEW-AMF UE context will work.
*
* Note that we should not send Session-Release to the SMF at this point.
* Another SBI Transaction can cause fatal errors.
*/
/* Phase-1 : Change AMF-UE Context in Session Context */
ogs_list_for_each(&old_amf_ue->sess_list, old_sess)
old_sess->amf_ue_id = amf_ue->id;
/* Phase-2 : Move Session Context from OLD to NEW AMF-UE Context */
memcpy(&amf_ue->sess_list,
&old_amf_ue->sess_list, sizeof(amf_ue->sess_list));
/* Phase-3 : Clear Session Context in OLD AMF-UE Context */
memset(&old_amf_ue->sess_list, 0, sizeof(old_amf_ue->sess_list));
amf_ue_remove(old_amf_ue);
/* Same-SUCI re-attach: tear the old context down. */
amf_ue_release_old_context(amf_ue, old_amf_ue, suci);
}
}
if (amf_ue->suci) {
ogs_hash_set(self.suci_hash, amf_ue->suci, strlen(amf_ue->suci), NULL);
ogs_hash_unset_if_owner(self.suci_hash,
amf_ue->suci, strlen(amf_ue->suci), amf_ue);
ogs_free(amf_ue->suci);
}
amf_ue->suci = suci;
@ -2200,8 +2336,27 @@ void amf_ue_set_supi(amf_ue_t *amf_ue, char *supi)
ogs_assert(supi);
if (amf_ue->supi) {
ogs_hash_set(self.supi_hash, amf_ue->supi, strlen(amf_ue->supi), NULL);
/* Re-assignment: only clear our OWN supi_hash entry if it still
* points at us (see ogs_hash_unset_if_owner). */
ogs_hash_unset_if_owner(self.supi_hash,
amf_ue->supi, strlen(amf_ue->supi), amf_ue);
ogs_free(amf_ue->supi);
} else {
/*
* First SUPI assignment for this amf_ue.
*
* Fresh-SUCI re-attach orphan cleanup (create path): if another
* amf_ue already holds this SUPI, it is an orphan from a previous
* attach cycle that amf_ue_set_suci() could NOT detect, because the
* UE re-attached with a fresh SUCI (suci_hash miss). The delete-path
* guard keeps the live entry findable but does not reclaim the stale
* slot - without this the pool grows by one slot on every fresh-SUCI
* re-registration, until the mobile-reachable timer expires. Tear it
* down through the same unified path the same-SUCI re-attach uses.
*/
amf_ue_t *old_amf_ue = amf_ue_find_by_supi(supi);
if (old_amf_ue && old_amf_ue != amf_ue)
amf_ue_release_old_context(amf_ue, old_amf_ue, supi);
}
amf_ue->supi = ogs_strdup(supi);
ogs_assert(amf_ue->supi);

View file

@ -141,6 +141,7 @@ typedef struct amf_gnb_s {
bool gnb_id_presence;
uint32_t gnb_id; /* gNB_ID received from gNB */
uint8_t gnb_id_length; /* gNB-ID BIT STRING length(22..32) */
ogs_plmn_id_t plmn_id; /* gNB PLMN-ID received from gNB */
ogs_sctp_sock_t sctp; /* SCTP socket */
@ -205,6 +206,7 @@ struct ran_ue_s {
struct {
ogs_5gs_tai_t nr_tai;
ogs_nr_cgi_t nr_cgi;
uint8_t nr_cgi_gnb_id_length;
} saved;
/* NG Holding timer for removing this context */
@ -363,6 +365,7 @@ struct amf_ue_s {
uint16_t gnb_ostream_id;
ogs_5gs_tai_t nr_tai;
ogs_nr_cgi_t nr_cgi;
uint8_t nr_cgi_gnb_id_length;
ogs_time_t ue_location_timestamp;
ogs_plmn_id_t last_visited_plmn_id;
ogs_nas_ue_usage_setting_t ue_usage_setting;
@ -441,6 +444,9 @@ struct amf_ue_s {
/* Security Context */
ogs_nas_ue_security_capability_t ue_security_capability;
ogs_nas_ue_network_capability_t ue_network_capability;
/* Transient Path Switch state */
bool send_ue_security_capability_in_path_switch_ack;
#define CHECK_5G_AKA_CONFIRMATION(__aMF) \
((__aMF) && ((__aMF)->confirmation_for_5g_aka.resource_uri))
#define STORE_5G_AKA_CONFIRMATION(__aMF, __rESOURCE_URI) \
@ -542,6 +548,24 @@ struct amf_ue_s {
do { \
ran_ue_t *ran_ue_holding = NULL; \
\
ran_ue_holding = ran_ue_find_by_id((__aMF)->ran_ue_holding_id); \
if (ran_ue_holding) { \
int r; \
ogs_warn("[%s] Holding NG context already exists", \
(__aMF)->suci); \
ogs_warn("[%s] RAN_UE_NGAP_ID[%lld] AMF_UE_NGAP_ID[%lld]", \
(__aMF)->suci, \
(long long)ran_ue_holding->ran_ue_ngap_id, \
(long long)ran_ue_holding->amf_ue_ngap_id); \
r = ngap_send_ran_ue_context_release_command( \
ran_ue_holding, \
NGAP_Cause_PR_nas, NGAP_CauseNas_normal_release, \
NGAP_UE_CTX_REL_NG_CONTEXT_REMOVE, 0); \
ogs_expect(r == OGS_OK); \
} else if ((__aMF)->ran_ue_holding_id != OGS_INVALID_POOL_ID) { \
ogs_warn("[%s] Holding NG context has already been removed", \
(__aMF)->suci); \
} \
(__aMF)->ran_ue_holding_id = OGS_INVALID_POOL_ID; \
\
ran_ue_holding = ran_ue_find_by_id((__aMF)->ran_ue_id); \
@ -964,7 +988,7 @@ void amf_gnb_remove(amf_gnb_t *gnb);
void amf_gnb_remove_all(void);
amf_gnb_t *amf_gnb_find_by_addr(ogs_sockaddr_t *addr);
amf_gnb_t *amf_gnb_find_by_gnb_id(uint32_t gnb_id);
int amf_gnb_set_gnb_id(amf_gnb_t *gnb, uint32_t gnb_id);
int amf_gnb_set_gnb_id(amf_gnb_t *gnb, uint32_t gnb_id, uint8_t gnb_id_length);
int amf_gnb_sock_type(ogs_sock_t *sock);
amf_gnb_t *amf_gnb_find_by_id(ogs_pool_id_t id);

View file

@ -131,12 +131,6 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_request(amf_ue_t *amf_ue,
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
if (mobile_identity->length < OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE) {
ogs_error("The length of Mobile Identity(%d) is less then the min(%d)",
mobile_identity->length, OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE);
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
mobile_identity_header =
(ogs_nas_5gs_mobile_identity_header_t *)mobile_identity->buffer;
@ -144,8 +138,17 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_request(amf_ue_t *amf_ue,
switch (mobile_identity_header->type) {
case OGS_NAS_5GS_MOBILE_IDENTITY_SUCI:
if (mobile_identity->length <
(OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1)) {
ogs_error("Too short SUCI Mobile Identity [%d:%d]",
mobile_identity->length,
OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1);
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
mobile_identity_suci =
(ogs_nas_5gs_mobile_identity_suci_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_suci);
if (mobile_identity_suci->h.supi_format !=
OGS_NAS_5GS_SUPI_FORMAT_IMSI) {
ogs_error("Not implemented SUPI format [%d]",
@ -183,13 +186,18 @@ ogs_nas_5gmm_cause_t gmm_handle_registration_request(amf_ue_t *amf_ue,
ogs_info("[%s] SUCI", amf_ue->suci);
break;
case OGS_NAS_5GS_MOBILE_IDENTITY_GUTI:
mobile_identity_guti =
(ogs_nas_5gs_mobile_identity_guti_t *)mobile_identity->buffer;
if (!mobile_identity_guti) {
ogs_error("No mobile identity");
if (mobile_identity->length <
sizeof(ogs_nas_5gs_mobile_identity_guti_t)) {
ogs_error("Too short 5G-GUTI Mobile Identity [%d:%d]",
mobile_identity->length,
(int)sizeof(ogs_nas_5gs_mobile_identity_guti_t));
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
mobile_identity_guti =
(ogs_nas_5gs_mobile_identity_guti_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_guti);
ogs_nas_5gs_mobile_identity_guti_to_nas_guti(
mobile_identity_guti, &amf_ue->old_guti);
@ -1024,18 +1032,21 @@ ogs_nas_5gmm_cause_t gmm_handle_identity_response(amf_ue_t *amf_ue,
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
if (mobile_identity->length < OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE) {
ogs_error("The length of Mobile Identity(%d) is less then the min(%d)",
mobile_identity->length, OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE);
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
mobile_identity_header =
(ogs_nas_5gs_mobile_identity_header_t *)mobile_identity->buffer;
if (mobile_identity_header->type == OGS_NAS_5GS_MOBILE_IDENTITY_SUCI) {
if (mobile_identity->length <
(OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1)) {
ogs_error("Too short SUCI Mobile Identity [%d:%d]",
mobile_identity->length,
OGS_NAS_5GS_MOBILE_IDENTITY_SUCI_MIN_SIZE + 1);
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
mobile_identity_suci =
(ogs_nas_5gs_mobile_identity_suci_t *)mobile_identity->buffer;
ogs_assert(mobile_identity_suci);
if (mobile_identity_suci->h.supi_format !=
OGS_NAS_5GS_SUPI_FORMAT_IMSI) {
ogs_error("Not implemented SUPI format [%d]",
@ -1755,6 +1766,7 @@ static ogs_nas_5gmm_cause_t gmm_handle_nas_message_container(
ogs_nas_message_container_t *nas_message_container)
{
int gmm_cause;
uint8_t expected_message_type = 0;
ogs_pkbuf_t *nasbuf = NULL;
ogs_nas_5gs_message_t nas_message;
@ -1811,6 +1823,55 @@ static ogs_nas_5gmm_cause_t gmm_handle_nas_message_container(
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
/*
* 3GPP TS 24.501 4.4.6 / TS 33.501 6.4.6
*
* Only an initial NAS message (REGISTRATION REQUEST or SERVICE REQUEST)
* may be carried in the NAS message container IE. Reject any other
* inner message type with SEMANTICALLY_INCORRECT_MESSAGE rather than
* dispatching it.
*/
switch (nas_message.gmm.h.message_type) {
case OGS_NAS_5GS_REGISTRATION_REQUEST:
case OGS_NAS_5GS_SERVICE_REQUEST:
break;
default:
ogs_error("[%s] Unexpected NAS message type [%d] in "
"NAS message container [outer:%d]",
amf_ue->supi,
nas_message.gmm.h.message_type, message_type);
ogs_pkbuf_free(nasbuf);
return OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
}
/*
* The protected NAS message must match the procedure being resumed.
* In SecurityModeComplete, the procedure is the previously stored
* initial NAS message. Otherwise, it is the current outer message.
*
* Without this check, a SECURITY MODE COMPLETE container can carry an
* initial NAS message of a different type than the one that triggered
* the security mode control procedure. The AMF would then resume on
* the other path, bypassing per-procedure preconditions (e.g.
* allowed_nssai is populated only on the registration path) and reach
* ngap_ue_build_initial_context_setup_request() with an empty
* allowed_nssai, hitting ogs_assert(amf_ue->allowed_nssai.num_of_s_nssai)
* and aborting open5gs-amfd (issue #4422).
*/
expected_message_type =
(message_type == OGS_NAS_5GS_SECURITY_MODE_COMPLETE) ?
amf_ue->nas.message_type : message_type;
if (nas_message.gmm.h.message_type != expected_message_type) {
ogs_error("[%s] NAS message type mismatch in NAS message container "
"[expected:%d, received:%d, outer:%d]",
amf_ue->supi,
expected_message_type,
nas_message.gmm.h.message_type,
message_type);
ogs_pkbuf_free(nasbuf);
return OGS_5GMM_CAUSE_MESSAGE_NOT_COMPATIBLE_WITH_THE_PROTOCOL_STATE;
}
gmm_cause = OGS_5GMM_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE;
switch (nas_message.gmm.h.message_type) {

View file

@ -496,8 +496,9 @@ int amf_namf_comm_handle_n1_n2_message_transfer(
break;
default:
ogs_error("Not implemented ngapIeType[%d]", ngapIeType);
ogs_assert_if_reached();
ogs_error("Unsupported ngapIeType[%d]", ngapIeType);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
break;
}
response = ogs_sbi_build_response(&sendmsg, status);
@ -903,22 +904,32 @@ static int update_ambr_check_obj(cJSON *obj, ogs_bitrate_t *ambr,
static int update_ambr(OpenAPI_change_item_t *item_change,
ogs_bitrate_t *ambr, bool *ambr_changed)
{
cJSON* json = item_change->new_value->json;
cJSON *json = NULL;
if (!item_change->path) {
ogs_error("No 'path' field present");
return OGS_ERROR;
}
switch (item_change->op) {
case OpenAPI_change_type_REPLACE:
case OpenAPI_change_type_ADD:
if (!item_change->new_value || !item_change->new_value->json) {
ogs_error("No 'new_value' field present");
return OGS_ERROR;
}
json = item_change->new_value->json;
if (!strcmp(item_change->path, "")) {
if (!cJSON_IsObject(json)) {
ogs_error("Invalid type of am-data");
return OGS_ERROR;
}
return update_ambr_check_obj(
cJSON_GetObjectItemCaseSensitive(json, "subscribedUeAmbr"),
ambr, ambr_changed);
cJSON_GetObjectItemCaseSensitive(
json, "subscribedUeAmbr"),
ambr, ambr_changed);
} else if (!strcmp(item_change->path, "/subscribedUeAmbr")) {
return update_ambr_check_obj(json, ambr, ambr_changed);
} else if (!strcmp(item_change->path, "/subscribedUeAmbr/uplink")) {
@ -928,12 +939,12 @@ static int update_ambr(OpenAPI_change_item_t *item_change,
}
return OGS_OK;
case OpenAPI_change_type__REMOVE:
if (!strcmp(item_change->path, "/subscribedUeAmbr")) {
update_ambr_check_obj(NULL, ambr, ambr_changed);
return update_ambr_check_obj(NULL, ambr, ambr_changed);
}
return OGS_OK;
default:
return OGS_OK;
}
@ -1123,6 +1134,7 @@ int amf_namf_comm_handle_ue_context_transfer_request(
OpenAPI_key_amf_t Key_amf;
OpenAPI_sc_type_e Tsc_type;
OpenAPI_ue_context_transfer_req_data_t *UeContextTransferReqData = NULL;
OpenAPI_ue_context_transfer_rsp_data_t UeContextTransferRspData;
ogs_sbi_nf_instance_t *pcf_nf_instance = NULL;
@ -1156,6 +1168,14 @@ int amf_namf_comm_handle_ue_context_transfer_request(
goto cleanup;
}
UeContextTransferReqData = recvmsg->UeContextTransferReqData;
if (!UeContextTransferReqData) {
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
strerror = ogs_msprintf("[%s] No UeContextTransferReqData",
amf_ue->supi ? amf_ue->supi : recvmsg->h.resource.component[1]);
goto cleanup;
}
if (amf_ue->amf_ue_context_transfer_state != UE_CONTEXT_INITIAL_STATE) {
ogs_warn("Incorrect UE context transfer state");
}
@ -1228,7 +1248,7 @@ int amf_namf_comm_handle_ue_context_transfer_request(
MmContextList = amf_namf_comm_encode_ue_mm_context_list(amf_ue);
UeContext.mm_context_list = MmContextList;
if (recvmsg->UeContextTransferReqData->reason ==
if (UeContextTransferReqData->reason ==
OpenAPI_transfer_reason_MOBI_REG) {
SessionContextList =
amf_namf_comm_encode_ue_session_context_list(amf_ue);
@ -1415,7 +1435,7 @@ static char *amf_namf_comm_base64_encode_ue_security_capability(
/* Security guarantee */
num_of_octets = ogs_min(
num_of_octets, sizeof(ue_security_capability) + 1);
enc_len = ogs_base64_encode_len(num_of_octets);
enc_len = ogs_base64_encoded_size(num_of_octets);
enc = ogs_calloc(1, enc_len);
ogs_assert(enc);
@ -1423,7 +1443,8 @@ static char *amf_namf_comm_base64_encode_ue_security_capability(
security_octets_string[0] = (uint8_t)
OGS_NAS_5GS_REGISTRATION_REQUEST_UE_SECURITY_CAPABILITY_TYPE;
memcpy(security_octets_string + 1, &ue_security_capability, num_of_octets);
ogs_base64_encode(enc , security_octets_string, num_of_octets);
ogs_assert(ogs_base64_encode_from_buffer(enc, enc_len,
(const uint8_t *)security_octets_string, num_of_octets) > 0);
return enc;
}
@ -1457,7 +1478,7 @@ static char *amf_namf_comm_base64_encode_5gmm_capability(amf_ue_t *amf_ue)
num_of_octets = ogs_min(
num_of_octets, sizeof(ogs_nas_5gmm_capability_t) + 1);
enc_len = ogs_base64_encode_len(num_of_octets);
enc_len = ogs_base64_encoded_size(num_of_octets);
enc = ogs_calloc(1, enc_len);
ogs_assert(enc);
@ -1466,7 +1487,8 @@ static char *amf_namf_comm_base64_encode_5gmm_capability(amf_ue_t *amf_ue)
(uint8_t)OGS_NAS_5GS_REGISTRATION_REQUEST_5GMM_CAPABILITY_TYPE;
memcpy(gmm_capability_octets_string + 1,
&nas_gmm_capability, num_of_octets);
ogs_base64_encode(enc, gmm_capability_octets_string, num_of_octets);
ogs_assert(ogs_base64_encode_from_buffer(enc, enc_len,
(const uint8_t *)gmm_capability_octets_string, num_of_octets) > 0);
return enc;
}
@ -1607,9 +1629,11 @@ static ogs_nas_5gmm_capability_t
(char*) ogs_calloc(sizeof(gmm_capability) + 1, sizeof(char));
ogs_assert(gmm_capability_octets_string);
len = ogs_base64_decode(gmm_capability_octets_string, encoded);
len = ogs_base64_decode_to_buffer(
(uint8_t *)gmm_capability_octets_string,
sizeof(gmm_capability) + 1, encoded);
if (len == 0)
if (len <= 0)
ogs_error("Gmm capability not decoded");
ogs_assert(sizeof(gmm_capability_octets_string) <=
@ -1643,7 +1667,9 @@ static ogs_nas_ue_security_capability_t
(char*) ogs_calloc(sizeof(ue_security_capability) + 1, sizeof(char));
ogs_assert(ue_security_capability_octets_string);
ogs_base64_decode(ue_security_capability_octets_string, encoded);
ogs_base64_decode_to_buffer(
(uint8_t *)ue_security_capability_octets_string,
sizeof(ue_security_capability) + 1, encoded);
ogs_assert(sizeof(ue_security_capability_octets_string) <=
sizeof(ogs_nas_ue_security_capability_t) + 1);
@ -1680,6 +1706,10 @@ static void amf_namf_comm_decode_ue_mm_context_list(
int num_of_nssai_mapping = 0;
MmContext = node->data;
if (!MmContext) {
ogs_error("No MmContext");
continue;
}
AllowedNssaiList = MmContext->allowed_nssai;
NssaiMappingList = MmContext->nssai_mapping_list;
@ -1687,7 +1717,16 @@ static void amf_namf_comm_decode_ue_mm_context_list(
OpenAPI_list_for_each(AllowedNssaiList, node1) {
OpenAPI_snssai_t *AllowedNssai = node1->data;
ogs_assert(num_of_s_nssai < OGS_MAX_NUM_OF_SLICE);
if (!AllowedNssai) {
ogs_error("No allowedNssai");
continue;
}
if (num_of_s_nssai >= OGS_MAX_NUM_OF_SLICE) {
ogs_error("Too many allowedNssai [%d:%d]",
num_of_s_nssai + 1, OGS_MAX_NUM_OF_SLICE);
break;
}
amf_ue->allowed_nssai.s_nssai[num_of_s_nssai].sst =
(uint8_t)AllowedNssai->sst;
@ -1700,9 +1739,24 @@ static void amf_namf_comm_decode_ue_mm_context_list(
OpenAPI_list_for_each(NssaiMappingList, node1) {
OpenAPI_nssai_mapping_t *NssaiMapping = node1->data;
OpenAPI_snssai_t *HSnssai = NssaiMapping->h_snssai;
OpenAPI_snssai_t *HSnssai = NULL;
ogs_assert(num_of_nssai_mapping < OGS_MAX_NUM_OF_SLICE);
if (!NssaiMapping) {
ogs_error("No nssaiMapping");
continue;
}
HSnssai = NssaiMapping->h_snssai;
if (!HSnssai) {
ogs_error("No hSnssai in nssaiMappingList");
continue;
}
if (num_of_nssai_mapping >= OGS_MAX_NUM_OF_SLICE) {
ogs_error("Too many nssaiMappingList [%d:%d]",
num_of_nssai_mapping + 1, OGS_MAX_NUM_OF_SLICE);
break;
}
amf_ue->allowed_nssai.s_nssai[num_of_nssai_mapping].
mapped_hplmn_sst = HSnssai->sst;
@ -1718,6 +1772,7 @@ static void amf_namf_comm_decode_ue_mm_context_list(
MmContext->ue_security_capability);
}
}
}
static void amf_namf_comm_decode_ue_session_context_list(
@ -1846,8 +1901,7 @@ int amf_namf_comm_handle_registration_status_update_request(
ran_ue_t *ran_ue = NULL;
amf_sess_t *sess = NULL;
OpenAPI_ue_reg_status_update_req_data_t *UeRegStatusUpdateReqData =
recvmsg->UeRegStatusUpdateReqData;
OpenAPI_ue_reg_status_update_req_data_t *UeRegStatusUpdateReqData = NULL;
OpenAPI_ue_reg_status_update_rsp_data_t UeRegStatusUpdateRspData;
int status = 0;
@ -1869,6 +1923,14 @@ int amf_namf_comm_handle_registration_status_update_request(
goto cleanup;
}
UeRegStatusUpdateReqData = recvmsg->UeRegStatusUpdateReqData;
if (!UeRegStatusUpdateReqData) {
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
strerror = ogs_msprintf("[%s] No UeRegStatusUpdateReqData",
amf_ue->supi ? amf_ue->supi : recvmsg->h.resource.component[1]);
goto cleanup;
}
if (amf_ue->amf_ue_context_transfer_state != UE_CONTEXT_TRANSFER_OLD_AMF_STATE) {
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
strerror = ogs_msprintf("Incorrect UE context transfer state");
@ -1963,7 +2025,8 @@ cleanup:
ogs_assert(true == ogs_sbi_server_send_error(stream, status, NULL, strerror, NULL, NULL));
ogs_free(strerror);
amf_ue->amf_ue_context_transfer_state = UE_CONTEXT_INITIAL_STATE;
if (amf_ue)
amf_ue->amf_ue_context_transfer_state = UE_CONTEXT_INITIAL_STATE;
return OGS_ERROR;
}

View file

@ -2295,8 +2295,10 @@ ogs_pkbuf_t *ngap_build_path_switch_ack(amf_ue_t *amf_ue)
NGAP_AMF_UE_NGAP_ID_t *AMF_UE_NGAP_ID = NULL;
NGAP_RAN_UE_NGAP_ID_t *RAN_UE_NGAP_ID = NULL;
NGAP_SecurityContext_t *SecurityContext = NULL;
NGAP_UESecurityCapabilities_t *UESecurityCapabilities = NULL;
NGAP_PDUSessionResourceSwitchedList_t *PDUSessionResourceSwitchedList;
NGAP_AllowedNSSAI_t *AllowedNSSAI = NULL;
bool send_ue_security_capability = false;
ogs_assert(amf_ue);
ran_ue = ran_ue_find_by_id(amf_ue->ran_ue_id);
@ -2304,6 +2306,10 @@ ogs_pkbuf_t *ngap_build_path_switch_ack(amf_ue_t *amf_ue)
ogs_debug("PathSwitchAcknowledge");
send_ue_security_capability =
amf_ue->send_ue_security_capability_in_path_switch_ack;
amf_ue->send_ue_security_capability_in_path_switch_ack = false;
memset(&pdu, 0, sizeof (NGAP_NGAP_PDU_t));
pdu.present = NGAP_NGAP_PDU_PR_successfulOutcome;
pdu.choice.successfulOutcome = CALLOC(1, sizeof(NGAP_SuccessfulOutcome_t));
@ -2348,6 +2354,52 @@ ogs_pkbuf_t *ngap_build_path_switch_ack(amf_ue_t *amf_ue)
ogs_assert(RAN_UE_NGAP_ID);
ie->value.choice.RAN_UE_NGAP_ID = RAN_UE_NGAP_ID;
if (send_ue_security_capability) {
ie = CALLOC(1, sizeof(NGAP_PathSwitchRequestAcknowledgeIEs_t));
ASN_SEQUENCE_ADD(&PathSwitchRequestAcknowledge->protocolIEs, ie);
ie->id = NGAP_ProtocolIE_ID_id_UESecurityCapabilities;
ie->criticality = NGAP_Criticality_reject;
ie->value.present =
NGAP_PathSwitchRequestAcknowledgeIEs__value_PR_UESecurityCapabilities;
UESecurityCapabilities = CALLOC(1, sizeof(*UESecurityCapabilities));
ogs_assert(UESecurityCapabilities);
ie->value.choice.UESecurityCapabilities = UESecurityCapabilities;
UESecurityCapabilities->nRencryptionAlgorithms.size = 2;
UESecurityCapabilities->nRencryptionAlgorithms.buf =
CALLOC(UESecurityCapabilities->
nRencryptionAlgorithms.size, sizeof(uint8_t));
UESecurityCapabilities->nRencryptionAlgorithms.bits_unused = 0;
UESecurityCapabilities->nRencryptionAlgorithms.buf[0] =
(amf_ue->ue_security_capability.nr_ea << 1);
UESecurityCapabilities->nRintegrityProtectionAlgorithms.size = 2;
UESecurityCapabilities->nRintegrityProtectionAlgorithms.buf =
CALLOC(UESecurityCapabilities->
nRintegrityProtectionAlgorithms.size, sizeof(uint8_t));
UESecurityCapabilities->nRintegrityProtectionAlgorithms.bits_unused = 0;
UESecurityCapabilities->nRintegrityProtectionAlgorithms.buf[0] =
(amf_ue->ue_security_capability.nr_ia << 1);
UESecurityCapabilities->eUTRAencryptionAlgorithms.size = 2;
UESecurityCapabilities->eUTRAencryptionAlgorithms.buf =
CALLOC(UESecurityCapabilities->
eUTRAencryptionAlgorithms.size, sizeof(uint8_t));
UESecurityCapabilities->eUTRAencryptionAlgorithms.bits_unused = 0;
UESecurityCapabilities->eUTRAencryptionAlgorithms.buf[0] =
(amf_ue->ue_security_capability.eutra_ea << 1);
UESecurityCapabilities->eUTRAintegrityProtectionAlgorithms.size = 2;
UESecurityCapabilities->eUTRAintegrityProtectionAlgorithms.buf =
CALLOC(UESecurityCapabilities->
eUTRAintegrityProtectionAlgorithms.size, sizeof(uint8_t));
UESecurityCapabilities->eUTRAintegrityProtectionAlgorithms.bits_unused = 0;
UESecurityCapabilities->eUTRAintegrityProtectionAlgorithms.buf[0] =
(amf_ue->ue_security_capability.eutra_ia << 1);
}
ie = CALLOC(1, sizeof(NGAP_PathSwitchRequestAcknowledgeIEs_t));
ogs_assert(ie);
ASN_SEQUENCE_ADD(PathSwitchRequestAcknowledge->protocolIEs, ie);

File diff suppressed because it is too large Load diff

View file

@ -168,8 +168,8 @@ int ngap_send_to_nas(ran_ue_t *ran_ue,
ogs_assert(ran_ue);
ogs_assert(nasPdu);
if (nasPdu->size == 0) {
ogs_error("Empty NAS PDU");
if (nasPdu->size < sizeof(ogs_nas_5gmm_header_t)) {
ogs_error("NAS PDU too short [%d]", (int)nasPdu->size);
ran_ue_remove(ran_ue);
return OGS_ERROR;
}
@ -192,27 +192,53 @@ int ngap_send_to_nas(ran_ue_t *ran_ue,
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED:
security_header_type.integrity_protected = 1;
ogs_pkbuf_pull(nasbuf, 7);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_CIPHERED:
security_header_type.integrity_protected = 1;
security_header_type.ciphered = 1;
ogs_pkbuf_pull(nasbuf, 7);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_NEW_SECURITY_CONTEXT:
security_header_type.integrity_protected = 1;
security_header_type.new_security_context = 1;
ogs_pkbuf_pull(nasbuf, 7);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_CIPHTERD_WITH_NEW_INTEGRITY_CONTEXT:
security_header_type.integrity_protected = 1;
security_header_type.ciphered = 1;
security_header_type.new_security_context = 1;
ogs_pkbuf_pull(nasbuf, 7);
break;
default:
ogs_error("Not implemented(security header type:0x%x)",
sh->security_header_type);
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);
return OGS_ERROR;
}
/*
* Skip the 7-octet NAS security header for integrity-protected messages.
* ogs_pkbuf_pull() returns NULL when the buffer is shorter than the
* requested length, so a protected NAS-PDU that is too short to even
* contain its own security header is rejected here instead of being
* parsed from a buffer that was never advanced.
*/
if (security_header_type.integrity_protected) {
if (!ogs_pkbuf_pull(nasbuf, sizeof(ogs_nas_5gs_security_header_t))) {
ogs_error("NAS PDU too short for security header [%d]",
(int)nasPdu->size);
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);
return OGS_ERROR;
}
}
/*
* Make sure the (post-security-header) NAS message is large enough to
* hold the 5GMM header that is dereferenced through 'h' below. This
* also covers ciphered messages: the length is unchanged by decoding.
*/
if (nasbuf->len < sizeof(ogs_nas_5gmm_header_t)) {
ogs_error("NAS PDU too short for 5GMM header [%d]", (int)nasbuf->len);
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);
return OGS_ERROR;
}
@ -220,7 +246,8 @@ int ngap_send_to_nas(ran_ue_t *ran_ue,
if (amf_ue) {
if (nas_5gs_security_decode(amf_ue,
security_header_type, nasbuf) != OGS_OK) {
ogs_error("nas_eps_security_decode failed()");
ogs_error("nas_5gs_security_decode failed()");
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);
return OGS_ERROR;
}
@ -235,6 +262,16 @@ int ngap_send_to_nas(ran_ue_t *ran_ue,
ogs_error("Invalid extended_protocol_discriminator [%d]",
h->extended_protocol_discriminator);
/*
* 3GPP TS 38.413 clause 10.4:
* Logical error in the NAS PDU carried in INITIAL UE MESSAGE.
* Reply with ERROR INDICATION before cleaning up the
* (newly-created) ran_ue context.
*/
ogs_expect(OGS_OK == ngap_send_error_indication2(ran_ue,
NGAP_Cause_PR_protocol,
NGAP_CauseProtocol_semantic_error));
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);
@ -245,7 +282,21 @@ int ngap_send_to_nas(ran_ue_t *ran_ue,
h->message_type != OGS_NAS_5GS_SERVICE_REQUEST &&
h->message_type != OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE) {
ogs_error("Invalid 5GMM message type [%d]", h->message_type);
ogs_error("Invalid 5GMM message type [%d] in InitialUEMessage",
h->message_type);
/*
* 3GPP TS 38.413 clause 10.4 / TS 24.501:
* INITIAL UE MESSAGE may carry only REGISTRATION REQUEST,
* SERVICE REQUEST, or DEREGISTRATION REQUEST (from UE).
* Any other 5GMM message (e.g. IDENTITY RESPONSE) carried
* in INITIAL UE MESSAGE is a logical error - the NG-RAN
* node must use UPLINK NAS TRANSPORT instead. Reply with
* ERROR INDICATION.
*/
ogs_expect(OGS_OK == ngap_send_error_indication2(ran_ue,
NGAP_Cause_PR_protocol,
NGAP_CauseProtocol_message_not_compatible_with_receiver_state));
ogs_pkbuf_free(nasbuf);
ran_ue_remove(ran_ue);

View file

@ -22,6 +22,100 @@
#include "ngap-path.h"
#include "nnrf-handler.h"
static bool amf_nnrf_try_old_amf_discovery_fallback(
amf_ue_t *amf_ue, OpenAPI_nf_type_e requester_nf_type,
ogs_sbi_discovery_option_t *discovery_option)
{
int r;
ogs_assert(amf_ue);
/*
* TS 23.502
* 4.2.2.2.2 General Registration
* If the SUCI is not provided by the UE nor retrieved from the old AMF,
* the Identity Request procedure is initiated by AMF sending an Identity
* Request message to the UE requesting the SUCI.
*/
if (amf_ue->nas.message_type != OGS_NAS_5GS_REGISTRATION_REQUEST ||
amf_ue->nas.registration.value !=
OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL ||
requester_nf_type != OpenAPI_nf_type_AMF ||
!discovery_option || !discovery_option->guami_presence)
return false;
/*
* Returns true only when Identity Request is sent.
* The context transfer state is reset even if SUCI/SUPI already exists,
* matching the original old-AMF discovery fallback behavior.
*/
amf_ue->amf_ue_context_transfer_state = UE_CONTEXT_INITIAL_STATE;
if (AMF_UE_HAVE_SUCI(amf_ue) || AMF_UE_HAVE_SUPI(amf_ue))
return false;
CLEAR_AMF_UE_TIMER(amf_ue->t3570);
r = nas_5gs_send_identity_request(amf_ue);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return true;
}
static void amf_nnrf_send_session_failure_to_ran(
amf_ue_t *amf_ue, amf_sess_t *sess, ogs_pool_id_t ran_ue_id)
{
int r;
ran_ue_t *ran_ue = NULL;
ogs_assert(amf_ue);
ogs_assert(sess);
if (ran_ue_id < OGS_MIN_POOL_ID || ran_ue_id > OGS_MAX_POOL_ID) {
ogs_error("[%s:%s:%d:%d] No assoc RAN-UE id [%d]",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti,
(int)ran_ue_id);
return;
}
ran_ue = ran_ue_find_by_id(ran_ue_id);
if (!ran_ue) {
ogs_error("[%s:%s:%d:%d] NG Context has already been removed",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
return;
}
if (ran_ue->amf_ue_id == OGS_INVALID_POOL_ID) {
ogs_error("[%s:%s:%d:%d] RAN-UE has already been deassociated",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
return;
}
if (amf_ue->id != ran_ue->amf_ue_id) {
ogs_error("[%s:%s:%d:%d] AMF-UE mismatched [%d!=%d]",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti,
amf_ue->id, ran_ue->amf_ue_id);
return;
}
if (sess->payload_container_type) {
r = nas_5gs_send_back_gsm_message(
ran_ue, sess,
OGS_5GMM_CAUSE_PAYLOAD_WAS_NOT_FORWARDED,
AMF_NAS_BACKOFF_TIME);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
} else {
r = ngap_send_error_indication2(
ran_ue,
NGAP_Cause_PR_transport,
NGAP_CauseTransport_transport_resource_unavailable);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
}
void amf_nnrf_handle_nf_discover(
ogs_sbi_xact_t *xact, ogs_sbi_message_t *recvmsg)
{
@ -36,7 +130,6 @@ void amf_nnrf_handle_nf_discover(
ogs_sbi_discovery_option_t *discovery_option = NULL;
amf_ue_t *amf_ue = NULL;
ran_ue_t *ran_ue = NULL;
amf_sess_t *sess = NULL;
ogs_pool_id_t ran_ue_id = OGS_INVALID_POOL_ID;
@ -93,10 +186,11 @@ void amf_nnrf_handle_nf_discover(
ran_ue_id = ctx->ran_ue_id;
}
ogs_assert(ran_ue_id >= OGS_MIN_POOL_ID &&
ran_ue_id <= OGS_MAX_POOL_ID);
ran_ue = ran_ue_find_by_id(ran_ue_id);
ogs_assert(ran_ue);
/*
* ran_ue_id can remain OGS_INVALID_POOL_ID if this
* transaction was created without an associated NG context.
* Resolve ran_ue only where it is actually needed below.
*/
} else {
ogs_fatal("(NF discover) Not implemented [%s:%d]",
OpenAPI_service_name_ToString(service_name), sbi_object->type);
@ -108,38 +202,22 @@ void amf_nnrf_handle_nf_discover(
nf_instance = ogs_sbi_nf_instance_find_by_discovery_param(
target_nf_type, requester_nf_type, discovery_option);
if (!nf_instance) {
ogs_sbi_xact_remove(xact);
switch(sbi_object->type) {
case OGS_SBI_OBJ_UE_TYPE:
amf_ue = (amf_ue_t *)sbi_object;
ogs_assert(amf_ue);
ogs_warn("[%s] (NF discover) No [%s]", amf_ue->suci,
OpenAPI_service_name_ToString(service_name));
/*
* TS 23.502
* 4.2.2.2.2 General Registration
* If the SUCI is not provided by the UE nor retrieved from the old AMF the Identity Request
* procedure is initiated by AMF sending an Identity Request message to the UE requesting the SUCI.
*/
if (amf_nnrf_try_old_amf_discovery_fallback(
amf_ue, requester_nf_type, discovery_option))
break;
if (amf_ue->nas.message_type == OGS_NAS_5GS_REGISTRATION_REQUEST &&
amf_ue->nas.registration.value == OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL &&
requester_nf_type == OpenAPI_nf_type_AMF &&
discovery_option->guami_presence) {
amf_ue->amf_ue_context_transfer_state =
UE_CONTEXT_INITIAL_STATE;
ogs_sbi_xact_remove(xact);
if (!(AMF_UE_HAVE_SUCI(amf_ue) ||
AMF_UE_HAVE_SUPI(amf_ue))) {
CLEAR_AMF_UE_TIMER(amf_ue->t3570);
r = nas_5gs_send_identity_request(amf_ue);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
break;
}
if (amf_ue->nas.message_type ==
OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE) {
ogs_error("[%s:%s] Deregistration request failed",
amf_ue->supi, amf_ue->suci);
break;
}
r = nas_5gs_send_gmm_reject_from_sbi(amf_ue,
@ -148,23 +226,10 @@ void amf_nnrf_handle_nf_discover(
ogs_assert(r != OGS_ERROR);
break;
case OGS_SBI_OBJ_SESS_TYPE:
ogs_error("[%d:%d] (NF discover) No [%s]", sess->psi, sess->pti,
OpenAPI_service_name_ToString(service_name));
if (sess->payload_container_type) {
r = nas_5gs_send_back_gsm_message(
ran_ue, sess,
OGS_5GMM_CAUSE_PAYLOAD_WAS_NOT_FORWARDED,
AMF_NAS_BACKOFF_TIME);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
} else {
r = ngap_send_error_indication2(
ran_ue,
NGAP_Cause_PR_transport,
NGAP_CauseTransport_transport_resource_unavailable);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
ogs_warn("[%d:%d] (NF discover) No [%s]", sess->psi, sess->pti,
OpenAPI_service_name_ToString(service_name));
amf_nnrf_send_session_failure_to_ran(amf_ue, sess, ran_ue_id);
break;
default:
ogs_fatal("(NF discover) Not implemented [%s:%d]",
@ -191,7 +256,11 @@ void amf_nnrf_handle_failed_amf_discovery(
ogs_sbi_discovery_option_t *discovery_option = NULL;
OpenAPI_service_name_e service_name = OpenAPI_service_name_NULL;
ogs_sbi_object_t *sbi_object = NULL;
ogs_pool_id_t sbi_object_id = OGS_INVALID_POOL_ID;
ogs_pool_id_t ran_ue_id = OGS_INVALID_POOL_ID;
amf_ue_t *amf_ue = NULL;
amf_sess_t *sess = NULL;
ogs_assert(sbi_xact);
sbi_object = sbi_xact->sbi_object;
@ -201,42 +270,76 @@ void amf_nnrf_handle_failed_amf_discovery(
requester_nf_type = sbi_xact->requester_nf_type;
ogs_assert(requester_nf_type);
sbi_object_id = sbi_xact->sbi_object_id;
ogs_assert(sbi_object_id >= OGS_MIN_POOL_ID &&
sbi_object_id <= OGS_MAX_POOL_ID);
discovery_option = sbi_xact->discovery_option;
if (sbi_xact->user_data) {
amf_sbi_xact_ctx_t *ctx = sbi_xact->user_data;
if (ctx->ran_ue_id != OGS_INVALID_POOL_ID)
ran_ue_id = ctx->ran_ue_id;
}
ogs_assert(sbi_object->type > OGS_SBI_OBJ_BASE &&
sbi_object->type < OGS_SBI_OBJ_TOP);
if (sbi_object->type == OGS_SBI_OBJ_UE_TYPE) {
ogs_sbi_xact_remove(sbi_xact);
amf_ue = (amf_ue_t *)sbi_object;
ogs_assert(amf_ue);
/*
* TS 23.502
* 4.2.2.2.2 General Registration
* If the SUCI is not provided by the UE nor retrieved from the old AMF the Identity Request
* procedure is initiated by AMF sending an Identity Request message to the UE requesting the SUCI.
*/
if (amf_ue->nas.message_type == OGS_NAS_5GS_REGISTRATION_REQUEST &&
amf_ue->nas.registration.value == OGS_NAS_5GS_REGISTRATION_TYPE_INITIAL &&
requester_nf_type == OpenAPI_nf_type_AMF &&
discovery_option->guami_presence) {
amf_ue->amf_ue_context_transfer_state =
UE_CONTEXT_INITIAL_STATE;
ogs_sbi_xact_remove(sbi_xact);
if (!(AMF_UE_HAVE_SUCI(amf_ue) ||
AMF_UE_HAVE_SUPI(amf_ue))) {
CLEAR_AMF_UE_TIMER(amf_ue->t3570);
r = nas_5gs_send_identity_request(amf_ue);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
}
switch(sbi_object->type) {
case OGS_SBI_OBJ_UE_TYPE:
amf_ue = amf_ue_find_by_id(sbi_object_id);
if (!amf_ue) {
ogs_error("UE(amf_ue) Context has already been removed");
break;
}
}
return;
ogs_error("[%s:%s] Cannot receive SBI message "
"[type:%d,value:%d]", amf_ue->supi, amf_ue->suci,
amf_ue->nas.message_type,
amf_ue->nas.registration.value);
if (amf_nnrf_try_old_amf_discovery_fallback(
amf_ue, requester_nf_type, discovery_option))
break;
if (amf_ue->nas.message_type ==
OGS_NAS_5GS_DEREGISTRATION_REQUEST_FROM_UE) {
ogs_error("[%s:%s] Deregistration request failed",
amf_ue->supi, amf_ue->suci);
break;
}
r = nas_5gs_send_gmm_reject_from_sbi(amf_ue,
OGS_SBI_HTTP_STATUS_GATEWAY_TIMEOUT);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
break;
case OGS_SBI_OBJ_SESS_TYPE:
sess = amf_sess_find_by_id(sbi_object_id);
if (!sess) {
ogs_error("Session has already been removed");
break;
}
amf_ue = amf_ue_find_by_id(sess->amf_ue_id);
if (!amf_ue) {
ogs_error("UE(amf_ue) Context has already been removed");
break;
}
ogs_error("[%s:%s:%d:%d] Cannot receive SBI message",
amf_ue->supi, amf_ue->suci, sess->psi, sess->pti);
amf_nnrf_send_session_failure_to_ran(amf_ue, sess, ran_ue_id);
break;
default:
ogs_fatal("Not implemented [%s:%d]",
OpenAPI_service_name_ToString(service_name), sbi_object->type);
ogs_assert_if_reached();
}
}

View file

@ -361,8 +361,21 @@ int amf_nsmf_pdusession_handle_update_sm_context(
AMF_UE_CLEAR_N2_TRANSFER(amf_ue, handover_request);
}
} else {
ogs_error("Invalid STATE[%d]", state);
ogs_assert_if_reached();
/*
* [Issue #4408]
* avoid abort on SMF /modify success response with
* n2SmInfoType=PDU_RES_SETUP_REQ in an unexpected AMF state.
*
* Replace ogs_assert_if_reached() with ogs_error to log the invalid state
* and keep AMF running; logs error for debugging and improves availability.
*/
ogs_error("[%s:%d] Unexpected N2 SM info type [%s] "
"for state [%d]",
amf_ue->supi, sess->psi,
OpenAPI_n2_sm_info_type_ToString(
SmContextUpdatedData->n2_sm_info_type),
state);
return OGS_ERROR;
}
break;
@ -636,14 +649,30 @@ int amf_nsmf_pdusession_handle_update_sm_context(
}
} else if (state == AMF_UPDATE_SM_CONTEXT_REGISTRATION_REQUEST) {
/* Not reached here */
ogs_assert_if_reached();
/*
* [Issue #4409]
* avoid abort on SMF /modify success response missing n2SmInfo
* during Registration Request activation.
*
* Replace ogs_assert_if_reached() with ogs_error to log the invalid state
* and keep AMF running; logs error for debugging and improves availability.
*/
ogs_error("[%s:%d] No N2 SM information in registration "
"request update", amf_ue->supi, sess->psi);
return OGS_ERROR;
} else if (state == AMF_UPDATE_SM_CONTEXT_SERVICE_REQUEST) {
/* Not reached here */
ogs_assert_if_reached();
/*
* [Issue #4409]
* avoid abort on SMF /modify success response missing n2SmInfo
* during Service Request activation.
*
* Replace ogs_assert_if_reached() with ogs_error to log the invalid state
* and keep AMF running; logs error for debugging and improves availability.
*/
ogs_error("[%s:%d] No N2 SM information in service "
"request update", amf_ue->supi, sess->psi);
return OGS_ERROR;
} else if (state == AMF_UPDATE_SM_CONTEXT_N2_RELEASED) {

View file

@ -56,20 +56,40 @@ int amf_nudm_sdm_handle_provisioned(
OpenAPI_list_for_each(gpsiList, node) {
if (node->data) {
char *gpsi = NULL;
bool is_msisdn = false;
gpsi = ogs_id_get_type(node->data);
if (gpsi) {
if (strncmp(gpsi, OGS_ID_GPSI_TYPE_MSISDN,
strlen(OGS_ID_GPSI_TYPE_MSISDN)) == 0) {
amf_ue->msisdn[amf_ue->num_of_msisdn] =
ogs_id_get_value(node->data);
ogs_assert(amf_ue->
msisdn[amf_ue->num_of_msisdn]);
amf_ue->num_of_msisdn++;
}
ogs_free(gpsi);
if (!gpsi) {
ogs_error("[%s] No type [%s]",
amf_ue->supi, (char *)node->data);
continue;
}
is_msisdn =
(strcmp(gpsi, OGS_ID_GPSI_TYPE_MSISDN) == 0);
ogs_free(gpsi);
if (!is_msisdn) {
ogs_error("[%s] Unsupported GPSI type [%s]",
amf_ue->supi, (char *)node->data);
continue;
}
if (amf_ue->num_of_msisdn >= OGS_MAX_NUM_OF_MSISDN) {
ogs_error("[%s] Ignore MSISDN; max %d reached",
amf_ue->supi, OGS_MAX_NUM_OF_MSISDN);
break;
}
amf_ue->msisdn[amf_ue->num_of_msisdn] =
ogs_id_get_value(node->data);
if (!amf_ue->msisdn[amf_ue->num_of_msisdn]) {
ogs_error("[%s] Invalid GPSI [%s]",
amf_ue->supi, (char *)node->data);
continue;
}
amf_ue->num_of_msisdn++;
}
}
}
@ -96,15 +116,25 @@ int amf_nudm_sdm_handle_provisioned(
if (DefaultSingleNssaiList) {
OpenAPI_list_for_each(DefaultSingleNssaiList, node) {
OpenAPI_snssai_t *Snssai = node->data;
ogs_slice_data_t *slice = NULL;
ogs_slice_data_t *slice =
&amf_ue->slice[amf_ue->num_of_slice];
if (Snssai) {
slice->s_nssai.sst = Snssai->sst;
slice->s_nssai.sd =
ogs_s_nssai_sd_from_string(Snssai->sd);
if (!Snssai) {
ogs_error("[%s] No S-NSSAI", amf_ue->supi);
continue;
}
if (amf_ue->num_of_slice >= OGS_MAX_NUM_OF_SLICE) {
ogs_error("[%s] Ignore Default S-NSSAI; "
"max %d reached",
amf_ue->supi, OGS_MAX_NUM_OF_SLICE);
break;
}
slice = &amf_ue->slice[amf_ue->num_of_slice];
slice->s_nssai.sst = Snssai->sst;
slice->s_nssai.sd =
ogs_s_nssai_sd_from_string(Snssai->sd);
/* DEFAULT S-NSSAI */
slice->default_indicator = true;
@ -115,15 +145,26 @@ int amf_nudm_sdm_handle_provisioned(
if (SingleNssaiList) {
OpenAPI_list_for_each(SingleNssaiList, node) {
OpenAPI_snssai_t *Snssai = node->data;
ogs_slice_data_t *slice = NULL;
ogs_slice_data_t *slice =
&amf_ue->slice[amf_ue->num_of_slice];
if (Snssai) {
slice->s_nssai.sst = Snssai->sst;
slice->s_nssai.sd =
ogs_s_nssai_sd_from_string(Snssai->sd);
if (!Snssai) {
ogs_error("[%s] No S-NSSAI", amf_ue->supi);
continue;
}
if (amf_ue->num_of_slice >=
OGS_MAX_NUM_OF_SLICE) {
ogs_error("[%s] Ignore S-NSSAI; "
"max %d reached",
amf_ue->supi, OGS_MAX_NUM_OF_SLICE);
break;
}
slice = &amf_ue->slice[amf_ue->num_of_slice];
slice->s_nssai.sst = Snssai->sst;
slice->s_nssai.sd =
ogs_s_nssai_sd_from_string(Snssai->sd);
/* Non default S-NSSAI */
slice->default_indicator = false;
@ -214,27 +255,47 @@ int amf_nudm_sdm_handle_provisioned(
DnnInfoList = SubscribedSnssaiInfo->dnn_infos;
if (DnnInfoList) {
OpenAPI_list_for_each(DnnInfoList, node2) {
ogs_session_t *session = NULL;
DnnInfo = node2->data;
if (DnnInfo) {
ogs_session_t *session =
&slice->session
[slice->num_of_session];
session->name =
ogs_strdup(DnnInfo->dnn);
ogs_assert(session->name);
if (DnnInfo->is_default_dnn_indicator ==
true) {
session->default_dnn_indicator =
DnnInfo->default_dnn_indicator;
}
slice->num_of_session++;
if (DnnInfo->is_lbo_roaming_allowed ==
true) {
session->lbo_roaming_allowed =
DnnInfo->lbo_roaming_allowed;
}
if (!DnnInfo) {
ogs_error("No DnnInfo");
continue;
}
if (!DnnInfo->dnn) {
ogs_error("No DnnInfo->dnn");
continue;
}
if (slice->num_of_session >=
OGS_MAX_NUM_OF_SESS) {
ogs_error("[%s] Too many DNNs in "
"S-NSSAI[SST:%d SD:0x%x]",
amf_ue->supi, s_nssai.sst,
s_nssai.sd.v);
break;
}
session =
&slice->session[slice->num_of_session];
session->name = ogs_strdup(DnnInfo->dnn);
ogs_assert(session->name);
if (DnnInfo->is_default_dnn_indicator ==
true) {
session->default_dnn_indicator =
DnnInfo->default_dnn_indicator;
}
if (DnnInfo->is_lbo_roaming_allowed ==
true) {
session->lbo_roaming_allowed =
DnnInfo->lbo_roaming_allowed;
}
slice->num_of_session++;
}
}
}

View file

@ -646,6 +646,31 @@ void amf_sbi_send_deactivate_all_sessions(
}
}
static void amf_sbi_release_ran_ue_on_gnb_remove(
amf_ue_t *amf_ue, ran_ue_t *ran_ue)
{
ogs_assert(amf_ue);
ogs_assert(ran_ue);
amf_ue_deassociate_ran_ue(amf_ue, ran_ue);
ran_ue_remove(ran_ue);
/*
* If the UE has already been registered, keep the AMF UE context
* and let the mobile reachable timer handle implicit deregistration.
*
* If the UE is still in registration procedure, there is no valid
* registered NAS context to keep. Since no SMF transaction is created
* in this path, remove the AMF UE context immediately.
*/
if (OGS_FSM_CHECK(&amf_ue->sm, gmm_state_registered)) {
ogs_timer_start(amf_ue->mobile_reachable.timer,
ogs_time_from_sec(amf_self()->time.t3512.value + 240));
} else {
amf_ue_remove(amf_ue);
}
}
void amf_sbi_send_deactivate_all_ue_in_gnb(amf_gnb_t *gnb, int state)
{
amf_ue_t *amf_ue = NULL;
@ -665,10 +690,8 @@ void amf_sbi_send_deactivate_all_ue_in_gnb(amf_gnb_t *gnb, int state)
new_xact_count = amf_sess_xact_count(amf_ue);
if (old_xact_count == new_xact_count) {
amf_ue_deassociate_ran_ue(amf_ue, ran_ue);
ran_ue_remove(ran_ue);
}
if (old_xact_count == new_xact_count)
amf_sbi_release_ran_ue_on_gnb_remove(amf_ue, ran_ue);
} else {
ogs_warn("amf_sbi_send_deactivate_all_ue_in_gnb()");
ogs_warn(" RAN_UE_NGAP_ID[%lld] AMF_UE_NGAP_ID[%lld] State[%d]",

View file

@ -131,6 +131,26 @@ static inline uint32_t u24_to_u32(ogs_uint24_t v)
return (x & 0xFFFFFFu);
}
static void split_nr_cgi(
uint64_t nci, uint8_t gnb_id_length,
uint32_t *gnb_id, uint32_t *cell_id)
{
int cell_id_bits;
ogs_assert(gnb_id);
ogs_assert(cell_id);
nci &= 0xFFFFFFFFFULL; /* NR Cell Identity is 36 bits */
if (gnb_id_length < 22 || gnb_id_length > 32)
gnb_id_length = 22;
cell_id_bits = 36 - gnb_id_length;
*gnb_id = (uint32_t)(nci >> cell_id_bits);
*cell_id = (uint32_t)(nci & ((1ULL << cell_id_bits) - 1));
}
/* AM policy feature labels */
static const char *am_policy_feature_names[64] = {
/*0*/ "AM Policy Association",
@ -222,9 +242,11 @@ static int add_gnb(cJSON *parent, const amf_ue_t *ue)
ran_ue_t *ran = ran_ue_find_by_id(ue->ran_ue_id);
if (ran) {
uint64_t nci = ue->nr_cgi.cell_id & 0xFFFFFFFFFULL; /* 36-bit */
uint32_t gnb_id = (uint32_t)((nci >> 14) & 0x3FFFFF);
uint32_t cell_id = (uint32_t)(nci & 0x3FFF);
uint64_t nci = ue->nr_cgi.cell_id;
uint32_t gnb_id = 0;
uint32_t cell_id = 0;
split_nr_cgi(nci, ue->nr_cgi_gnb_id_length, &gnb_id, &cell_id);
cJSON *a = cJSON_CreateNumber((double)ran->amf_ue_ngap_id);
cJSON *r = cJSON_CreateNumber((double)ran->ran_ue_ngap_id);
@ -294,9 +316,11 @@ static int add_location(cJSON *parent, const amf_ue_t *ue)
if (!p) { cJSON_Delete(cgi); cJSON_Delete(loc); return -1; }
cJSON_AddItemToObjectCS(cgi, "plmn", p);
uint64_t nci = ue->nr_cgi.cell_id & 0xFFFFFFFFFULL; /* 36-bit */
uint32_t gnb_id = (uint32_t)((nci >> 14) & 0x3FFFFF);
uint32_t cell_id = (uint32_t)(nci & 0x3FFF);
uint64_t nci = ue->nr_cgi.cell_id;
uint32_t gnb_id = 0;
uint32_t cell_id = 0;
split_nr_cgi(nci, ue->nr_cgi_gnb_id_length, &gnb_id, &cell_id);
cJSON *n = cJSON_CreateNumber((double)nci);
cJSON *g = cJSON_CreateNumber((double)gnb_id);

View file

@ -421,24 +421,26 @@ void ausf_state_operational(ogs_fsm_t *s, ausf_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);

View file

@ -175,12 +175,13 @@ void ausf_ue_remove(ausf_ue_t *ausf_ue)
ogs_free(ausf_ue->ctx_id);
ogs_assert(ausf_ue->suci);
ogs_hash_set(self.suci_hash, ausf_ue->suci, strlen(ausf_ue->suci), NULL);
ogs_hash_unset_if_owner(self.suci_hash,
ausf_ue->suci, strlen(ausf_ue->suci), ausf_ue);
ogs_free(ausf_ue->suci);
if (ausf_ue->supi) {
ogs_hash_set(self.supi_hash,
ausf_ue->supi, strlen(ausf_ue->supi), NULL);
ogs_hash_unset_if_owner(self.supi_hash,
ausf_ue->supi, strlen(ausf_ue->supi), ausf_ue);
ogs_free(ausf_ue->supi);
}

View file

@ -170,8 +170,8 @@ bool ausf_nudm_ueau_handle_get(ausf_ue_t *ausf_ue,
/* SUPI */
if (ausf_ue->supi) {
ogs_hash_set(ausf_self()->supi_hash,
ausf_ue->supi, strlen(ausf_ue->supi), NULL);
ogs_hash_unset_if_owner(ausf_self()->supi_hash,
ausf_ue->supi, strlen(ausf_ue->supi), ausf_ue);
ogs_free(ausf_ue->supi);
}
ausf_ue->supi = ogs_strdup(AuthenticationInfoResult->supi);

View file

@ -43,6 +43,7 @@ void bsf_state_operational(ogs_fsm_t *s, bsf_event_t *e)
int service_name_id = OpenAPI_service_name_NULL;
bsf_sess_t *sess = NULL;
bool invalid = false;
ogs_sbi_stream_t *stream = NULL;
ogs_pool_id_t stream_id = OGS_INVALID_POOL_ID;
@ -146,28 +147,84 @@ void bsf_state_operational(ogs_fsm_t *s, bsf_event_t *e)
(message.PcfBinding->ipv4_addr ||
message.PcfBinding->ipv6_prefix)) {
if (message.PcfBinding->ipv4_addr)
if (message.PcfBinding->ipv4_addr) {
sess = bsf_sess_find_by_ipv4addr(
message.PcfBinding->ipv4_addr,
&invalid);
if (invalid) {
ogs_error("Invalid IPv4 address [%s]",
message.PcfBinding->ipv4_addr);
if (!sess && message.PcfBinding->ipv6_prefix)
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "Invalid IPv4 address",
message.PcfBinding->ipv4_addr,
NULL));
goto cleanup;
}
}
if (!sess && message.PcfBinding->ipv6_prefix) {
sess = bsf_sess_find_by_ipv6prefix(
message.PcfBinding->ipv6_prefix,
&invalid);
if (invalid) {
ogs_error("Invalid IPv6 prefix [%s]",
message.PcfBinding->ipv6_prefix);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "Invalid IPv6 prefix",
message.PcfBinding->ipv6_prefix,
NULL));
goto cleanup;
}
}
if (!sess) {
sess = bsf_sess_add_by_ip_address(
message.PcfBinding->ipv4_addr,
message.PcfBinding->ipv6_prefix);
ogs_assert(sess);
if (!sess) {
ogs_error("Cannot add BSF session");
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "Cannot add BSF session",
NULL, NULL));
goto cleanup;
}
}
}
break;
CASE(OGS_SBI_HTTP_METHOD_GET)
if (message.param.ipv4addr)
if (message.param.ipv4addr) {
sess = bsf_sess_find_by_ipv4addr(
message.param.ipv4addr, &invalid);
if (invalid) {
ogs_error("Invalid IPv4 address [%s]",
message.param.ipv4addr);
if (!sess && message.param.ipv6prefix)
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "Invalid IPv4 address",
message.param.ipv4addr, NULL));
goto cleanup;
}
}
if (!sess && message.param.ipv6prefix) {
sess = bsf_sess_find_by_ipv6prefix(
message.param.ipv6prefix, &invalid);
if (invalid) {
ogs_error("Invalid IPv6 prefix [%s]",
message.param.ipv6prefix);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "Invalid IPv6 prefix",
message.param.ipv6prefix, NULL));
goto cleanup;
}
}
break;
DEFAULT
ogs_error("Invalid HTTP method [%s]", message.h.method);
@ -207,6 +264,7 @@ void bsf_state_operational(ogs_fsm_t *s, bsf_event_t *e)
}
/* In lib/sbi/server.c, notify_completed() releases 'request' buffer. */
cleanup:
ogs_sbi_message_free(&message);
break;
@ -396,24 +454,26 @@ void bsf_state_operational(ogs_fsm_t *s, bsf_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);

View file

@ -148,7 +148,12 @@ bsf_sess_t *bsf_sess_add_by_ip_address(
}
if (ipv6prefix_string &&
bsf_sess_set_ipv6prefix(sess, ipv6prefix_string) == false) {
ogs_error("bsf_sess_set_ipv6prefix[%s] failed", ipv4addr_string);
ogs_error("bsf_sess_set_ipv6prefix[%s] failed", ipv6prefix_string);
if (sess->ipv4addr_string) {
ogs_hash_set(self.ipv4addr_hash,
&sess->ipv4addr, sizeof(sess->ipv4addr), NULL);
ogs_free(sess->ipv4addr_string);
}
ogs_pool_free(&bsf_sess_pool, sess);
return NULL;
}
@ -273,7 +278,10 @@ bool bsf_sess_set_ipv6prefix(bsf_sess_t *sess, char *ipv6prefix_string)
return false;
}
ogs_assert(sess->ipv6prefix.len == OGS_IPV6_128_PREFIX_LEN);
if (sess->ipv6prefix.len != OGS_IPV6_128_PREFIX_LEN) {
ogs_error("Unsupported IPv6 prefix length [%d]", sess->ipv6prefix.len);
return false;
}
sess->ipv6prefix_string = ogs_strdup(ipv6prefix_string);
if (!sess->ipv6prefix_string) {
@ -313,23 +321,30 @@ bsf_sess_t *bsf_sess_find_by_binding_id(char *binding_id)
return bsf_sess_find(atoll(binding_id));
}
bsf_sess_t *bsf_sess_find_by_ipv4addr(char *ipv4addr_string)
bsf_sess_t *bsf_sess_find_by_ipv4addr(
char *ipv4addr_string, bool *invalid)
{
uint32_t ipv4addr;
int rv;
ogs_assert(ipv4addr_string);
if (invalid)
*invalid = false;
rv = ogs_ipv4_from_string(&ipv4addr, ipv4addr_string);
if (rv != OGS_OK) {
ogs_error("ogs_ipv4_from_string() failed");
if (invalid)
*invalid = true;
return NULL;
}
return ogs_hash_get(self.ipv4addr_hash, &ipv4addr, sizeof(ipv4addr));
}
bsf_sess_t *bsf_sess_find_by_ipv6prefix(char *ipv6prefix_string)
bsf_sess_t *bsf_sess_find_by_ipv6prefix(
char *ipv6prefix_string, bool *invalid)
{
int rv;
struct {
@ -339,11 +354,24 @@ bsf_sess_t *bsf_sess_find_by_ipv6prefix(char *ipv6prefix_string)
ogs_assert(ipv6prefix_string);
if (invalid)
*invalid = false;
rv = ogs_ipv6prefix_from_string(
ipv6prefix.addr6, &ipv6prefix.len, ipv6prefix_string);
ogs_assert(rv == OGS_OK);
if (rv != OGS_OK) {
ogs_error("ogs_ipv6prefix_from_string() failed");
if (invalid)
*invalid = true;
return NULL;
}
ogs_assert(ipv6prefix.len == OGS_IPV6_128_PREFIX_LEN);
if (ipv6prefix.len != OGS_IPV6_128_PREFIX_LEN) {
ogs_error("Unsupported IPv6 prefix length [%d]", ipv6prefix.len);
if (invalid)
*invalid = true;
return NULL;
}
return ogs_hash_get(self.ipv6prefix_hash,
&ipv6prefix, (ipv6prefix.len >> 3) + 1);

View file

@ -97,8 +97,10 @@ bool bsf_sess_set_ipv6prefix(bsf_sess_t *sess, char *ipv6prefix);
bsf_sess_t *bsf_sess_find(uint32_t index);
bsf_sess_t *bsf_sess_find_by_snssai_and_dnn(ogs_s_nssai_t *s_nssai, char *dnn);
bsf_sess_t *bsf_sess_find_by_binding_id(char *binding_id);
bsf_sess_t *bsf_sess_find_by_ipv4addr(char *ipv4addr_string);
bsf_sess_t *bsf_sess_find_by_ipv6prefix(char *ipv6prefix_string);
bsf_sess_t *bsf_sess_find_by_ipv4addr(
char *ipv4addr_string, bool *invalid);
bsf_sess_t *bsf_sess_find_by_ipv6prefix(
char *ipv6prefix_string, bool *invalid);
int get_sess_load(void);
#ifdef __cplusplus

View file

@ -33,6 +33,30 @@ static struct disp_hdl *hdl_cx_sar = NULL;
/* handler for Location-Info-Request cb */
static struct disp_hdl *hdl_cx_lir = NULL;
static bool hss_cx_user_name_to_bcd(
const char *user_name, char *bcd, size_t bcd_len)
{
size_t i, j = 0;
ogs_assert(bcd);
if (!user_name || bcd_len == 0)
return false;
bcd[0] = '\0';
for (i = 0; user_name[i]; i++) {
if (user_name[i] >= '0' && user_name[i] <= '9') {
if (j + 1 >= bcd_len)
return false;
bcd[j++] = user_name[i];
}
}
bcd[j] = '\0';
return ogs_bcd_string_is_valid(bcd, OGS_MAX_IMSI_BCD_LEN);
}
/* Default callback for the application. */
static int hss_ogs_diam_cx_fb_cb(struct msg **msg, struct avp *avp,
struct session *session, void *opaque, enum disp_action *act)
@ -119,7 +143,13 @@ static int hss_ogs_diam_cx_uar_cb(struct msg **msg, struct avp *avp,
goto out;
}
ogs_extract_digit_from_string(imsi_or_msisdn_bcd, user_name);
if (hss_cx_user_name_to_bcd(user_name,
imsi_or_msisdn_bcd, sizeof(imsi_or_msisdn_bcd)) == false) {
ogs_error("Invalid User-Name BCD");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
}
/* Get Public-Identity AVP (Mandatory) */
ret = fd_msg_search_avp(qry, ogs_diam_cx_public_identity, &avp);
@ -516,6 +546,13 @@ static int hss_ogs_diam_cx_mar_cb(struct msg **msg, struct avp *avp,
error_occurred = 1;
goto out;
}
if (ogs_imsi_bcd_is_valid(imsi_bcd) == false) {
ogs_error("Invalid IMSI for User-Name[%s] Public-Identity[%s]",
user_name, public_identity);
result_code = OGS_DIAM_CX_ERROR_IDENTITY_NOT_REGISTERED;
error_occurred = 1;
goto out;
}
/* Get the SIP-Auth-Data-Item AVP (Mandatory) */
ret = fd_msg_search_avp(
@ -1160,6 +1197,13 @@ static int hss_ogs_diam_cx_sar_cb(struct msg **msg, struct avp *avp,
error_occurred = 1;
goto out;
}
if (ogs_imsi_bcd_is_valid(imsi_bcd) == false) {
ogs_error("Invalid IMSI for User-Name[%s] Public-Identity[%s]",
user_name, public_identity);
result_code = OGS_DIAM_CX_ERROR_IDENTITY_NOT_REGISTERED;
error_occurred = 1;
goto out;
}
/* Check if Visited-Network-Identifier */
visited_network_identifier =

View file

@ -57,6 +57,25 @@ static void state_cleanup(struct sess_state *sess_data, os0_t sid, void *opaque)
ogs_free(sess_data);
}
static bool hss_s6a_user_name_to_imsi_bcd(
struct avp_hdr *hdr, char *imsi_bcd, size_t imsi_bcd_len)
{
ogs_assert(imsi_bcd);
if (!hdr || !hdr->avp_value || !hdr->avp_value->os.data)
return false;
if (hdr->avp_value->os.len == 0 ||
hdr->avp_value->os.len > OGS_MAX_IMSI_BCD_LEN ||
hdr->avp_value->os.len >= imsi_bcd_len)
return false;
ogs_cpystrn(imsi_bcd, (char*)hdr->avp_value->os.data,
hdr->avp_value->os.len + 1);
return ogs_imsi_bcd_is_valid(imsi_bcd);
}
/* Default callback for the application. */
static int hss_ogs_diam_s6a_fb_cb(struct msg **msg, struct avp *avp,
struct session *session, void *opaque, enum disp_action *act)
@ -148,8 +167,13 @@ static int hss_ogs_diam_s6a_air_cb(struct msg **msg, struct avp *avp,
goto out;
}
ogs_cpystrn(imsi_bcd, (char*)hdr->avp_value->os.data,
ogs_min(hdr->avp_value->os.len, OGS_MAX_IMSI_BCD_LEN)+1);
if (hss_s6a_user_name_to_imsi_bcd(
hdr, imsi_bcd, sizeof(imsi_bcd)) == false) {
ogs_error("Invalid User-Name IMSI");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
}
/* Get authentication info from database */
rv = hss_db_auth_info(imsi_bcd, &auth_info);
@ -938,7 +962,7 @@ static int hss_ogs_diam_s6a_ulr_cb(struct msg **msg, struct avp *avp,
struct avp *avpch1 = NULL;
union avp_value val;
char *imsi_bcd = NULL;
char imsi_bcd[OGS_MAX_IMSI_BCD_LEN+1];
char imeisv_bcd[OGS_MAX_IMEISV_BCD_LEN+1];
char *mme_host = NULL;
char *mme_realm = NULL;
@ -959,6 +983,7 @@ static int hss_ogs_diam_s6a_ulr_cb(struct msg **msg, struct avp *avp,
/* Initialize variables */
memset(&subscription_data, 0, sizeof(ogs_subscription_data_t));
memset(imsi_bcd, 0, sizeof(imsi_bcd));
memset(imeisv_bcd, 0, sizeof(imeisv_bcd));
memset(&visited_plmn_id, 0, sizeof(visited_plmn_id));
@ -990,11 +1015,9 @@ static int hss_ogs_diam_s6a_ulr_cb(struct msg **msg, struct avp *avp,
goto out;
}
imsi_bcd = ogs_strndup(
(char*)hdr->avp_value->os.data,
ogs_min(hdr->avp_value->os.len, OGS_MAX_IMSI_BCD_LEN) + 1);
if (!imsi_bcd) {
ogs_error("Failed to duplicate IMSI");
if (hss_s6a_user_name_to_imsi_bcd(
hdr, imsi_bcd, sizeof(imsi_bcd)) == false) {
ogs_error("Invalid User-Name IMSI");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
@ -1144,9 +1167,13 @@ static int hss_ogs_diam_s6a_ulr_cb(struct msg **msg, struct avp *avp,
}
if (strlen(imeisv_bcd) > 0) {
rv = hss_db_update_imeisv(imsi_bcd, imeisv_bcd);
if (rv != OGS_OK) {
ogs_warn("Failed to update IMEISV for IMSI: %s", imsi_bcd);
if (ogs_imeisv_bcd_is_valid(imeisv_bcd) == false) {
ogs_warn("Ignore invalid IMEISV for IMSI: %s", imsi_bcd);
} else {
rv = hss_db_update_imeisv(imsi_bcd, imeisv_bcd);
if (rv != OGS_OK) {
ogs_warn("Failed to update IMEISV for IMSI: %s", imsi_bcd);
}
}
}
}
@ -1446,7 +1473,6 @@ static int hss_ogs_diam_s6a_ulr_cb(struct msg **msg, struct avp *avp,
/* Cleanup resources */
ogs_subscription_data_free(&subscription_data);
if (imsi_bcd) ogs_free(imsi_bcd);
if (mme_host) ogs_free(mme_host);
if (mme_realm) ogs_free(mme_realm);
@ -1491,7 +1517,6 @@ out:
/* Cleanup resources */
ogs_subscription_data_free(&subscription_data);
if (imsi_bcd) ogs_free(imsi_bcd);
if (mme_host) ogs_free(mme_host);
if (mme_realm) ogs_free(mme_realm);
@ -1565,8 +1590,13 @@ static int hss_ogs_diam_s6a_pur_cb(struct msg **msg, struct avp *avp,
goto out;
}
ogs_cpystrn(imsi_bcd, (char*)hdr->avp_value->os.data,
ogs_min(hdr->avp_value->os.len, OGS_MAX_IMSI_BCD_LEN)+1);
if (hss_s6a_user_name_to_imsi_bcd(
hdr, imsi_bcd, sizeof(imsi_bcd)) == false) {
ogs_error("Invalid User-Name IMSI");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
}
/* Get subscription data from database */
rv = hss_db_subscription_data(imsi_bcd, &subscription_data);

View file

@ -29,6 +29,30 @@ static struct disp_hdl *hdl_swx_mar = NULL;
/* handler for Server-Assignment-Request cb */
static struct disp_hdl *hdl_swx_sar = NULL;
static bool hss_swx_user_name_to_imsi_bcd(
const char *user_name, char *imsi_bcd, size_t imsi_bcd_len)
{
size_t i, j = 0;
ogs_assert(imsi_bcd);
if (!user_name || imsi_bcd_len == 0)
return false;
imsi_bcd[0] = '\0';
for (i = 0; user_name[i]; i++) {
if (user_name[i] >= '0' && user_name[i] <= '9') {
if (j + 1 >= imsi_bcd_len)
return false;
imsi_bcd[j++] = user_name[i];
}
}
imsi_bcd[j] = '\0';
return ogs_imsi_bcd_is_valid(imsi_bcd);
}
/* Default callback for the application. */
static int hss_ogs_diam_swx_fb_cb(struct msg **msg, struct avp *avp,
struct session *session, void *opaque, enum disp_action *act)
@ -130,7 +154,13 @@ static int hss_ogs_diam_swx_mar_cb(struct msg **msg, struct avp *avp,
goto out;
}
ogs_extract_digit_from_string(imsi_bcd, user_name);
if (hss_swx_user_name_to_imsi_bcd(
user_name, imsi_bcd, sizeof(imsi_bcd)) == false) {
ogs_error("Invalid User-Name IMSI");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
}
/* Get the SIP-Auth-Data-Item AVP (Mandatory) */
ret = fd_msg_search_avp(
@ -668,7 +698,13 @@ static int hss_ogs_diam_swx_sar_cb(struct msg **msg, struct avp *avp,
goto out;
}
ogs_extract_digit_from_string(imsi_bcd, user_name);
if (hss_swx_user_name_to_imsi_bcd(
user_name, imsi_bcd, sizeof(imsi_bcd)) == false) {
ogs_error("Invalid User-Name IMSI");
result_code = OGS_DIAM_INVALID_AVP_VALUE;
error_occurred = 1;
goto out;
}
/* DB : HSS Subscription Data */
rv = hss_db_subscription_data(imsi_bcd, &subscription_data);

View file

@ -235,7 +235,11 @@ ogs_pkbuf_t *emm_build_attach_accept(
} else {
eps_network_feature_support->length = 1;
}
if (ogs_global_conf()->parameter.no_ims == true) {
eps_network_feature_support->ims_voice_over_ps_session_in_s1_mode = 0;
} else {
eps_network_feature_support->ims_voice_over_ps_session_in_s1_mode = 1;
}
eps_network_feature_support->extended_protocol_configuration_options = 1;
if (mme_self()->emergency.dnn)
eps_network_feature_support->emergency_bearer_services_in_s1_mode = 1;
@ -680,8 +684,13 @@ ogs_pkbuf_t *emm_build_tau_accept(mme_ue_t *mme_ue)
} else {
tau_accept->eps_network_feature_support.length = 1;
}
tau_accept->eps_network_feature_support.
ims_voice_over_ps_session_in_s1_mode = 1;
if (ogs_global_conf()->parameter.no_ims == true) {
tau_accept->eps_network_feature_support.
ims_voice_over_ps_session_in_s1_mode = 0;
} else {
tau_accept->eps_network_feature_support.
ims_voice_over_ps_session_in_s1_mode = 1;
}
tau_accept->eps_network_feature_support.
extended_protocol_configuration_options = 1;

View file

@ -743,7 +743,7 @@ static void common_register_state(ogs_fsm_t *s, mme_event_t *e,
/* check BCS regardless of active_flag */
if (mme_ue->tracking_area_update_request_presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_TYPE) {
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_PRESENT) {
ogs_info("[%s] TAU accept(active_flag=%d, BCS check)",
mme_ue->imsi_bcd,
mme_ue->nas_eps.update.active_flag);

View file

@ -24,6 +24,19 @@
#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __esm_log_domain
static bool esm_pdn_connectivity_reject_t3396_allowed(
ogs_nas_esm_cause_t esm_cause)
{
switch (esm_cause) {
case OGS_NAS_ESM_CAUSE_MISSING_OR_UNKNOWN_APN:
case OGS_NAS_ESM_CAUSE_SERVICE_OPTION_NOT_SUPPORTED:
case OGS_NAS_ESM_CAUSE_REQUESTED_SERVICE_OPTION_NOT_SUBSCRIBED:
return true;
default:
return false;
}
}
ogs_pkbuf_t *esm_build_pdn_connectivity_reject(
mme_sess_t *sess, ogs_nas_esm_cause_t esm_cause, int create_action)
{
@ -55,6 +68,21 @@ ogs_pkbuf_t *esm_build_pdn_connectivity_reject(
pdn_connectivity_reject->esm_cause = esm_cause;
if (mme_self()->time.t3396.value &&
esm_pdn_connectivity_reject_t3396_allowed(esm_cause)) {
int rv;
ogs_nas_gprs_timer_t gprs_timer;
rv = ogs_nas_gprs_timer_3_from_sec(
&gprs_timer, mme_self()->time.t3396.value);
ogs_assert(rv == OGS_OK);
pdn_connectivity_reject->presencemask |=
OGS_NAS_EPS_PDN_CONNECTIVITY_REJECT_BACK_OFF_TIMER_VALUE_PRESENT;
pdn_connectivity_reject->back_off_timer_value.length = 1;
pdn_connectivity_reject->back_off_timer_value.t = gprs_timer;
}
if (create_action == OGS_GTP_CREATE_IN_ATTACH_REQUEST)
return ogs_nas_eps_plain_encode(&message);
else

View file

@ -215,6 +215,8 @@ static int mme_context_prepare(void)
self.diam_config->cnf_port = DIAMETER_PORT;
self.diam_config->cnf_port_tls = DIAMETER_SECURE_PORT;
/* Set the default T3396 to 12 minutes */
self.time.t3396.value = 720;
/* Set the default T3412 to 9 minutes for backward compatibility. */
self.time.t3412.value = 540;
@ -297,12 +299,20 @@ static int mme_context_validation(void)
ogs_app()->file);
return OGS_ERROR;
}
if (ogs_nas_gprs_timer_from_sec(&gprs_timer, self.time.t3402.value) !=
if (self.time.t3402.value && /* Optional */
ogs_nas_gprs_timer_from_sec(&gprs_timer, self.time.t3402.value) !=
OGS_OK) {
ogs_error("Not support GPRS Timer [%d]", (int)self.time.t3402.value);
return OGS_ERROR;
}
if (!self.time.t3412.value) {
if (self.time.t3396.value && /* Optional */
ogs_nas_gprs_timer_3_from_sec(&gprs_timer, self.time.t3396.value) !=
OGS_OK) {
ogs_error("Not support GPRS Timer 3 [%d]",
(int)self.time.t3396.value);
return OGS_ERROR;
}
if (!self.time.t3412.value) { /* Mandatory */
ogs_error("No mme.time.t3412.value in '%s'",
ogs_app()->file);
return OGS_ERROR;
@ -312,7 +322,8 @@ static int mme_context_validation(void)
ogs_error("Not support GPRS Timer [%d]", (int)self.time.t3412.value);
return OGS_ERROR;
}
if (ogs_nas_gprs_timer_from_sec(&gprs_timer, self.time.t3423.value) !=
if (self.time.t3423.value && /* Optional */
ogs_nas_gprs_timer_from_sec(&gprs_timer, self.time.t3423.value) !=
OGS_OK) {
ogs_error("Not support GPRS Timer [%d]", (int)self.time.t3423.value);
return OGS_ERROR;
@ -2488,6 +2499,22 @@ int mme_context_parse_config(void)
} else
ogs_warn("unknown key `%s`", t3402_key);
}
} else if (!strcmp(time_key, "t3396")) {
ogs_yaml_iter_t t3396_iter;
ogs_yaml_iter_recurse(&time_iter, &t3396_iter);
while (ogs_yaml_iter_next(&t3396_iter)) {
const char *t3396_key =
ogs_yaml_iter_key(&t3396_iter);
ogs_assert(t3396_key);
if (!strcmp(t3396_key, "value")) {
const char *v = ogs_yaml_iter_value(&t3396_iter);
if (v)
self.time.t3396.value = atoll(v);
} else
ogs_warn("unknown key `%s`", t3396_key);
}
} else if (!strcmp(time_key, "t3412")) {
ogs_yaml_iter_t t3412_iter;
ogs_yaml_iter_recurse(&time_iter, &t3412_iter);
@ -3484,8 +3511,8 @@ void mme_ue_confirm_guti(mme_ue_t *mme_ue)
if (MME_CURRENT_GUTI_IS_AVAILABLE(mme_ue)) {
/* MME has a VALID GUTI
* As such, we need to remove previous GUTI in hash table */
ogs_hash_set(self.guti_ue_hash,
&mme_ue->current.guti, sizeof(ogs_nas_eps_guti_t), NULL);
ogs_hash_unset_if_owner(self.guti_ue_hash,
&mme_ue->current.guti, sizeof(ogs_nas_eps_guti_t), mme_ue);
ogs_assert(mme_m_tmsi_free(mme_ue->current.m_tmsi) == OGS_OK);
}
@ -3755,12 +3782,12 @@ void mme_ue_remove(mme_ue_t *mme_ue)
if (sgw_ue) sgw_ue_remove(sgw_ue);
if (mme_ue->imsi_len != 0)
ogs_hash_set(mme_self()->imsi_ue_hash,
mme_ue->imsi, mme_ue->imsi_len, NULL);
ogs_hash_unset_if_owner(mme_self()->imsi_ue_hash,
mme_ue->imsi, mme_ue->imsi_len, mme_ue);
if (MME_CURRENT_GUTI_IS_AVAILABLE(mme_ue)) {
ogs_hash_set(self.guti_ue_hash,
&mme_ue->current.guti, sizeof(ogs_nas_eps_guti_t), NULL);
ogs_hash_unset_if_owner(self.guti_ue_hash,
&mme_ue->current.guti, sizeof(ogs_nas_eps_guti_t), mme_ue);
ogs_assert(mme_m_tmsi_free(mme_ue->current.m_tmsi) == OGS_OK);
}
@ -4079,8 +4106,8 @@ int mme_ue_set_imsi(mme_ue_t *mme_ue, char *imsi_bcd)
* in mme_state_operational().
*/
if (mme_ue->imsi_len != 0)
ogs_hash_set(mme_self()->imsi_ue_hash,
mme_ue->imsi, mme_ue->imsi_len, NULL);
ogs_hash_unset_if_owner(mme_self()->imsi_ue_hash,
mme_ue->imsi, mme_ue->imsi_len, mme_ue);
ogs_cpystrn(mme_ue->imsi_bcd, imsi_bcd, OGS_MAX_IMSI_BCD_LEN+1);
ogs_bcd_to_buffer(mme_ue->imsi_bcd, mme_ue->imsi, &mme_ue->imsi_len);
@ -4094,14 +4121,59 @@ int mme_ue_set_imsi(mme_ue_t *mme_ue, char *imsi_bcd)
ogs_warn("[%s] OLD UE Context Release", mme_ue->imsi_bcd);
if (ECM_CONNECTED(old_mme_ue)) {
enb_ue_t *enb_ue = enb_ue_find_by_id(old_mme_ue->enb_ue_id);
/* Implcit S1 release */
ogs_warn("[%s] Implicit S1 release", mme_ue->imsi_bcd);
enb_ue_t *enb_ue_holding = NULL;
/*
* Keep the old S1 context until the new attach/TAU procedure
* is authenticated. CLEAR_S1_CONTEXT(mme_ue) will then send
* UEContextReleaseCommand to the old E-UTRAN context.
*
* Do not use HOLDING_S1_CONTEXT(old_mme_ue) here: that macro
* stores the holding id in old_mme_ue, but this function
* removes old_mme_ue below after moving the session context
* to the new mme_ue.
*/
ogs_warn("[%s] Holding old S1 context", mme_ue->imsi_bcd);
if (enb_ue) {
int r;
enb_ue_holding =
enb_ue_find_by_id(mme_ue->enb_ue_holding_id);
if (enb_ue_holding) {
ogs_error("[%s] Holding S1 context already exists",
mme_ue->imsi_bcd);
ogs_error("[%s] ENB_UE_S1AP_ID[%d] "
"MME_UE_S1AP_ID[%d]",
mme_ue->imsi_bcd,
enb_ue_holding->enb_ue_s1ap_id,
enb_ue_holding->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue_holding,
S1AP_Cause_PR_nas,
S1AP_CauseNas_normal_release,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
} else if (mme_ue->enb_ue_holding_id !=
OGS_INVALID_POOL_ID) {
ogs_error("[%s] Holding S1 context has already "
"been removed", mme_ue->imsi_bcd);
}
mme_ue->enb_ue_holding_id = OGS_INVALID_POOL_ID;
enb_ue->mme_ue_id = OGS_INVALID_POOL_ID;
ogs_warn("[%s] ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
old_mme_ue->imsi_bcd,
enb_ue->enb_ue_s1ap_id,
enb_ue->mme_ue_s1ap_id);
enb_ue_remove(enb_ue);
enb_ue->ue_ctx_rel_action =
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE;
ogs_timer_start(enb_ue->t_s1_holding,
mme_timer_cfg(MME_TIMER_S1_HOLDING)->duration);
mme_ue->enb_ue_holding_id = old_mme_ue->enb_ue_id;
old_mme_ue->enb_ue_id = OGS_INVALID_POOL_ID;
} else {
ogs_error("[%s] S1 Context has already been removed",
old_mme_ue->imsi_bcd);
@ -4136,6 +4208,8 @@ int mme_ue_set_imsi(mme_ue_t *mme_ue, char *imsi_bcd)
}
/* Phase-2 : Move Session Context from OLD to NEW MME-UE Context */
ogs_assert(ogs_list_empty(&mme_ue->sess_list));
memcpy(&mme_ue->sess_list,
&old_mme_ue->sess_list, sizeof(mme_ue->sess_list));

View file

@ -163,7 +163,7 @@ typedef struct mme_context_s {
struct {
struct {
ogs_time_t value; /* Timer Value(Seconds) */
} t3402, t3412, t3423;
} t3402, t3396, t3412, t3423;
} time;
struct {
@ -564,6 +564,10 @@ struct mme_ue_s {
/* Security Context */
ogs_nas_ue_network_capability_t ue_network_capability;
/* Transient Path Switch state */
bool send_ue_security_capability_in_path_switch_ack;
ogs_nas_ms_network_capability_t ms_network_capability;
ogs_nas_ue_additional_security_capability_t
ue_additional_security_capability;
@ -663,6 +667,24 @@ struct mme_ue_s {
do { \
enb_ue_t *enb_ue_holding = NULL; \
\
enb_ue_holding = enb_ue_find_by_id((__mME)->enb_ue_holding_id); \
if (enb_ue_holding) { \
int r; \
ogs_warn("[%s] Holding S1 context already exists", \
(__mME)->imsi_bcd); \
ogs_warn("[%s] ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]", \
(__mME)->imsi_bcd, \
enb_ue_holding->enb_ue_s1ap_id, \
enb_ue_holding->mme_ue_s1ap_id); \
r = s1ap_send_ue_context_release_command( \
enb_ue_holding, \
S1AP_Cause_PR_nas, S1AP_CauseNas_normal_release, \
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0); \
ogs_expect(r == OGS_OK); \
} else if ((__mME)->enb_ue_holding_id != OGS_INVALID_POOL_ID) { \
ogs_warn("[%s] Holding S1 context has already been removed", \
(__mME)->imsi_bcd); \
} \
(__mME)->enb_ue_holding_id = OGS_INVALID_POOL_ID; \
\
enb_ue_holding = enb_ue_find_by_id((__mME)->enb_ue_id); \

View file

@ -2186,6 +2186,8 @@ ogs_pkbuf_t *s1ap_build_path_switch_ack(
S1AP_ENB_UE_S1AP_ID_t *ENB_UE_S1AP_ID = NULL;
struct S1AP_E_RABToBeSwitchedULList *E_RABToBeSwitchedULList = NULL;
S1AP_SecurityContext_t *SecurityContext = NULL;
S1AP_UESecurityCapabilities_t *UESecurityCapabilities = NULL;
bool send_ue_security_capability = false;
mme_sess_t *sess = NULL;
mme_bearer_t *bearer = NULL;
@ -2197,6 +2199,10 @@ ogs_pkbuf_t *s1ap_build_path_switch_ack(
ogs_debug("PathSwitchAcknowledge");
send_ue_security_capability =
mme_ue->send_ue_security_capability_in_path_switch_ack;
mme_ue->send_ue_security_capability_in_path_switch_ack = false;
memset(&pdu, 0, sizeof (S1AP_S1AP_PDU_t));
pdu.present = S1AP_S1AP_PDU_PR_successfulOutcome;
pdu.choice.successfulOutcome =
@ -2319,6 +2325,38 @@ ogs_pkbuf_t *s1ap_build_path_switch_ack(
memcpy(SecurityContext->nextHopParameter.buf,
mme_ue->nh, SecurityContext->nextHopParameter.size);
if (send_ue_security_capability) {
ie = CALLOC(1, sizeof(S1AP_PathSwitchRequestAcknowledgeIEs_t));
ASN_SEQUENCE_ADD(&PathSwitchRequestAcknowledge->protocolIEs, ie);
ie->id = S1AP_ProtocolIE_ID_id_UESecurityCapabilities;
ie->criticality = S1AP_Criticality_ignore;
ie->value.present =
S1AP_PathSwitchRequestAcknowledgeIEs__value_PR_UESecurityCapabilities;
ie->value.choice.UESecurityCapabilities =
CALLOC(1, sizeof(*UESecurityCapabilities));
UESecurityCapabilities = ie->value.choice.UESecurityCapabilities;
ogs_assert(UESecurityCapabilities);
UESecurityCapabilities->encryptionAlgorithms.size = 2;
UESecurityCapabilities->encryptionAlgorithms.buf =
CALLOC(UESecurityCapabilities->encryptionAlgorithms.size,
sizeof(uint8_t));
UESecurityCapabilities->encryptionAlgorithms.bits_unused = 0;
UESecurityCapabilities->encryptionAlgorithms.buf[0] =
(mme_ue->ue_network_capability.eea << 1);
UESecurityCapabilities->integrityProtectionAlgorithms.size = 2;
UESecurityCapabilities->integrityProtectionAlgorithms.buf =
CALLOC(UESecurityCapabilities->
integrityProtectionAlgorithms.size,
sizeof(uint8_t));
UESecurityCapabilities->integrityProtectionAlgorithms.bits_unused = 0;
UESecurityCapabilities->integrityProtectionAlgorithms.buf[0] =
(mme_ue->ue_network_capability.eia << 1);
}
return ogs_s1ap_encode(&pdu);
}

View file

@ -83,6 +83,85 @@ static bool served_tai_is_found(mme_enb_t *enb)
return false;
}
static enb_ue_t *s1ap_find_enb_ue_by_message_ue_ids(
mme_enb_t *enb, S1AP_MME_UE_S1AP_ID_t *MME_UE_S1AP_ID,
S1AP_ENB_UE_S1AP_ID_t *ENB_UE_S1AP_ID)
{
int r;
enb_ue_t *enb_ue = NULL;
ogs_assert(enb);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%ld]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
if (enb_ue->enb_id != enb->id) {
ogs_error("MME_UE_S1AP_ID[%lld] does not belong to this eNB "
"[UE:eNB-ID:%llu, Message:eNB-ID:%llu]",
(long long)*MME_UE_S1AP_ID,
(unsigned long long)enb_ue->enb_id,
(unsigned long long)enb->id);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
if (enb_ue->enb_ue_s1ap_id != *ENB_UE_S1AP_ID) {
ogs_error("Invalid ENB_UE_S1AP_ID[%lld] for "
"MME_UE_S1AP_ID[%lld] [expected:%u]",
(long long)*ENB_UE_S1AP_ID,
(long long)*MME_UE_S1AP_ID,
enb_ue->enb_ue_s1ap_id);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return NULL;
}
return enb_ue;
}
void s1ap_handle_s1_setup_request(mme_enb_t *enb, ogs_s1ap_message_t *message)
{
char buf[OGS_ADDRSTRLEN];
@ -518,6 +597,7 @@ void s1ap_handle_initial_ue_message(mme_enb_t *enb, ogs_s1ap_message_t *message)
S1AP_CellIdentity_t *cell_ID = NULL;
enb_ue_t *enb_ue = NULL;
mme_ue_t *mme_ue_from_stmsi = NULL;
ogs_assert(enb);
ogs_assert(enb->sctp.sock);
@ -590,8 +670,6 @@ void s1ap_handle_initial_ue_message(mme_enb_t *enb, ogs_s1ap_message_t *message)
if (S_TMSI) {
served_gummei_t *served_gummei = &mme_self()->served_gummei[0];
ogs_nas_eps_guti_t nas_guti;
mme_ue_t *mme_ue = NULL;
memset(&nas_guti, 0, sizeof(ogs_nas_eps_guti_t));
/* Use the first configured plmn_id and mme group id */
@ -627,44 +705,17 @@ void s1ap_handle_initial_ue_message(mme_enb_t *enb, ogs_s1ap_message_t *message)
memcpy(&nas_guti.m_tmsi, S_TMSI->m_TMSI.buf, S_TMSI->m_TMSI.size);
nas_guti.m_tmsi = be32toh(nas_guti.m_tmsi);
mme_ue = mme_ue_find_by_guti(&nas_guti);
if (!mme_ue) {
mme_ue_from_stmsi = mme_ue_find_by_guti(&nas_guti);
if (!mme_ue_from_stmsi) {
ogs_info("Unknown UE by S_TMSI[G:%d,C:%d,M_TMSI:0x%x]",
nas_guti.mme_gid, nas_guti.mme_code, nas_guti.m_tmsi);
} else {
ogs_info(" S_TMSI[G:%d,C:%d,M_TMSI:0x%x] IMSI:[%s]",
mme_ue->current.guti.mme_gid,
mme_ue->current.guti.mme_code,
mme_ue->current.guti.m_tmsi,
MME_UE_HAVE_IMSI(mme_ue)
? mme_ue->imsi_bcd : "Unknown");
/* If NAS(mme_ue_t) has already been associated with
* older S1(enb_ue_t) context */
if (ECM_CONNECTED(mme_ue)) {
/*
* Issue #2786
*
* In cases where the UE sends an Integrity Un-Protected Attach
* Request or Service Request, there is an issue of sending
* a UEContextReleaseCommand for the OLD ENB Context.
*
* For example, if the UE switchs off and power-on after
* the first connection, the EPC sends a UEContextReleaseCommand.
*
* However, since there is no ENB context for this on the eNB,
* the eNB does not send a UEContextReleaseComplete,
* so the deletion of the ENB Context does not function properly.
*
* To solve this problem, the EPC has been modified to implicitly
* delete the ENB Context instead of sending a UEContextReleaseCommand.
*/
HOLDING_S1_CONTEXT(mme_ue);
}
enb_ue_associate_mme_ue(enb_ue, mme_ue);
ogs_debug("Mobile Reachable timer stopped for IMSI[%s]",
mme_ue->imsi_bcd);
CLEAR_MME_UE_TIMER(mme_ue->t_mobile_reachable);
mme_ue_from_stmsi->current.guti.mme_gid,
mme_ue_from_stmsi->current.guti.mme_code,
mme_ue_from_stmsi->current.guti.m_tmsi,
MME_UE_HAVE_IMSI(mme_ue_from_stmsi)
? mme_ue_from_stmsi->imsi_bcd : "Unknown");
}
}
} else {
@ -763,6 +814,36 @@ void s1ap_handle_initial_ue_message(mme_enb_t *enb, ogs_s1ap_message_t *message)
ogs_assert(r != OGS_ERROR);
return;
}
if (mme_ue_from_stmsi) {
/* If NAS(mme_ue_t) has already been associated with
* older S1(enb_ue_t) context */
if (ECM_CONNECTED(mme_ue_from_stmsi)) {
/*
* Issue #2786
*
* In cases where the UE sends an Integrity Un-Protected Attach
* Request or Service Request, there is an issue of sending
* a UEContextReleaseCommand for the OLD ENB Context.
*
* For example, if the UE switchs off and power-on after
* the first connection, the EPC sends a UEContextReleaseCommand.
*
* However, since there is no ENB context for this on the eNB,
* the eNB does not send a UEContextReleaseComplete,
* so the deletion of the ENB Context does not function properly.
*
* To solve this problem, the EPC has been modified to implicitly
* delete the ENB Context instead of sending a UEContextReleaseCommand.
*/
HOLDING_S1_CONTEXT(mme_ue_from_stmsi);
}
enb_ue_associate_mme_ue(enb_ue, mme_ue_from_stmsi);
ogs_debug("Mobile Reachable timer stopped for IMSI[%s]",
mme_ue_from_stmsi->imsi_bcd);
CLEAR_MME_UE_TIMER(mme_ue_from_stmsi->t_mobile_reachable);
}
memcpy(&enb_ue->saved.e_cgi.plmn_id, pLMNidentity->buf,
sizeof(enb_ue->saved.e_cgi.plmn_id));
memcpy(&enb_ue->saved.e_cgi.cell_id, cell_ID->buf,
@ -840,48 +921,51 @@ void s1ap_handle_uplink_nas_transport(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
ogs_debug(" ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
/*
* Stale S1 context. Dominant trigger: HOLDING_S1_CONTEXT clears
* enb_ue->mme_ue_id when a newer S1-connection arrives for the same
* NAS UE (mme_ue); the held enb_ue stays in the pool until
* CLEAR_S1_CONTEXT sends UEContextReleaseCommand after authentication
* completes on the new connection (per TS 23.401 §5.3.4.4 / mirrors
* TS 23.502 §4.2.6 for 5G: "after successfully authenticating the
* UE, the [MME/AMF] releases the old NAS signalling connection").
*
* If the eNB sends a stale UplinkNASTransport on the held IDs during
* that window, send UEContextReleaseCommand here so the eNB can
* promptly release its end of the stale RAN context. We do NOT
* forward the held NAS message to the NAS layer - the UE has
* abandoned this radio link, and replaying an old NAS message
* through the EMM state machine could re-associate the held enb_ue
* with the live mme_ue and cause responses to be sent on the wrong
* S1 connection. The t_s1_holding timer guarantees local cleanup
* even if the eNB does not respond.
*
* Mirrors the AMF NGAP handling in ngap_handle_uplink_nas_transport().
*/
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("UplinkNASTransport on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!NAS_PDU) {
ogs_error("No NAS_PDU");
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
@ -1006,15 +1090,10 @@ void s1ap_handle_uplink_nas_transport(
enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id,
enb_ue->saved.tai.tac, enb_ue->saved.e_cgi.cell_id);
/* Copy Stream-No/TAI/ECGI from enb_ue */
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (mme_ue) {
memcpy(&mme_ue->tai, &enb_ue->saved.tai, sizeof(ogs_eps_tai_t));
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
} else {
ogs_error("No UE Context in UplinkNASTransport");
}
/* Copy Stream-No/TAI/ECGI from enb_ue (mme_ue checked at function entry) */
memcpy(&mme_ue->tai, &enb_ue->saved.tai, sizeof(ogs_eps_tai_t));
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
ogs_expect(OGS_OK == s1ap_send_to_nas(
enb_ue, S1AP_ProcedureCode_id_uplinkNASTransport, NAS_PDU));
@ -1024,7 +1103,7 @@ void s1ap_handle_ue_capability_info_indication(
mme_enb_t *enb, ogs_s1ap_message_t *message)
{
char buf[OGS_ADDRSTRLEN];
int i, r;
int i;
S1AP_InitiatingMessage_t *initiatingMessage = NULL;
S1AP_UECapabilityInfoIndication_t *UECapabilityInfoIndication = NULL;
@ -1069,42 +1148,10 @@ void s1ap_handle_ue_capability_info_indication(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1168,42 +1215,10 @@ void s1ap_handle_initial_context_setup_response(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1212,7 +1227,20 @@ void s1ap_handle_initial_context_setup_response(
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("No UE(mme-ue) context");
/*
* Stale S1 context - see s1ap_handle_uplink_nas_transport()
* for the rationale. Send UEContextReleaseCommand so the eNB
* cleans up its end of the stale RAN context.
*/
ogs_error("InitialContextSetupResponse on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
@ -1375,42 +1403,10 @@ void s1ap_handle_initial_context_setup_failure(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1502,42 +1498,10 @@ void s1ap_handle_ue_context_modification_response(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1546,7 +1510,20 @@ void s1ap_handle_ue_context_modification_response(
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("No UE(mme-ue) context");
/*
* Stale S1 context - see s1ap_handle_uplink_nas_transport()
* for the rationale. Send UEContextReleaseCommand so the eNB
* cleans up its end of the stale RAN context.
*/
ogs_error("UEContextModificationResponse on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
@ -1602,42 +1579,10 @@ void s1ap_handle_ue_context_modification_failure(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1658,7 +1603,20 @@ void s1ap_handle_ue_context_modification_failure(
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("No UE(mme-ue) context");
/*
* Stale S1 context - see s1ap_handle_uplink_nas_transport()
* for the rationale. Send UEContextReleaseCommand so the eNB
* cleans up its end of the stale RAN context.
*/
ogs_error("UEContextModificationFailure on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
CLEAR_SERVICE_INDICATOR(mme_ue);
@ -1729,42 +1687,10 @@ void s1ap_handle_e_rab_setup_response(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_error("No eNB UE Context : MME_UE_S1AP_ID[%lld]",
(long long)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -1773,7 +1699,20 @@ void s1ap_handle_e_rab_setup_response(
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("No UE(mme-ue) context");
/*
* Stale S1 context - see s1ap_handle_uplink_nas_transport()
* for the rationale. Send UEContextReleaseCommand so the eNB
* cleans up its end of the stale RAN context.
*/
ogs_error("E-RABSetupResponse on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
@ -2437,42 +2376,10 @@ void s1ap_handle_e_rab_modification_indication(
ogs_debug(" IP[%s] ENB_ID[%d]",
OGS_ADDR(enb->sctp.addr, buf), enb->enb_id);
if (!ENB_UE_S1AP_ID) {
ogs_error("No ENB_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (*ENB_UE_S1AP_ID > 0x00ffffff) {
ogs_error("Invalid ENB_UE_S1AP_ID [%lx]", *ENB_UE_S1AP_ID);
r = s1ap_send_error_indication(enb, NULL, NULL,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
if (!MME_UE_S1AP_ID) {
ogs_error("No MME_UE_S1AP_ID");
r = s1ap_send_error_indication(enb, NULL, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
enb_ue = enb_ue_find_by_mme_ue_s1ap_id(*MME_UE_S1AP_ID);
enb_ue = s1ap_find_enb_ue_by_message_ue_ids(
enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID);
if (!enb_ue) {
ogs_warn("No ENB UE Context : MME_UE_S1AP_ID[%d]",
(int)*MME_UE_S1AP_ID);
r = s1ap_send_error_indication(enb,
MME_UE_S1AP_ID, NULL,
S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unknown_mme_ue_s1ap_id);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
ogs_error("%s: Failed to find eNB UE by S1AP UE IDs", __func__);
return;
}
@ -2490,7 +2397,20 @@ void s1ap_handle_e_rab_modification_indication(
mme_ue = mme_ue_find_by_id(enb_ue->mme_ue_id);
if (!mme_ue) {
ogs_error("No UE(mme-ue) context");
/*
* Stale S1 context - see s1ap_handle_uplink_nas_transport()
* for the rationale. Send UEContextReleaseCommand so the eNB
* cleans up its end of the stale RAN context.
*/
ogs_error("E-RABModificationIndication on stale S1 context "
"[MME_UE_S1AP_ID:%d] - sending UEContextReleaseCommand",
enb_ue->mme_ue_s1ap_id);
r = s1ap_send_ue_context_release_command(
enb_ue, S1AP_Cause_PR_radioNetwork,
S1AP_CauseRadioNetwork_unspecified,
S1AP_UE_CTX_REL_S1_CONTEXT_REMOVE, 0);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
@ -2783,7 +2703,8 @@ void s1ap_handle_path_switch_request(
S1AP_EncryptionAlgorithms_t *encryptionAlgorithms = NULL;
S1AP_IntegrityProtectionAlgorithms_t *integrityProtectionAlgorithms = NULL;
uint16_t eea = 0, eia = 0;
uint8_t eea0 = 0, eia0 = 0;
uint8_t received_eea = 0, received_eia = 0;
bool ue_security_capability_mismatch = false;
enb_ue_t *enb_ue = NULL;
mme_ue_t *mme_ue = NULL;
@ -2956,6 +2877,8 @@ void s1ap_handle_path_switch_request(
return;
}
mme_ue->send_ue_security_capability_in_path_switch_ack = false;
if (!SECURITY_CONTEXT_IS_VALID(mme_ue)) {
ogs_error("No Security Context");
s1apbuf = s1ap_build_path_switch_failure(
@ -2979,24 +2902,22 @@ void s1ap_handle_path_switch_request(
ogs_plmn_id_hexdump(&mme_ue->e_cgi.plmn_id),
mme_ue->e_cgi.cell_id);
/* Update ENB-UE-S1AP-ID */
enb_ue->enb_ue_s1ap_id = *ENB_UE_S1AP_ID;
/* Change enb_ue to the NEW eNB */
enb_ue_switch_to_enb(enb_ue, enb);
ogs_info(" NEW ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
if (!UESecurityCapabilities) {
ogs_error("No UESecurityCapabilities");
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
pLMNidentity = &TAI->pLMNidentity;
if (pLMNidentity->size != sizeof(enb_ue->saved.tai.plmn_id)) {
ogs_error("Invalid pLMNidentity->size = %d (expected %d)",
(int)pLMNidentity->size,
(int)sizeof(enb_ue->saved.tai.plmn_id));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
@ -3005,28 +2926,20 @@ void s1ap_handle_path_switch_request(
if (tAC->size != sizeof(enb_ue->saved.tai.tac)) {
ogs_error("Invalid tAC->size = %d (expected %d)",
(int)tAC->size, (int)sizeof(enb_ue->saved.tai.tac));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
memcpy(&enb_ue->saved.tai.plmn_id, pLMNidentity->buf,
sizeof(enb_ue->saved.tai.plmn_id));
memcpy(&enb_ue->saved.tai.tac, tAC->buf, sizeof(enb_ue->saved.tai.tac));
enb_ue->saved.tai.tac = be16toh(enb_ue->saved.tai.tac);
pLMNidentity = &EUTRAN_CGI->pLMNidentity;
if (pLMNidentity->size != sizeof(enb_ue->saved.e_cgi.plmn_id)) {
ogs_error("Invalid pLMNidentity->size = %d (expected %d)",
(int)pLMNidentity->size,
(int)sizeof(enb_ue->saved.e_cgi.plmn_id));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
@ -3036,16 +2949,86 @@ void s1ap_handle_path_switch_request(
ogs_error("Invalid cell_ID->size = %d (expected %d)",
(int)cell_ID->size,
(int)sizeof(enb_ue->saved.e_cgi.cell_id));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol, S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
encryptionAlgorithms =
&UESecurityCapabilities->encryptionAlgorithms;
integrityProtectionAlgorithms =
&UESecurityCapabilities->integrityProtectionAlgorithms;
if (encryptionAlgorithms->size != sizeof(eea)) {
ogs_error("Invalid encryptionAlgorithms->size = %d (expected %d)",
(int)encryptionAlgorithms->size,
(int)sizeof(eea));
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_message_not_compatible_with_receiver_state);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
/*
* The MME shall verify that the UE security capabilities received
* from the target eNB are the same as the UE security capabilities
* locally stored in the MME.
*
* If there is a mismatch, the MME shall send its locally stored
* UE security capabilities to the target eNB in the
* Path Switch Request Acknowledge message.
*
* Therefore, do not overwrite mme_ue->ue_network_capability
* with the value received in PathSwitchRequest.
*/
memcpy(&eea, encryptionAlgorithms->buf, sizeof(eea));
eea = be16toh(eea);
received_eea = eea >> 9;
if (integrityProtectionAlgorithms->size != sizeof(eia)) {
ogs_error("Invalid integrityProtectionAlgorithms->size = %d "
"(expected %d)",
(int)integrityProtectionAlgorithms->size,
(int)sizeof(eia));
r = s1ap_send_error_indication(enb, MME_UE_S1AP_ID, ENB_UE_S1AP_ID,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_message_not_compatible_with_receiver_state);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
memcpy(&eia, integrityProtectionAlgorithms->buf, sizeof(eia));
eia = be16toh(eia);
received_eia = eia >> 9;
if (received_eea != (mme_ue->ue_network_capability.eea & 0x7f) ||
received_eia != (mme_ue->ue_network_capability.eia & 0x7f)) {
ue_security_capability_mismatch = true;
}
/* Update ENB-UE-S1AP-ID */
enb_ue->enb_ue_s1ap_id = *ENB_UE_S1AP_ID;
/* Change enb_ue to the NEW eNB after mandatory IE validation */
enb_ue_switch_to_enb(enb_ue, enb);
ogs_info(" NEW ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
pLMNidentity = &TAI->pLMNidentity;
memcpy(&enb_ue->saved.tai.plmn_id, pLMNidentity->buf,
sizeof(enb_ue->saved.tai.plmn_id));
tAC = &TAI->tAC;
memcpy(&enb_ue->saved.tai.tac, tAC->buf, sizeof(enb_ue->saved.tai.tac));
enb_ue->saved.tai.tac = be16toh(enb_ue->saved.tai.tac);
pLMNidentity = &EUTRAN_CGI->pLMNidentity;
memcpy(&enb_ue->saved.e_cgi.plmn_id, pLMNidentity->buf,
sizeof(enb_ue->saved.e_cgi.plmn_id));
cell_ID = &EUTRAN_CGI->cell_ID;
memcpy(&enb_ue->saved.e_cgi.cell_id, cell_ID->buf,
sizeof(enb_ue->saved.e_cgi.cell_id));
enb_ue->saved.e_cgi.cell_id = (be32toh(enb_ue->saved.e_cgi.cell_id) >> 4);
@ -3063,52 +3046,18 @@ void s1ap_handle_path_switch_request(
memcpy(&mme_ue->e_cgi, &enb_ue->saved.e_cgi, sizeof(ogs_e_cgi_t));
mme_ue->ue_location_timestamp = ogs_time_now();
ogs_assert(UESecurityCapabilities);
encryptionAlgorithms =
&UESecurityCapabilities->encryptionAlgorithms;
integrityProtectionAlgorithms =
&UESecurityCapabilities->integrityProtectionAlgorithms;
if (ue_security_capability_mismatch) {
mme_ue->send_ue_security_capability_in_path_switch_ack = true;
if (encryptionAlgorithms->size != sizeof(eea)) {
ogs_error("Invalid encryptionAlgorithms->size = %d (expected %d)",
(int)encryptionAlgorithms->size,
(int)sizeof(eea));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
ogs_warn("[%s] UE Security Capability mismatch in "
"PathSwitchRequest",
MME_UE_HAVE_IMSI(mme_ue) ? mme_ue->imsi_bcd : "Unknown");
ogs_warn(" Stored EEA[0x%x] EIA[0x%x]",
mme_ue->ue_network_capability.eea,
mme_ue->ue_network_capability.eia);
ogs_warn(" Received EEA[0x%x] EIA[0x%x]",
received_eea, received_eia);
}
memcpy(&eea, encryptionAlgorithms->buf, sizeof(eea));
eea = be16toh(eea);
eea0 = mme_ue->ue_network_capability.eea0;
mme_ue->ue_network_capability.eea = eea >> 9;
mme_ue->ue_network_capability.eea0 = eea0;
if (integrityProtectionAlgorithms->size != sizeof(eia)) {
ogs_error("Invalid integrityProtectionAlgorithms->size = %d "
"(expected %d)",
(int)integrityProtectionAlgorithms->size,
(int)sizeof(eia));
r = s1ap_send_error_indication1(
enb_ue,
S1AP_Cause_PR_protocol,
S1AP_CauseProtocol_semantic_error);
ogs_expect(r == OGS_OK);
ogs_assert(r != OGS_ERROR);
return;
}
memcpy(&eia, integrityProtectionAlgorithms->buf, sizeof(eia));
eia = be16toh(eia);
eia0 = mme_ue->ue_network_capability.eia0;
mme_ue->ue_network_capability.eia = eia >> 9;
mme_ue->ue_network_capability.eia0 = eia0;
/* Update Security Context (NextHop) */
mme_ue->nhcc++;
ogs_kdf_nh_enb(mme_ue->kasme, mme_ue->nh, mme_ue->nh);
ogs_list_init(&mme_ue->bearer_to_modify_list);
@ -3198,6 +3147,18 @@ void s1ap_handle_path_switch_request(
ogs_warn("Bearer [%d] Duplicated", (int)e_rab->e_RAB_ID);
}
/*
* Update Security Context (NextHop)
*
* Defer NH/NCC derivation until every E-RAB in the PathSwitchRequest
* has been validated. An unknown E-RAB ID (or malformed GTP-TEID /
* transportLayerAddress) returns an ErrorIndication in the loop above;
* advancing the NextHop chain before that point would desynchronize
* the {NH, NCC} with the eNB with no rollback (TS 33.401 7.2.8).
*/
mme_ue->nhcc++;
ogs_kdf_nh_enb(mme_ue->kasme, mme_ue->nh, mme_ue->nh);
relocation = sgw_ue_check_if_relocated(mme_ue);
if (relocation == SGW_WITHOUT_RELOCATION) {
if (ogs_list_count(&mme_ue->bearer_to_modify_list)) {

View file

@ -173,8 +173,8 @@ int s1ap_send_to_nas(enb_ue_t *enb_ue,
ogs_assert(enb_ue);
ogs_assert(nasPdu);
if (nasPdu->size == 0) {
ogs_error("Empty NAS PDU");
if (nasPdu->size < sizeof(ogs_nas_emm_header_t)) {
ogs_error("NAS PDU too short [%d]", (int)nasPdu->size);
enb_ue_remove(enb_ue);
return OGS_ERROR;
}
@ -200,27 +200,56 @@ int s1ap_send_to_nas(enb_ue_t *enb_ue,
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED:
security_header_type.integrity_protected = 1;
ogs_pkbuf_pull(nasbuf, 6);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_CIPHERED:
security_header_type.integrity_protected = 1;
security_header_type.ciphered = 1;
ogs_pkbuf_pull(nasbuf, 6);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_NEW_SECURITY_CONTEXT:
security_header_type.integrity_protected = 1;
security_header_type.new_security_context = 1;
ogs_pkbuf_pull(nasbuf, 6);
break;
case OGS_NAS_SECURITY_HEADER_INTEGRITY_PROTECTED_AND_CIPHTERD_WITH_NEW_INTEGRITY_CONTEXT:
security_header_type.integrity_protected = 1;
security_header_type.ciphered = 1;
security_header_type.new_security_context = 1;
ogs_pkbuf_pull(nasbuf, 6);
break;
default:
ogs_error("Not implemented(security header type:0x%x)",
sh->security_header_type);
ogs_pkbuf_free(nasbuf);
enb_ue_remove(enb_ue);
return OGS_ERROR;
}
/*
* Skip the 6-octet NAS security header for integrity-protected messages.
* ogs_pkbuf_pull() returns NULL when the buffer is shorter than the
* requested length, so a protected NAS-PDU that is too short to even
* contain its own security header is rejected here instead of being
* parsed from a buffer that was never advanced.
*
* The PLAIN and SERVICE_REQUEST formats carry no security header and
* must not be pulled; both leave integrity_protected == 0.
*/
if (security_header_type.integrity_protected) {
if (!ogs_pkbuf_pull(nasbuf, sizeof(ogs_nas_eps_security_header_t))) {
ogs_error("NAS PDU too short for security header [%d]",
(int)nasPdu->size);
ogs_pkbuf_free(nasbuf);
enb_ue_remove(enb_ue);
return OGS_ERROR;
}
}
/*
* Make sure the (post-security-header) NAS message is large enough to
* hold the EMM header that is dereferenced through 'h' below. This also
* covers ciphered messages: the length is unchanged by decoding.
*/
if (nasbuf->len < sizeof(ogs_nas_emm_header_t)) {
ogs_error("NAS PDU too short for EMM header [%d]", (int)nasbuf->len);
ogs_pkbuf_free(nasbuf);
enb_ue_remove(enb_ue);
return OGS_ERROR;
}
@ -229,6 +258,7 @@ int s1ap_send_to_nas(enb_ue_t *enb_ue,
if (nas_eps_security_decode(mme_ue,
security_header_type, nasbuf) != OGS_OK) {
ogs_error("nas_eps_security_decode failed()");
ogs_pkbuf_free(nasbuf);
enb_ue_remove(enb_ue);
return OGS_ERROR;
}

View file

@ -152,7 +152,7 @@ void sgsap_handle_location_update_accept(mme_vlr_t *vlr, ogs_pkbuf_t *pkbuf)
/* check BCS regardless of active_flag */
if (mme_ue->tracking_area_update_request_presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_TYPE) {
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_PRESENT) {
ogs_info("[%s] LU accept + TAU accept(active_flag=%d, BCS)",
mme_ue->imsi_bcd,
mme_ue->nas_eps.update.active_flag);
@ -317,7 +317,7 @@ void sgsap_handle_location_update_reject(mme_vlr_t *vlr, ogs_pkbuf_t *pkbuf)
/* check BCS regardless of active_flag */
if (mme_ue->tracking_area_update_request_presencemask &
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_TYPE) {
OGS_NAS_EPS_TRACKING_AREA_UPDATE_REQUEST_EPS_BEARER_CONTEXT_STATUS_PRESENT) {
ogs_info("[%s] LU reject + TAU accept(active_flag=%d, BCS)",
mme_ue->imsi_bcd,
mme_ue->nas_eps.update.active_flag);

View file

@ -44,7 +44,8 @@
* "plmn": "99970",
* "tac_hex": "0001",
* "tac": 1
* }
* },
* "timestamp": 1778223227627488
* },
* "ambr": {
* "downlink": 1000000000,
@ -176,6 +177,17 @@ static cJSON *build_location(const mme_ue_t *ue)
if (!cJSON_AddNumberToObject(tai, "tac", (double)ue->tai.tac)) { cJSON_Delete(tai); goto end; }
cJSON_AddItemToObjectCS(loc, "tai", tai);
/* Last location update timestamp (epoch microseconds, ogs_time_t).
* A value of 0 means the location has not yet been updated (i.e.
* the UE has not completed Initial Attach / TAU / Handover /
* Service Request response since the context was created).
* Updated on Initial Attach, TAU, Handover and Service Request responses;
* bounded by Periodic TAU (T3412). Keep the field name aligned with
* AMF /ue-info, which exposes this value as location.timestamp. */
if (!cJSON_AddNumberToObject(loc, "timestamp",
(double)ue->ue_location_timestamp)) goto end;
return loc;
end:

View file

@ -137,9 +137,20 @@ int nrf_context_parse_config(void)
nrf_assoc_t *nrf_assoc_add(ogs_sbi_stream_t *stream)
{
nrf_assoc_t *assoc = NULL;
ogs_pool_id_t stream_id = OGS_INVALID_POOL_ID;
ogs_assert(stream);
/*
* The caller passes a live inbound stream taken straight out of
* the nnrf-disc request callback, so the ID must be valid here.
* Resolve it up front so we never have to dereference the raw
* stream pointer later -- by the time the Home-NRF response
* comes back the original client may already be gone.
*/
stream_id = ogs_sbi_id_from_stream(stream);
ogs_assert(stream_id >= OGS_MIN_POOL_ID && stream_id <= OGS_MAX_POOL_ID);
ogs_pool_alloc(&nrf_assoc_pool, &assoc);
if (!assoc) {
ogs_error("Maximum number of association[%d] reached",
@ -148,7 +159,7 @@ nrf_assoc_t *nrf_assoc_add(ogs_sbi_stream_t *stream)
}
memset(assoc, 0, sizeof *assoc);
assoc->stream = stream;
assoc->stream_id = stream_id;
ogs_list_add(&self.assoc_list, assoc);

View file

@ -44,7 +44,21 @@ typedef struct nrf_assoc_s nrf_assoc_t;
typedef struct nrf_assoc_s {
ogs_lnode_t lnode;
ogs_sbi_stream_t *stream;
/*
* Pool-ID of the inbound HTTP/2 stream that triggered an
* inter-PLMN NF discovery forwarding request to the Home NRF.
*
* The raw ogs_sbi_stream_t pointer must NOT be cached here.
* The original client may RST_STREAM or otherwise disconnect
* before the (possibly delayed) Home-NRF response arrives,
* in which case nghttp2 frees the stream and any cached
* pointer becomes dangling. Storing the ID and resolving it
* through ogs_sbi_stream_find_by_id() on the response path
* lets us drop the response cleanly when the originating
* stream is already gone, instead of asserting on a freed
* socket fd inside the SBI server.
*/
ogs_pool_id_t stream_id;
} nrf_assoc_t;
void nrf_context_init(void);

View file

@ -266,8 +266,33 @@ void nrf_nf_state_registered(ogs_fsm_t *s, nrf_event_t *e)
handled = nrf_nnrf_handle_nf_update(
nf_instance, stream, message);
if (handled == false)
OGS_FSM_TRAN(s, nrf_nf_state_exception);
if (handled == false) {
ogs_error("[%s] Invalid NF update [type:%s] - "
"keeping existing registration",
nf_instance->id,
OpenAPI_nf_type_ToString(
nf_instance->nf_type));
/*
* ogs_nnrf_nfm_handle_nf_profile() now guarantees
* that the live nf_instance is unchanged on failure
* (shadow-swap rebuild). nrf_nnrf_handle_nf_update()
* has already sent the HTTP error response, so we
* keep the existing registration intact - treating
* a malformed PUT/PATCH update as an implicit
* de-registration would evict an otherwise healthy
* peer because of one bad message.
*
* The previous behaviour was to transition to
* nrf_nf_state_exception, which causes the FSM
* dispatcher in nrf_state_operational() to call
* nrf_nf_fsm_fini() + ogs_sbi_nf_instance_remove().
* That eviction is the correct response for
* nrf_nf_state_will_register (initial registration
* never completed), but is wrong for
* nrf_nf_state_registered (the registration is
* still valid - only this one update was bad).
*/
}
break;
CASE(OGS_SBI_HTTP_METHOD_DELETE)

View file

@ -121,8 +121,24 @@ bool nrf_nnrf_handle_nf_register(ogs_sbi_nf_instance_t *nf_instance,
}
}
ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile);
if (ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile) == false) {
ogs_error("[%s] Invalid NFProfile", NFProfile->nf_instance_id);
/*
* Do not finalize or remove nf_instance here. This handler is called
* from the NRF NF state machine. The caller performs OGS_FSM_TRAN()
* on &nf_instance->sm immediately after this function returns, so
* freeing nf_instance here would cause a use-after-free.
*
* Return failure and let the FSM dispatcher perform cleanup after the
* transition to nrf_nf_state_exception.
*/
ogs_assert(true == ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST,
recvmsg, "Invalid NFProfile", NFProfile->nf_instance_id, NULL));
return false;
}
ogs_sbi_client_associate(nf_instance);
/* ---------------------------------------------------------- */
@ -317,8 +333,13 @@ bool nrf_nnrf_handle_nf_update(ogs_sbi_nf_instance_t *nf_instance,
CASE(OGS_SBI_PATCH_PATH_PLMN_LIST)
/* Ensure the value is not null and is a valid JSON array */
if (patch_item->value && patch_item->value->json) {
ogs_plmn_id_t new_plmn_id[
OGS_ARRAY_SIZE(nf_instance->plmn_id)];
int new_num_of_plmn_id = 0;
/* Set PLMN status to invalid */
plmn_valid = false;
memset(new_plmn_id, 0, sizeof(new_plmn_id));
plmn_array = patch_item->value->json;
if (!cJSON_IsArray(plmn_array)) {
@ -329,18 +350,13 @@ bool nrf_nnrf_handle_nf_update(ogs_sbi_nf_instance_t *nf_instance,
return false;
}
/* Clear existing PLMN data in nf_instance */
memset(nf_instance->plmn_id, 0,
sizeof(nf_instance->plmn_id));
nf_instance->num_of_plmn_id = 0;
/* Iterate through the JSON array of PLMN IDs */
cJSON_ArrayForEach(plmn_item, plmn_array) {
OpenAPI_plmn_id_t plmn_id;
memset(&plmn_id, 0, sizeof(plmn_id));
if (nf_instance->num_of_plmn_id >=
OGS_ARRAY_SIZE(nf_instance->plmn_id)) {
if (new_num_of_plmn_id >=
OGS_ARRAY_SIZE(new_plmn_id)) {
ogs_error("Exceeded maximum number of PLMN IDs");
ogs_assert(true == ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST,
@ -374,15 +390,11 @@ bool nrf_nnrf_handle_nf_update(ogs_sbi_nf_instance_t *nf_instance,
return false;
}
/*
* Convert OpenAPI_plmn_id_t to ogs_plmn_id_t
* and store in nf_instance
*/
/* Convert OpenAPI_plmn_id_t to temporary PLMN list */
ogs_sbi_parse_plmn_id(
&nf_instance->
plmn_id[nf_instance->num_of_plmn_id],
&new_plmn_id[new_num_of_plmn_id],
&plmn_id);
nf_instance->num_of_plmn_id++;
new_num_of_plmn_id++;
/* Compare with the serving PLMN list */
for (i = 0;
@ -408,6 +420,15 @@ bool nrf_nnrf_handle_nf_update(ogs_sbi_nf_instance_t *nf_instance,
"PLMN-ID not allowed", NULL, NULL));
return false;
}
/*
* Commit the new PLMN list only after all validation
* succeeds. This keeps rejected PATCH requests from
* modifying the stored NF instance state.
*/
memcpy(nf_instance->plmn_id, new_plmn_id,
sizeof(new_plmn_id));
nf_instance->num_of_plmn_id = new_num_of_plmn_id;
}
break;
DEFAULT
@ -486,7 +507,14 @@ bool nrf_nnrf_handle_nf_status_subscribe(
ogs_uuid_format(id, &uuid);
subscription_data = ogs_sbi_subscription_data_add();
ogs_assert(subscription_data);
if (!subscription_data) {
ogs_error("ogs_sbi_subscription_data_add() failed");
ogs_assert(true ==
ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR,
recvmsg, "No subscription data available", NULL, NULL));
return false;
}
ogs_sbi_subscription_data_set_id(subscription_data, id);
ogs_assert(subscription_data->id);
@ -600,7 +628,19 @@ bool nrf_nnrf_handle_nf_status_subscribe(
if (!client) {
ogs_debug("%s: ogs_sbi_client_add()", OGS_FUNC);
client = ogs_sbi_client_add(scheme, fqdn, fqdn_port, addr, addr6);
ogs_assert(client);
if (!client) {
ogs_error("ogs_sbi_client_add() failed");
ogs_assert(true ==
ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR,
recvmsg, "No SBI client available",
subscription_data->notification_uri, NULL));
ogs_free(fqdn);
ogs_freeaddrinfo(addr);
ogs_freeaddrinfo(addr6);
ogs_sbi_subscription_data_remove(subscription_data);
return false;
}
}
OGS_SBI_SETUP_CLIENT(subscription_data, client);
@ -1082,6 +1122,54 @@ bool nrf_nnrf_handle_nf_discover(
}
}
/*
* TS 33.518 4.2.2.2.1 - NF discovery authorization for specific slice
*
* When the discovery request carries S-NSSAI filters, verify that the
* requesting NF is authorized for those slices. The requester's
* registered sNssais (from its NFProfile) define the set of slices
* it may legitimately query. If the requester registered with sNssais
* and none of the requested slices match, reject with 403 Forbidden.
*/
if (discovery_option &&
discovery_option->num_of_snssais &&
discovery_option->requester_nf_instance_id) {
ogs_sbi_nf_instance_t *requester = NULL;
requester = ogs_sbi_nf_instance_find(
discovery_option->requester_nf_instance_id);
if (requester && requester->num_of_s_nssai) {
bool authorized = false;
int ri, qi;
for (qi = 0; qi < discovery_option->num_of_snssais; qi++) {
for (ri = 0; ri < requester->num_of_s_nssai; ri++) {
if (discovery_option->snssais[qi].sst ==
requester->s_nssai[ri].sst &&
discovery_option->snssais[qi].sd.v ==
requester->s_nssai[ri].sd.v) {
authorized = true;
break;
}
}
if (authorized) break;
}
if (!authorized) {
ogs_error("NF [%s] not authorized for requested S-NSSAI",
discovery_option->requester_nf_instance_id);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_FORBIDDEN,
recvmsg,
"Requester not authorized for requested S-NSSAI",
discovery_option->requester_nf_instance_id, NULL));
return false;
}
}
}
SearchResult = ogs_calloc(1, sizeof(*SearchResult));
ogs_assert(SearchResult);
@ -1197,7 +1285,13 @@ bool nrf_nnrf_handle_nf_discover(
if (!nf_instance) {
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("Can't add NRF instance due to insufficient space");
ogs_assert(true == ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_PAYLOAD_TOO_LARGE,
recvmsg, "Insufficient space", NULL, NULL));
goto cleanup;
}
ogs_sbi_nf_instance_set_type(nf_instance, OpenAPI_nf_type_NRF);
/*
@ -1229,9 +1323,18 @@ bool nrf_nnrf_handle_nf_discover(
rc = ogs_sbi_getaddr_from_uri(
&scheme, &fqdn, &fqdn_port, &addr, &addr6,
discovery_option->hnrf_uri);
if (rc == false || scheme == OpenAPI_uri_scheme_NULL)
ogs_error("Invalid URL [%s]", request->h.uri);
else {
if (rc == false || scheme == OpenAPI_uri_scheme_NULL) {
ogs_error("Invalid hnrf-uri [%s]",
discovery_option->hnrf_uri);
ogs_sbi_nf_instance_remove(nf_instance);
ogs_assert(true == ogs_sbi_server_send_error(
stream, OGS_SBI_HTTP_STATUS_BAD_REQUEST,
recvmsg, "Invalid hnrf-uri",
discovery_option->hnrf_uri, NULL));
goto cleanup;
} else {
/*
* If there is an hnrf-uri, this value creates an nf-instance->fqdn,
* which in turn creates a client->fqdn from the hnrf-uri.
@ -1360,33 +1463,54 @@ static int discover_handler(
int status, ogs_sbi_response_t *response, void *data)
{
int rv;
int ret = OGS_OK;
ogs_sbi_message_t message;
nrf_assoc_t *assoc = data;
ogs_sbi_stream_t *stream = NULL;
ogs_assert(assoc);
stream = assoc->stream;
ogs_assert(stream);
/*
* Zero-init so the unified cleanup: below can always call
* ogs_sbi_message_free() unconditionally. The free function
* is fully NULL-guarded per field, and ogs_sbi_parse_response()
* (via parse_header()) zeroes the struct itself on entry,
* so this single initialization covers every goto cleanup path.
*/
memset(&message, 0, sizeof(message));
/*
* Resolve the originating inbound stream (GH#4476).
*
* Per the lib/sbi/client.c callback convention:
* status == OGS_OK -> response is non-NULL
* OGS_DONE / TIMEUP / ERROR -> response is NULL
*
* The stream may already be gone if the original inter-PLMN
* client RST_STREAM'd or timed out before this (possibly
* delayed) Home-NRF reply arrived. All paths converge on
* cleanup: below, which dispatches over (stream, response).
*/
stream = ogs_sbi_stream_find_by_id(assoc->stream_id);
if (status != OGS_OK) {
ogs_log_message(
status == OGS_DONE ? OGS_LOG_DEBUG : OGS_LOG_WARN, 0,
"response_handler() failed [%d]", status);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL,
"response_handler() failed", NULL, NULL));
nrf_assoc_remove(assoc);
return OGS_ERROR;
ret = OGS_ERROR;
goto cleanup;
}
ogs_assert(response);
if (!stream) {
ogs_error("nnrf-disc: originating SBI stream already closed; "
"dropping delayed Home-NRF response [assoc_stream_id:%d]",
(int)assoc->stream_id);
goto cleanup;
}
rv = ogs_sbi_parse_response(&message, response);
if (rv != OGS_OK) {
ogs_error("cannot parse HTTP response");
@ -1406,11 +1530,28 @@ static int discover_handler(
handle_nf_discover_search_result(message.SearchResult);
cleanup:
ogs_expect(true == ogs_sbi_server_send_response(stream, response));
nrf_assoc_remove(assoc);
ogs_sbi_message_free(&message);
/*
* Dispatch over (stream, response):
* alive + resp -> forward (proxy: parseable or not)
* alive + null -> synthesize HTTP 500
* gone + resp -> drop orphaned response (#4476)
* gone + null -> nothing to dispose
*/
if (stream && response)
ogs_expect(true ==
ogs_sbi_server_send_response(stream, response));
else if (stream)
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL,
"response_handler() failed", NULL, NULL));
else if (response)
ogs_sbi_response_free(response);
return OGS_OK;
ogs_sbi_message_free(&message);
nrf_assoc_remove(assoc);
return ret;
}
static void handle_nf_discover_search_result(
@ -1423,6 +1564,7 @@ static void handle_nf_discover_search_result(
OpenAPI_list_for_each(SearchResult->nf_instances, node) {
OpenAPI_nf_profile_t *NFProfile = NULL;
bool nf_instance_created = false;
if (!node->data) continue;
@ -1451,9 +1593,16 @@ static void handle_nf_discover_search_result(
nf_instance = ogs_sbi_nf_instance_find(NFProfile->nf_instance_id);
if (!nf_instance) {
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
if (!nf_instance) {
ogs_error("Can't add discovered NF instance [%s:%s] "
"due to insufficient space",
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
continue;
}
ogs_sbi_nf_instance_set_id(nf_instance, NFProfile->nf_instance_id);
nf_instance_created = true;
/*
* If nrf_nf_fsm_init() is not executed, nf_instance->sm is NULL.
@ -1473,7 +1622,24 @@ static void handle_nf_discover_search_result(
}
if (NF_INSTANCE_ID_IS_OTHERS(nf_instance->id)) {
ogs_nnrf_nfm_handle_nf_profile(nf_instance, NFProfile);
if (ogs_nnrf_nfm_handle_nf_profile(
nf_instance, NFProfile) == false) {
ogs_error("[%s] (NF-discover) Invalid NFProfile [type:%s]",
NFProfile->nf_instance_id,
OpenAPI_nf_type_ToString(NFProfile->nf_type));
/*
* Only roll back nf_instances we just created here. A
* pre-existing cache entry will be corrected by the next
* inter-NRF discovery cycle; removing it now would cause
* a temporary blackhole. (No fsm_fini: this code path
* never calls nrf_nf_fsm_init() for newly added entries
* per the comment at line 1517-1522, so sm is NULL.)
*/
if (nf_instance_created)
ogs_sbi_nf_instance_remove(nf_instance);
continue;
}
ogs_sbi_client_associate(nf_instance);

View file

@ -123,19 +123,26 @@ void nrf_state_operational(ogs_fsm_t *s, nrf_event_t *e)
break;
DEFAULT
if (message.h.resource.component[1]) {
nf_instance = ogs_sbi_nf_instance_find(
message.h.resource.component[1]);
if (!message.h.resource.component[1]) {
ogs_error("No NFInstanceId");
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST,
&message, "No NFInstanceId", NULL, NULL));
break;
}
nf_instance = ogs_sbi_nf_instance_find(
message.h.resource.component[1]);
if (!nf_instance) {
SWITCH(message.h.method)
CASE(OGS_SBI_HTTP_METHOD_PUT)
if (ogs_sbi_nf_instance_maximum_number_is_reached())
{
ogs_warn("Can't add instance [%s] "
"due to insufficient space",
message.h.resource.component[1]);
nf_instance = ogs_sbi_nf_instance_add();
if (!nf_instance) {
ogs_error("Can't add instance [%s] "
"due to insufficient space",
message.h.resource.component[1]);
ogs_assert(
true ==
ogs_sbi_server_send_error(
@ -145,8 +152,6 @@ void nrf_state_operational(ogs_fsm_t *s, nrf_event_t *e)
message.h.resource.component[1], NULL));
break;
}
nf_instance = ogs_sbi_nf_instance_add();
ogs_assert(nf_instance);
ogs_sbi_nf_instance_set_id(nf_instance,
message.h.resource.component[1]);
@ -194,6 +199,14 @@ void nrf_state_operational(ogs_fsm_t *s, nrf_event_t *e)
ogs_error("[%s] State machine exception",
nf_instance->id);
/*
* Handlers invoked from the NF FSM must not free
* nf_instance before returning because the FSM
* transition writes to &nf_instance->sm. Cleanup
* of failed registration/update attempts is
* centralized here, after ogs_fsm_dispatch()
* completes.
*/
nrf_nf_fsm_fini(nf_instance);
ogs_sbi_nf_instance_remove(nf_instance);
}

View file

@ -108,6 +108,17 @@ bool nssf_nnrf_nsselection_handle_get_from_amf_or_vnssf(
nssf_nnssf_nsselection_param_t param;
ogs_sbi_discovery_option_t *h_discovery_option = NULL;
if (ogs_local_conf()->num_of_serving_plmn_id == 0) {
status = OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR;
strerror = ogs_msprintf("No serving PLMN configured: "
"cannot build Home-NSSF discovery query "
"for PLMN-ID(0x%x) S-NSSAI[SST:%d SD:0x%x]",
ogs_plmn_id_hexdump(&recvmsg->param.home_plmn_id),
recvmsg->param.home_snssai.sst,
recvmsg->param.home_snssai.sd.v);
goto cleanup;
}
h_discovery_option = ogs_sbi_discovery_option_new();
ogs_assert(h_discovery_option);
@ -115,7 +126,6 @@ bool nssf_nnrf_nsselection_handle_get_from_amf_or_vnssf(
h_discovery_option, &home->s_nssai);
ogs_sbi_discovery_option_add_target_plmn_list(
h_discovery_option, &home->plmn_id);
ogs_assert(ogs_local_conf()->num_of_serving_plmn_id);
for (i = 0; i < ogs_local_conf()->num_of_serving_plmn_id; i++) {
ogs_sbi_discovery_option_add_requester_plmn_list(
h_discovery_option,

View file

@ -374,13 +374,19 @@ void nssf_state_operational(ogs_fsm_t *s, nssf_event_t *e)
ogs_sbi_xact_remove(sbi_xact);
if (!stream) {
ogs_warn("Original NSSelection stream has been removed "
"before Home-NSSF response arrived");
break;
}
home = nssf_home_find_by_id(sbi_object_id);
if (!home) {
ogs_error("Home Network Context has already been removed");
break;
}
e->h.sbi.message = &message;;
e->h.sbi.message = &message;
nssf_nnrf_nsselection_handle_get_from_hnssf(home, stream, &message);
break;
@ -420,24 +426,26 @@ void nssf_state_operational(ogs_fsm_t *s, nssf_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);

View file

@ -30,6 +30,7 @@ static OGS_POOL(pcf_app_pool, pcf_app_t);
static int context_initialized = 0;
static void pcf_qos_profile_clear(void);
static void clear_ipv4addr(pcf_sess_t *sess);
static void clear_ipv6prefix(pcf_sess_t *sess);
@ -70,6 +71,8 @@ void pcf_context_final(void)
pcf_ue_am_remove_all();
pcf_ue_sm_remove_all();
pcf_qos_profile_clear();
ogs_assert(self.supi_am_hash);
ogs_hash_destroy(self.supi_am_hash);
ogs_assert(self.supi_sm_hash);
@ -92,8 +95,20 @@ pcf_context_t *pcf_self(void)
return &self;
}
static void pcf_qos_profile_clear(void)
{
int i;
for (i = 0; i < self.num_of_qos_profile; i++)
ogs_free(self.qos_profile[i].reference);
memset(self.qos_profile, 0, sizeof(self.qos_profile));
self.num_of_qos_profile = 0;
}
static int pcf_context_prepare(void)
{
pcf_qos_profile_clear();
return OGS_OK;
}
@ -204,6 +219,20 @@ static int policy_conf_validation(void)
return OGS_OK;
}
static bool pcf_qos_profile_reference_exists(const char *reference)
{
int i;
ogs_assert(reference);
for (i = 0; i < self.num_of_qos_profile; i++) {
if (!strcmp(self.qos_profile[i].reference, reference))
return true;
}
return false;
}
static int parse_policy_conf(ogs_yaml_iter_t *parent)
{
int rv;
@ -288,6 +317,75 @@ static int parse_policy_conf(ogs_yaml_iter_t *parent)
return OGS_OK;
}
static int parse_qos_profiles_conf(ogs_yaml_iter_t *parent)
{
ogs_yaml_iter_t profile_array, profile_iter;
ogs_assert(parent);
ogs_yaml_iter_recurse(parent, &profile_array);
do {
const char *reference = NULL;
const char *qos_index_string = NULL;
char *end = NULL;
long qos_index = 0;
OGS_YAML_ARRAY_NEXT(&profile_array, &profile_iter);
while (ogs_yaml_iter_next(&profile_iter)) {
const char *profile_key = ogs_yaml_iter_key(&profile_iter);
ogs_assert(profile_key);
if (!strcmp(profile_key, "reference")) {
reference = ogs_yaml_iter_value(&profile_iter);
} else if (!strcmp(profile_key, "qos_index")) {
qos_index_string = ogs_yaml_iter_value(&profile_iter);
} else {
ogs_warn("unknown qos_profiles key `%s`", profile_key);
}
}
if (!reference || !reference[0]) {
ogs_warn("Ignore qos_profiles entry without reference");
goto next;
}
if (!qos_index_string || !qos_index_string[0]) {
ogs_warn("Ignore qos_profiles[%s] without qos_index", reference);
goto next;
}
qos_index = strtol(qos_index_string, &end, 10);
if (*end || qos_index <= 0 || qos_index > UINT8_MAX) {
ogs_warn("Ignore qos_profiles[%s] invalid qos_index [%s]",
reference, qos_index_string);
goto next;
}
if (pcf_qos_profile_reference_exists(reference)) {
ogs_warn("Ignore duplicate qos_profiles reference [%s]", reference);
goto next;
}
if (self.num_of_qos_profile >= OGS_PCF_MAX_NUM_OF_QOS_PROFILE) {
ogs_warn("Ignore qos_profiles[%s] beyond max [%d]",
reference, OGS_PCF_MAX_NUM_OF_QOS_PROFILE);
goto next;
}
self.qos_profile[self.num_of_qos_profile].reference =
ogs_strdup(reference);
ogs_assert(self.qos_profile[self.num_of_qos_profile].reference);
self.qos_profile[self.num_of_qos_profile].qos_index =
(uint8_t)qos_index;
self.num_of_qos_profile++;
next:
;
} while (ogs_yaml_iter_type(&profile_array) == YAML_SEQUENCE_NODE);
return OGS_OK;
}
int pcf_context_parse_config(void)
{
int rv;
@ -326,6 +424,12 @@ int pcf_context_parse_config(void)
/* handle config in sbi library */
} else if (!strcmp(pcf_key, "metrics")) {
/* handle config in metrics library */
} else if (!strcmp(pcf_key, "qos_profiles")) {
rv = parse_qos_profiles_conf(&pcf_iter);
if (rv != OGS_OK) {
ogs_error("parse_qos_profiles_conf() failed");
return rv;
}
} else if (!strcmp(pcf_key, OGS_POLICY_STRING)) {
rv = parse_policy_conf(&pcf_iter);
if (rv != OGS_OK) {
@ -404,7 +508,8 @@ void pcf_ue_am_remove(pcf_ue_am_t *pcf_ue_am)
ogs_free(pcf_ue_am->association_id);
ogs_assert(pcf_ue_am->supi);
ogs_hash_set(self.supi_am_hash, pcf_ue_am->supi, strlen(pcf_ue_am->supi), NULL);
ogs_hash_unset_if_owner(self.supi_am_hash,
pcf_ue_am->supi, strlen(pcf_ue_am->supi), pcf_ue_am);
ogs_free(pcf_ue_am->supi);
if (pcf_ue_am->notification_uri)
@ -467,7 +572,8 @@ void pcf_ue_sm_remove(pcf_ue_sm_t *pcf_ue_sm)
pcf_sess_remove_all(pcf_ue_sm);
ogs_assert(pcf_ue_sm->supi);
ogs_hash_set(self.supi_sm_hash, pcf_ue_sm->supi, strlen(pcf_ue_sm->supi), NULL);
ogs_hash_unset_if_owner(self.supi_sm_hash,
pcf_ue_sm->supi, strlen(pcf_ue_sm->supi), pcf_ue_sm);
ogs_free(pcf_ue_sm->supi);
if (pcf_ue_sm->gpsi)
@ -672,7 +778,12 @@ bool pcf_sess_set_ipv6prefix(pcf_sess_t *sess, char *ipv6prefix_string)
return false;
}
ogs_assert(sess->ipv6prefix.len == OGS_IPV6_128_PREFIX_LEN);
if (sess->ipv6prefix.len != OGS_IPV6_128_PREFIX_LEN) {
ogs_error("Unsupported IPv6 prefix length [%d] in [%s]: "
"expected /%d", sess->ipv6prefix.len, ipv6prefix_string,
OGS_IPV6_128_PREFIX_LEN);
return false;
}
sess->ipv6prefix_string = ogs_strdup(ipv6prefix_string);
if (!sess->ipv6prefix_string) {
@ -778,9 +889,17 @@ pcf_sess_t *pcf_sess_find_by_ipv6prefix(char *ipv6prefix_string)
rv = ogs_ipv6prefix_from_string(
ipv6prefix.addr6, &ipv6prefix.len, ipv6prefix_string);
ogs_assert(rv == OGS_OK);
if (rv != OGS_OK) {
ogs_error("ogs_ipv6prefix_from_string() failed");
return NULL;
}
ogs_assert(ipv6prefix.len == OGS_IPV6_128_PREFIX_LEN);
if (ipv6prefix.len != OGS_IPV6_128_PREFIX_LEN) {
ogs_error("Unsupported IPv6 prefix length [%d] in [%s]: "
"expected /%d", ipv6prefix.len, ipv6prefix_string,
OGS_IPV6_128_PREFIX_LEN);
return NULL;
}
return ogs_hash_get(self.ipv6prefix_hash,
&ipv6prefix, (ipv6prefix.len >> 3) + 1);

View file

@ -37,6 +37,13 @@ extern int __pcf_log_domain;
#undef OGS_LOG_DOMAIN
#define OGS_LOG_DOMAIN __pcf_log_domain
#define OGS_PCF_MAX_NUM_OF_QOS_PROFILE 64
typedef struct pcf_qos_profile_s {
char *reference;
uint8_t qos_index;
} pcf_qos_profile_t;
typedef struct pcf_context_s {
ogs_list_t pcf_ue_am_list;
#define PCF_UE_SM_IS_LAST_SESSION(__pCF) \
@ -47,6 +54,9 @@ typedef struct pcf_context_s {
ogs_hash_t *ipv4addr_hash;
ogs_hash_t *ipv6prefix_hash;
pcf_qos_profile_t qos_profile[OGS_PCF_MAX_NUM_OF_QOS_PROFILE];
int num_of_qos_profile;
} pcf_context_t;
struct pcf_ue_am_s {

View file

@ -81,12 +81,14 @@ bool pcf_nbsf_management_handle_register(
if (rv != OGS_OK) {
strerror = ogs_msprintf("[%s:%d] Cannot parse http.location [%s]",
pcf_ue_sm->supi, sess->psi, recvmsg->http.location);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
if (!message.h.resource.component[1]) {
strerror = ogs_msprintf("[%s:%d] No Binding ID [%s]",
pcf_ue_sm->supi, sess->psi, recvmsg->http.location);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
ogs_sbi_header_free(&header);
goto cleanup;
@ -97,6 +99,7 @@ bool pcf_nbsf_management_handle_register(
if (rc == false || scheme == OpenAPI_uri_scheme_NULL) {
strerror = ogs_msprintf("[%s:%d] Invalid URI [%s]",
pcf_ue_sm->supi, sess->psi, header.uri);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
ogs_sbi_header_free(&header);
goto cleanup;
}
@ -108,6 +111,7 @@ bool pcf_nbsf_management_handle_register(
if (!client) {
strerror = ogs_msprintf("[%s:%d] ogs_sbi_client_add() failed",
pcf_ue_sm->supi, sess->psi);
status = OGS_SBI_HTTP_STATUS_INTERNAL_SERVER_ERROR;
ogs_sbi_header_free(&header);
ogs_free(fqdn);

View file

@ -101,7 +101,12 @@ void pcf_nnrf_handle_nf_discover(
ogs_assert(xact->assoc_stream_id >= OGS_MIN_POOL_ID &&
xact->assoc_stream_id <= OGS_MAX_POOL_ID);
stream = ogs_sbi_stream_find_by_id(xact->assoc_stream_id);
ogs_assert(stream);
if (!stream) {
ogs_warn("Associated SBI stream has already been removed [%d]",
xact->assoc_stream_id);
ogs_sbi_xact_remove(xact);
return;
}
/* Send Response for SM Policy Association establishment */
ogs_expect(true ==

View file

@ -21,6 +21,42 @@
#include "npcf-handler.h"
static uint8_t pcf_qos_index_from_media(
const char *qos_reference,
OpenAPI_media_type_e media_type,
const char **err_out)
{
int i;
ogs_assert(err_out);
*err_out = NULL;
if (qos_reference) {
for (i = 0; i < pcf_self()->num_of_qos_profile; i++) {
if (!strcmp(pcf_self()->qos_profile[i].reference, qos_reference))
return pcf_self()->qos_profile[i].qos_index;
}
*err_out = "Unknown qosReference - check PCF qos_profiles config";
return 0;
}
switch (media_type) {
case OpenAPI_media_type_AUDIO:
return OGS_QOS_INDEX_1;
case OpenAPI_media_type_VIDEO:
return OGS_QOS_INDEX_2;
case OpenAPI_media_type_CONTROL:
return OGS_QOS_INDEX_5;
case OpenAPI_media_type_NULL:
*err_out = "Media-Type is Required";
return 0;
default:
*err_out = "Unknown Media-Type";
return 0;
}
}
bool pcf_npcf_am_policy_control_handle_create(pcf_ue_am_t *pcf_ue_am,
ogs_sbi_stream_t *stream, ogs_sbi_message_t *message)
{
@ -285,11 +321,8 @@ bool pcf_npcf_smpolicycontrol_handle_create(pcf_sess_t *sess,
if (!SmPolicyContextData->ipv4_address &&
!SmPolicyContextData->ipv6_address_prefix) {
strerror = ogs_msprintf(
"[%s:%d] No IPv4 address[%p] or IPv6 prefix[%p]",
pcf_ue_sm->supi, sess->psi,
SmPolicyContextData->ipv4_address,
SmPolicyContextData->ipv6_address_prefix);
strerror = ogs_msprintf("[%s:%d] No IPv4 address or IPv6 prefix",
pcf_ue_sm->supi, sess->psi);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
@ -452,13 +485,27 @@ bool pcf_npcf_smpolicycontrol_handle_create(pcf_sess_t *sess,
ogs_freeaddrinfo(addr);
ogs_freeaddrinfo(addr6);
if (SmPolicyContextData->ipv4_address)
ogs_assert(true ==
pcf_sess_set_ipv4addr(sess, SmPolicyContextData->ipv4_address));
if (SmPolicyContextData->ipv6_address_prefix)
ogs_assert(true ==
pcf_sess_set_ipv6prefix(
sess, SmPolicyContextData->ipv6_address_prefix));
if (SmPolicyContextData->ipv4_address) {
if (pcf_sess_set_ipv4addr(
sess, SmPolicyContextData->ipv4_address) == false) {
strerror = ogs_msprintf("[%s:%d] Invalid ipv4Address [%s]",
pcf_ue_sm->supi, sess->psi,
SmPolicyContextData->ipv4_address);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
}
if (SmPolicyContextData->ipv6_address_prefix) {
if (pcf_sess_set_ipv6prefix(
sess, SmPolicyContextData->ipv6_address_prefix) == false) {
strerror = ogs_msprintf("[%s:%d] Invalid ipv6AddressPrefix [%s]",
pcf_ue_sm->supi, sess->psi,
SmPolicyContextData->ipv6_address_prefix);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
}
if (SmPolicyContextData->ipv4_frame_route_list) {
OpenAPI_lnode_t *node = NULL;
@ -638,6 +685,7 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
ogs_ims_data_t ims_data;
ogs_media_component_t *media_component = NULL;
ogs_media_sub_component_t *sub = NULL;
const char *qos_reference[OGS_MAX_NUM_OF_MEDIA_COMPONENT] = {0};
OpenAPI_list_t *MediaComponentList = NULL;
OpenAPI_map_t *MediaComponentMap = NULL;
@ -735,6 +783,7 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
if (MediaComponentMap) {
MediaComponent = MediaComponentMap->value;
if (MediaComponent) {
int n;
if (ims_data.num_of_media_component >=
OGS_ARRAY_SIZE(ims_data.media_component)) {
ogs_error("OVERFLOW ims_data.num_of_media_component "
@ -744,8 +793,10 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
(int)OGS_ARRAY_SIZE(ims_data.media_component));
break;
}
media_component = &ims_data.
media_component[ims_data.num_of_media_component];
n = ims_data.num_of_media_component;
media_component = &ims_data.media_component[n];
qos_reference[n] = MediaComponent->qos_reference;
media_component->media_component_number =
MediaComponent->med_comp_n;
media_component->media_type = MediaComponent->med_type;
@ -871,28 +922,17 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
ogs_pcc_rule_t *db_pcc_rule = NULL;
uint8_t qos_index = 0;
ogs_media_component_t *media_component = &ims_data.media_component[i];
const char *reference = qos_reference[i];
const char *err_str = NULL;
if (media_component->media_type == OpenAPI_media_type_NULL) {
strerror = ogs_msprintf("[%s:%d] Media-Type is Required",
pcf_ue_sm->supi, sess->psi);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
switch(media_component->media_type) {
case OpenAPI_media_type_AUDIO:
qos_index = OGS_QOS_INDEX_1;
break;
case OpenAPI_media_type_VIDEO:
qos_index = OGS_QOS_INDEX_2;
break;
case OpenAPI_media_type_CONTROL:
qos_index = OGS_QOS_INDEX_5;
break;
default:
strerror = ogs_msprintf("[%s:%d] Unknown Media-Type [%d]",
pcf_ue_sm->supi, sess->psi, media_component->media_type);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
qos_index = pcf_qos_index_from_media(
reference, media_component->media_type, &err_str);
if (qos_index == 0) {
strerror = ogs_msprintf("[%s:%d] %s",
pcf_ue_sm->supi, sess->psi, err_str);
status = reference ?
OGS_SBI_HTTP_STATUS_FORBIDDEN :
OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
@ -903,7 +943,7 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
}
}
if (!db_pcc_rule &&
if (!db_pcc_rule && !reference &&
(media_component->media_type == OpenAPI_media_type_CONTROL)) {
/*
* Check for default bearer for IMS signalling
@ -1094,7 +1134,14 @@ bool pcf_npcf_policyauthorization_handle_create(pcf_sess_t *sess,
cleanup:
ogs_assert(status);
ogs_assert(strerror);
ogs_error("%s", strerror);
/*
* Unknown qosReference is rejected with 403 in a negative test case.
* Log it as warning to avoid treating the expected rejection as an error.
*/
ogs_log_message(
status == OGS_SBI_HTTP_STATUS_FORBIDDEN ?
OGS_LOG_WARN : OGS_LOG_ERROR, 0, "%s", strerror);
ogs_assert(true ==
ogs_sbi_server_send_error(stream, status, recvmsg, strerror, NULL,
NULL));
@ -1125,6 +1172,9 @@ cleanup:
ogs_ims_data_free(&ims_data);
OGS_SESSION_DATA_FREE(&session_data);
if (app_session)
pcf_app_remove(app_session);
return false;
}
@ -1148,6 +1198,7 @@ bool pcf_npcf_policyauthorization_handle_update(
ogs_ims_data_t ims_data;
ogs_media_component_t *media_component = NULL;
ogs_media_sub_component_t *sub = NULL;
const char *qos_reference[OGS_MAX_NUM_OF_MEDIA_COMPONENT] = {0};
OpenAPI_list_t *MediaComponentList = NULL;
OpenAPI_map_t *MediaComponentMap = NULL;
@ -1210,6 +1261,7 @@ bool pcf_npcf_policyauthorization_handle_update(
if (MediaComponentMap) {
MediaComponent = MediaComponentMap->value;
if (MediaComponent) {
int n;
if (ims_data.num_of_media_component >=
OGS_ARRAY_SIZE(ims_data.media_component)) {
ogs_error("OVERFLOW ims_data.num_of_media_component "
@ -1219,9 +1271,10 @@ bool pcf_npcf_policyauthorization_handle_update(
(int)OGS_ARRAY_SIZE(ims_data.media_component));
break;
}
media_component = &ims_data.
media_component[ims_data.num_of_media_component];
n = ims_data.num_of_media_component;
media_component = &ims_data.media_component[n];
qos_reference[n] = MediaComponent->qos_reference;
media_component->media_component_number =
MediaComponent->med_comp_n;
media_component->media_type = MediaComponent->med_type;
@ -1321,28 +1374,17 @@ bool pcf_npcf_policyauthorization_handle_update(
ogs_pcc_rule_t *db_pcc_rule = NULL;
uint8_t qos_index = 0;
ogs_media_component_t *media_component = &ims_data.media_component[i];
const char *reference = qos_reference[i];
const char *err_str = NULL;
if (media_component->media_type == OpenAPI_media_type_NULL) {
strerror = ogs_msprintf("[%s:%d] Media-Type is Required",
pcf_ue_sm->supi, sess->psi);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
switch(media_component->media_type) {
case OpenAPI_media_type_AUDIO:
qos_index = OGS_QOS_INDEX_1;
break;
case OpenAPI_media_type_VIDEO:
qos_index = OGS_QOS_INDEX_2;
break;
case OpenAPI_media_type_CONTROL:
qos_index = OGS_QOS_INDEX_5;
break;
default:
strerror = ogs_msprintf("[%s:%d] Unknown Media-Type [%d]",
pcf_ue_sm->supi, sess->psi, media_component->media_type);
status = OGS_SBI_HTTP_STATUS_BAD_REQUEST;
qos_index = pcf_qos_index_from_media(
reference, media_component->media_type, &err_str);
if (qos_index == 0) {
strerror = ogs_msprintf("[%s:%d] %s",
pcf_ue_sm->supi, sess->psi, err_str);
status = reference ?
OGS_SBI_HTTP_STATUS_FORBIDDEN :
OGS_SBI_HTTP_STATUS_BAD_REQUEST;
goto cleanup;
}
@ -1353,7 +1395,7 @@ bool pcf_npcf_policyauthorization_handle_update(
}
}
if (!db_pcc_rule &&
if (!db_pcc_rule && !reference &&
(media_component->media_type == OpenAPI_media_type_CONTROL)) {
/*
* Check for default bearer for IMS signalling

View file

@ -711,24 +711,26 @@ void pcf_state_operational(ogs_fsm_t *s, pcf_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);

View file

@ -93,14 +93,28 @@ void pcf_sm_state_operational(ogs_fsm_t *s, pcf_event_t *e)
} else {
SWITCH(message->h.resource.component[2])
CASE(OGS_SBI_RESOURCE_NAME_DELETE)
handled = pcf_npcf_smpolicycontrol_handle_delete(
sess, stream, message);
if (!handled) {
ogs_error("[%s:%d] "
"pcf_npcf_smpolicycontrol_handle_delete() failed",
pcf_ue_sm->supi, sess->psi);
OGS_FSM_TRAN(s, pcf_sm_state_exception);
}
SWITCH(message->h.method)
CASE(OGS_SBI_HTTP_METHOD_POST)
handled = pcf_npcf_smpolicycontrol_handle_delete(
sess, stream, message);
if (!handled) {
ogs_error("[%s:%d] "
"pcf_npcf_smpolicycontrol_handle_delete() "
"failed", pcf_ue_sm->supi, sess->psi);
OGS_FSM_TRAN(s, pcf_sm_state_exception);
}
break;
DEFAULT
ogs_error("[%s:%d] Invalid HTTP method [%s] for "
"/delete", pcf_ue_sm->supi, sess->psi,
message->h.method);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_METHOD_NOT_ALLOWED,
message, "Invalid HTTP method",
message->h.uri, NULL));
END
break;
DEFAULT
@ -119,8 +133,22 @@ void pcf_sm_state_operational(ogs_fsm_t *s, pcf_event_t *e)
if (message->h.resource.component[2]) {
SWITCH(message->h.resource.component[2])
CASE(OGS_SBI_RESOURCE_NAME_DELETE)
handled = pcf_npcf_policyauthorization_handle_delete(
sess, e->app, stream, message);
SWITCH(message->h.method)
CASE(OGS_SBI_HTTP_METHOD_POST)
handled =
pcf_npcf_policyauthorization_handle_delete(
sess, e->app, stream, message);
break;
DEFAULT
ogs_error("[%s:%d] Invalid HTTP method [%s] for "
"/delete", pcf_ue_sm->supi, sess->psi,
message->h.method);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_METHOD_NOT_ALLOWED,
message, "Invalid HTTP method",
message->h.uri, NULL));
END
break;
DEFAULT
ogs_error("[%s:%d] Invalid resource name [%s]",

View file

@ -248,9 +248,18 @@ static int request_handler(ogs_sbi_request_t *request, void *data)
ogs_sbi_discovery_option_set_requester_nf_instance_id(
discovery_option, val);
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_SERVICE_NAMES)) {
if (val)
ogs_sbi_discovery_option_parse_service_names(
discovery_option, val);
if (val) {
if (ogs_sbi_discovery_option_parse_service_names(
discovery_option, val) != OGS_OK) {
ogs_error("Invalid service-names [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid service-names", val, NULL));
return OGS_ERROR;
}
}
/*
* So, we'll use the first item in service-names list.
@ -273,32 +282,79 @@ static int request_handler(ogs_sbi_request_t *request, void *data)
service_name = discovery_option->service_names[0];
}
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_SNSSAIS)) {
if (val)
ogs_sbi_discovery_option_parse_snssais(discovery_option, val);
if (val) {
if (ogs_sbi_discovery_option_parse_snssais(
discovery_option, val) != OGS_OK) {
ogs_error("Invalid snssais [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid snssais", val, NULL));
return OGS_ERROR;
}
}
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_GUAMI)) {
if (val)
ogs_sbi_discovery_option_parse_guami(discovery_option, val);
if (val) {
if (ogs_sbi_discovery_option_parse_guami(
discovery_option, val) != OGS_OK) {
ogs_error("Invalid guami [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid guami", val, NULL));
return OGS_ERROR;
}
}
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_DNN)) {
ogs_sbi_discovery_option_set_dnn(discovery_option, val);
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_TAI)) {
if (val)
ogs_sbi_discovery_option_parse_tai(discovery_option, val);
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_GUAMI)) {
if (val)
ogs_sbi_discovery_option_parse_guami(discovery_option, val);
if (val) {
if (ogs_sbi_discovery_option_parse_tai(
discovery_option, val) != OGS_OK) {
ogs_error("Invalid tai [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid tai", val, NULL));
return OGS_ERROR;
}
}
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_TARGET_PLMN_LIST)) {
if (val)
discovery_option->num_of_target_plmn_list =
ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->target_plmn_list, val);
if (val) {
int n = ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->target_plmn_list, val);
if (n < 0) {
ogs_error("Invalid target-plmn-list [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid target-plmn-list", val, NULL));
return OGS_ERROR;
}
discovery_option->num_of_target_plmn_list = n;
}
} else if (!strcasecmp(key, OGS_SBI_CUSTOM_DISCOVERY_HNRF_URI)) {
ogs_sbi_discovery_option_set_hnrf_uri(discovery_option, val);
} else if (!strcasecmp(key,
OGS_SBI_CUSTOM_DISCOVERY_REQUESTER_PLMN_LIST)) {
if (val)
discovery_option->num_of_requester_plmn_list =
ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->requester_plmn_list, val);
if (val) {
int n = ogs_sbi_discovery_option_parse_plmn_list(
discovery_option->requester_plmn_list, val);
if (n < 0) {
ogs_error("Invalid requester-plmn-list [%s]", val);
scp_assoc_remove(assoc);
ogs_assert(true ==
ogs_sbi_server_send_error(stream,
OGS_SBI_HTTP_STATUS_BAD_REQUEST, NULL,
"Invalid requester-plmn-list", val, NULL));
return OGS_ERROR;
}
discovery_option->num_of_requester_plmn_list = n;
}
} else if (!strcasecmp(key,
OGS_SBI_CUSTOM_DISCOVERY_REQUESTER_FEATURES)) {
if (val)

View file

@ -289,24 +289,26 @@ void scp_state_operational(ogs_fsm_t *s, scp_event_t *e)
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_subscribe(
ogs_sbi_self()->nf_instance->nf_type,
subscription_data->req_nf_instance_id,
subscription_data->subscr_cond.nf_type,
subscription_data->subscr_cond.service_name));
ogs_error("[%s] Subscription validity expired",
subscription_data->id);
ogs_sbi_subscription_data_remove(subscription_data);
subscription_data->id ?
subscription_data->id : "Unknown");
/*
* Helper strdup-s the fields we need, removes the old
* subscription so the pool slot is freed, then resubscribes.
*/
(void)ogs_nnrf_nfm_send_nf_status_subscribe_renew(
subscription_data);
break;
case OGS_TIMER_SUBSCRIPTION_PATCH:
subscription_data = e->h.sbi.data;
ogs_assert(subscription_data);
ogs_assert(true ==
ogs_nnrf_nfm_send_nf_status_update(subscription_data));
if (ogs_nnrf_nfm_send_nf_status_update(subscription_data) != true)
ogs_error("[%s] NF status subscription update failed",
subscription_data->id ?
subscription_data->id : "Unknown");
ogs_info("[%s] Need to update Subscription",
subscription_data->id);

Some files were not shown because too many files have changed in this diff Show more