From 2be12903cb3a72288f350796b29606ab49160bc6 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Sat, 9 Apr 2022 01:26:28 +0200 Subject: [PATCH] [SMF] Introduce optional Gy interface support (#1479) The use of the Gy interface (SMF acting as CTF towards an OCS node) is mandated through configuration file. Default value "enable: auto" will only make use of it in case a Diameter peer announcing support for the Credit-Control Application is found. Upon subscriber session creation, and after auth check over Gx, the SMF will create a Gy session with the OCS and only after that step the SMF will accept the session back to the subscriber. The OCS may then grant some traffic volumes/time and ask to be notified back with updated measurements. In order to get the measurements, the SMF relies on PFCP URR configured to the UPF through Session Repoort Request messages. When closing the subscriber session, the SMF will also terminate the Gy session at the OCS. So far only some specifics parts of the Gy interface as well as the PFCP side are implemented. Those should be enough to at least have volume/time thresholds granted by the OCS, which then will be able to track subsriber resource use. This patch doesn't implement the OCS side of the Gy interface, that's left as a future exercise. The interface was tested using an OCS emulator implemented in TTCN-3 [1] [1] https://cgit.osmocom.org/osmo-ttcn3-hacks/ --- configs/open5gs/smf.yaml.in | 14 + lib/pfcp/build.c | 45 ++ lib/pfcp/build.h | 2 + lib/pfcp/xact.h | 5 + src/smf/context.c | 48 ++ src/smf/context.h | 29 + src/smf/event.c | 2 + src/smf/event.h | 7 +- src/smf/fd-path.c | 3 + src/smf/fd-path.h | 5 +- src/smf/gx-handler.c | 27 +- src/smf/gy-handler.c | 260 ++++++++ src/smf/gy-handler.h | 46 ++ src/smf/gy-path.c | 1129 +++++++++++++++++++++++++++++++++++ src/smf/meson.build | 5 + src/smf/n4-build.c | 11 +- src/smf/n4-handler.c | 85 ++- src/smf/smf-sm.c | 46 ++ 18 files changed, 1759 insertions(+), 10 deletions(-) create mode 100644 src/smf/gy-handler.c create mode 100644 src/smf/gy-handler.h create mode 100644 src/smf/gy-path.c diff --git a/configs/open5gs/smf.yaml.in b/configs/open5gs/smf.yaml.in index a0e680aff..a72a40f4a 100644 --- a/configs/open5gs/smf.yaml.in +++ b/configs/open5gs/smf.yaml.in @@ -233,6 +233,18 @@ logger: # - 127.0.0.1 # - ::1 # +# +# +# o Gy interface parameters towards OCS. +# o enabled: +# o auto: Default. Use Gy only if OCS available among Diameter peers +# o yes: Use Gy always; reject subscribers if no OCS available among Diameter peers +# o no: Don't use Gy interface if there is an OCS available +# +# ctf: +# enabled: auto|yes|no +# +# # # 1. SMF sends SmfInfo(S-NSSAI, DNN, TAI) to the NRF # 2. NRF responds to AMF with SmfInfo during NF-Discovery. @@ -385,6 +397,8 @@ smf: - 2001:4860:4860::8888 - 2001:4860:4860::8844 mtu: 1400 + ctf: + enabled: auto freeDiameter: @sysconfdir@/freeDiameter/smf.conf # diff --git a/lib/pfcp/build.c b/lib/pfcp/build.c index d6470aa9a..521941249 100644 --- a/lib/pfcp/build.c +++ b/lib/pfcp/build.c @@ -637,6 +637,51 @@ void ogs_pfcp_build_create_urr( } } +void ogs_pfcp_build_update_urr( + ogs_pfcp_tlv_update_urr_t *message, int i, ogs_pfcp_urr_t *urr, uint64_t modify_flags) +{ + ogs_assert(message); + ogs_assert(urr); + + /* No change requested, skip. */ + if (!(modify_flags & (OGS_PFCP_MODIFY_URR_MEAS_METHOD| + OGS_PFCP_MODIFY_URR_REPORT_TRIGGER| + OGS_PFCP_MODIFY_URR_VOLUME_THRESH| + OGS_PFCP_MODIFY_URR_TIME_THRESH))) + return; + + /* Change request: Send only changed IEs */ + message->presence = 1; + message->urr_id.presence = 1; + message->urr_id.u32 = urr->id; + if (modify_flags & OGS_PFCP_MODIFY_URR_MEAS_METHOD) { + message->measurement_method.presence = 1; + message->measurement_method.u8 = urr->meas_method; + } + if (modify_flags & OGS_PFCP_MODIFY_URR_REPORT_TRIGGER) { + message->reporting_triggers.presence = 1; + message->reporting_triggers.u24 = (urr->rep_triggers.reptri_5 << 16) + | (urr->rep_triggers.reptri_6 << 8) + | (urr->rep_triggers.reptri_7); + } + + if (modify_flags & OGS_PFCP_MODIFY_URR_VOLUME_THRESH) { + if (urr->vol_threshold.flags) { + message->volume_threshold.presence = 1; + ogs_pfcp_build_volume( + &message->volume_threshold, &urr->vol_threshold, + &urrbuf[i].vol_threshold, sizeof(urrbuf[i].vol_threshold)); + } + } + + if (modify_flags & OGS_PFCP_MODIFY_URR_TIME_THRESH) { + if (urr->time_threshold) { + message->time_threshold.presence = 1; + message->time_threshold.u32 = urr->time_threshold; + } + } +} + static struct { char mbr[OGS_PFCP_BITRATE_LEN]; char gbr[OGS_PFCP_BITRATE_LEN]; diff --git a/lib/pfcp/build.h b/lib/pfcp/build.h index 4eda0f8ee..c2065cae9 100644 --- a/lib/pfcp/build.h +++ b/lib/pfcp/build.h @@ -59,6 +59,8 @@ void ogs_pfcp_build_update_qer( void ogs_pfcp_build_create_urr( ogs_pfcp_tlv_create_urr_t *message, int i, ogs_pfcp_urr_t *urr); +void ogs_pfcp_build_update_urr( + ogs_pfcp_tlv_update_urr_t *message, int i, ogs_pfcp_urr_t *urr, uint64_t modify_flags); void ogs_pfcp_build_create_bar( ogs_pfcp_tlv_create_bar_t *message, ogs_pfcp_bar_t *bar); diff --git a/lib/pfcp/xact.h b/lib/pfcp/xact.h index 6d7904496..4ba968f02 100644 --- a/lib/pfcp/xact.h +++ b/lib/pfcp/xact.h @@ -97,6 +97,11 @@ typedef struct ogs_pfcp_xact_s { #define OGS_PFCP_MODIFY_XN_HANDOVER ((uint64_t)1<<21) #define OGS_PFCP_MODIFY_N2_HANDOVER ((uint64_t)1<<22) #define OGS_PFCP_MODIFY_HANDOVER_CANCEL ((uint64_t)1<<23) +#define OGS_PFCP_MODIFY_URR ((uint64_t)1<<24) /* type of trigger */ +#define OGS_PFCP_MODIFY_URR_MEAS_METHOD ((uint64_t)1<<25) +#define OGS_PFCP_MODIFY_URR_REPORT_TRIGGER ((uint64_t)1<<26) +#define OGS_PFCP_MODIFY_URR_VOLUME_THRESH ((uint64_t)1<<27) +#define OGS_PFCP_MODIFY_URR_TIME_THRESH ((uint64_t)1<<28) uint64_t modify_flags; diff --git a/src/smf/context.c b/src/smf/context.c index 6d9e96cb7..ad744bdcf 100644 --- a/src/smf/context.c +++ b/src/smf/context.c @@ -38,6 +38,27 @@ static int num_of_smf_sess = 0; static void stats_add_smf_session(void); static void stats_remove_smf_session(void); +int smf_ctf_config_init(smf_ctf_config_t *ctf_config) +{ + ctf_config->enabled = SMF_CTF_ENABLED_AUTO; + return OGS_OK; +} + +/* Shall Gy session be used according to policy and state? 1: yes, 0: no, -1: reject */ +int smf_use_gy_iface() +{ + switch (smf_self()->ctf_config.enabled) { + case SMF_CTF_ENABLED_AUTO: + return ogs_diam_app_connected(OGS_DIAM_GY_APPLICATION_ID) ? 1 : 0; + case SMF_CTF_ENABLED_YES: + return ogs_diam_app_connected(OGS_DIAM_GY_APPLICATION_ID) ? 1 : -1; + case SMF_CTF_ENABLED_NO: + return 0; + default: + return -1; + } +} + void smf_context_init(void) { ogs_assert(context_initialized == 0); @@ -47,6 +68,7 @@ void smf_context_init(void) /* Initialize SMF context */ memset(&self, 0, sizeof(smf_context_t)); + smf_ctf_config_init(&self.ctf_config); self.diam_config = &g_diam_conf; ogs_log_install_domain(&__ogs_ngap_domain, "ngap", ogs_core()->log.level); @@ -331,6 +353,32 @@ int smf_context_parse_config(void) ogs_warn("unknown key `%s`", fd_key); } } + } else if (!strcmp(smf_key, "ctf")) { + ogs_yaml_iter_t ctf_iter; + yaml_node_t *node = + yaml_document_get_node(document, smf_iter.pair->value); + ogs_assert(node); + ogs_assert(node->type == YAML_MAPPING_NODE); + ogs_yaml_iter_recurse(&smf_iter, &ctf_iter); + while (ogs_yaml_iter_next(&ctf_iter)) { + const char *ctf_key = ogs_yaml_iter_key(&ctf_iter); + ogs_assert(ctf_key); + if (!strcmp(ctf_key, "enabled")) { + yaml_node_t *ctf_node = + yaml_document_get_node(document, ctf_iter.pair->value); + ogs_assert(ctf_node->type == YAML_SCALAR_NODE); + const char* enabled = ogs_yaml_iter_value(&ctf_iter); + if (!strcmp(enabled, "auto")) + self.ctf_config.enabled = SMF_CTF_ENABLED_AUTO; + else if (!strcmp(enabled, "yes")) + self.ctf_config.enabled = SMF_CTF_ENABLED_YES; + else if (!strcmp(enabled, "no")) + self.ctf_config.enabled = SMF_CTF_ENABLED_NO; + else + ogs_warn("unknown 'enabled' value `%s`", enabled); + } else + ogs_warn("unknown key `%s`", ctf_key); + } } else if (!strcmp(smf_key, "gtpc")) { /* handle config in gtp library */ } else if (!strcmp(smf_key, "gtpu")) { diff --git a/src/smf/context.h b/src/smf/context.h index 3ce15b93a..abdf26089 100644 --- a/src/smf/context.h +++ b/src/smf/context.h @@ -24,6 +24,7 @@ #include "ogs-gtp.h" #include "ogs-diameter-gx.h" +#include "ogs-diameter-gy.h" #include "ogs-diameter-rx.h" #include "ogs-diameter-s6b.h" #include "ogs-pfcp.h" @@ -50,7 +51,20 @@ extern int __gsm_log_domain; #undef OGS_LOG_DOMAIN #define OGS_LOG_DOMAIN __smf_log_domain +typedef enum { + SMF_CTF_ENABLED_AUTO = 0, + SMF_CTF_ENABLED_YES, + SMF_CTF_ENABLED_NO, +} smf_ctf_enabled_mode; + +typedef struct smf_ctf_config_s { + smf_ctf_enabled_mode enabled; +} smf_ctf_config_t; + +int smf_ctf_config_init(smf_ctf_config_t *ctf_config); + typedef struct smf_context_s { + smf_ctf_config_t ctf_config; const char* diam_conf_path; /* SMF Diameter conf path */ ogs_diam_config_t *diam_config; /* SMF Diameter config */ @@ -217,6 +231,7 @@ typedef struct smf_sess_s { ogs_ip_t gnb_n3_ip; /* gNB-N3 IPv4/IPv6 */ char *gx_sid; /* Gx Session ID */ + char *gy_sid; /* Gx Session ID */ char *s6b_sid; /* S6b Session ID */ OGS_POOL(pf_precedence_pool, uint8_t); @@ -291,6 +306,18 @@ typedef struct smf_sess_s { uint8_t nsapi; } gtp1; /* GTPv1C specific fields */ + struct { + uint64_t ul_octets; + uint64_t dl_octets; + ogs_time_t duration; + /* Snapshot of measurement when last report was sent: */ + struct { + uint64_t ul_octets; + uint64_t dl_octets; + ogs_time_t duration; + } last_report; + } gy; + struct { ogs_nas_extended_protocol_configuration_options_t ue_pco; } nas; /* Saved from NAS-5GS */ @@ -358,6 +385,8 @@ smf_context_t *smf_self(void); int smf_context_parse_config(void); +int smf_use_gy_iface(void); + smf_ue_t *smf_ue_add_by_supi(char *supi); smf_ue_t *smf_ue_add_by_imsi(uint8_t *imsi, int imsi_len); void smf_ue_remove(smf_ue_t *smf_ue); diff --git a/src/smf/event.c b/src/smf/event.c index 7453c8e16..181aa0cc3 100644 --- a/src/smf/event.c +++ b/src/smf/event.c @@ -68,6 +68,8 @@ const char *smf_event_get_name(smf_event_t *e) return "SMF_EVT_GN_MESSAGE"; case SMF_EVT_GX_MESSAGE: return "SMF_EVT_GX_MESSAGE"; + case SMF_EVT_GY_MESSAGE: + return "SMF_EVT_GY_MESSAGE"; case SMF_EVT_N4_MESSAGE: return "SMF_EVT_N4_MESSAGE"; case SMF_EVT_N4_TIMER: diff --git a/src/smf/event.h b/src/smf/event.h index 625e0ced9..8b6cfca30 100644 --- a/src/smf/event.h +++ b/src/smf/event.h @@ -32,6 +32,7 @@ typedef struct ogs_pfcp_node_s ogs_pfcp_node_t; typedef struct ogs_pfcp_xact_s ogs_pfcp_xact_t; typedef struct ogs_pfcp_message_s ogs_pfcp_message_t; typedef struct ogs_diam_gx_message_s ogs_diam_gx_message_t; +typedef struct ogs_diam_gy_message_s ogs_diam_gy_message_t; typedef struct smf_sess_s smf_sess_t; typedef struct smf_upf_s smf_upf_t; typedef struct ogs_sbi_request_s ogs_sbi_request_t; @@ -48,6 +49,7 @@ typedef enum { SMF_EVT_S5C_MESSAGE, SMF_EVT_GN_MESSAGE, SMF_EVT_GX_MESSAGE, + SMF_EVT_GY_MESSAGE, SMF_EVT_N4_MESSAGE, SMF_EVT_N4_TIMER, @@ -79,7 +81,10 @@ typedef struct smf_event_s { ogs_pfcp_xact_t *pfcp_xact; ogs_pfcp_message_t *pfcp_message; - ogs_diam_gx_message_t *gx_message; + union { + ogs_diam_gx_message_t *gx_message; + ogs_diam_gy_message_t *gy_message; + }; struct { ogs_sbi_request_t *request; diff --git a/src/smf/fd-path.c b/src/smf/fd-path.c index 9e073a94b..622181278 100644 --- a/src/smf/fd-path.c +++ b/src/smf/fd-path.c @@ -36,6 +36,9 @@ int smf_fd_init(void) ogs_assert(rv == 0); rv = smf_gx_init(); + ogs_assert(rv == OGS_OK); + + rv = smf_gy_init(); ogs_assert(rv == OGS_OK); rv = ogs_diam_rx_init(); diff --git a/src/smf/fd-path.h b/src/smf/fd-path.h index 77c810458..571057ce8 100644 --- a/src/smf/fd-path.h +++ b/src/smf/fd-path.h @@ -33,11 +33,15 @@ void smf_fd_final(void); int smf_gx_init(void); void smf_gx_final(void); +int smf_gy_init(void); +void smf_gy_final(void); int smf_s6b_init(void); void smf_s6b_final(void); void smf_gx_send_ccr(smf_sess_t *sess, ogs_gtp_xact_t *xact, uint32_t cc_request_type); +void smf_gy_send_ccr(smf_sess_t *sess, void *xact, + uint32_t cc_request_type); void smf_s6b_send_aar(smf_sess_t *sess, ogs_gtp_xact_t *xact); void smf_s6b_send_str(smf_sess_t *sess, ogs_gtp_xact_t *xact, uint32_t cause); @@ -47,4 +51,3 @@ void smf_s6b_send_str(smf_sess_t *sess, ogs_gtp_xact_t *xact, uint32_t cause); #endif #endif /* SMF_FD_PATH_H */ - diff --git a/src/smf/gx-handler.c b/src/smf/gx-handler.c index 395158a98..1089a44e5 100644 --- a/src/smf/gx-handler.c +++ b/src/smf/gx-handler.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH * * This file is part of Open5GS. * @@ -20,6 +21,7 @@ #include "context.h" #include "gtp-path.h" #include "pfcp-path.h" +#include "fd-path.h" #include "gx-handler.h" #include "binding.h" @@ -271,8 +273,29 @@ void smf_gx_handle_cca_initial_request( ogs_pfcp_pdr_associate_qer(ul_pdr, qer); } - ogs_assert(OGS_OK == - smf_epc_pfcp_send_session_establishment_request(sess, gtp_xact)); + switch(smf_use_gy_iface()) { + case 1: + /* Gy is available, set up session for the bearer before accepting it towards the UE */ + smf_gy_send_ccr(sess, gtp_xact, + OGS_DIAM_GY_CC_REQUEST_TYPE_INITIAL_REQUEST); + return; + case 0: + /* Not using Gy, jump directly to PFCP Session Establishment Request */ + ogs_assert(OGS_OK == + smf_epc_pfcp_send_session_establishment_request(sess, gtp_xact)); + return; + case -1: + ogs_error("No Gy Diameter Peer"); + if (gtp_xact->gtp_version == 1) + ogs_gtp1_send_error_message(gtp_xact, sess ? sess->sgw_s5c_teid : 0, + OGS_GTP1_CREATE_PDP_CONTEXT_RESPONSE_TYPE, + OGS_GTP1_CAUSE_NO_RESOURCES_AVAILABLE); + else + ogs_gtp2_send_error_message(gtp_xact, sess ? sess->sgw_s5c_teid : 0, + OGS_GTP_CREATE_SESSION_RESPONSE_TYPE, + OGS_GTP_CAUSE_UE_NOT_AUTHORISED_BY_OCS_OR_EXTERNAL_AAA_SERVER); + return; + } } void smf_gx_handle_cca_termination_request( diff --git a/src/smf/gy-handler.c b/src/smf/gy-handler.c new file mode 100644 index 000000000..7890edef7 --- /dev/null +++ b/src/smf/gy-handler.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH + * + * This file is part of Open5GS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "context.h" +#include "gtp-path.h" +#include "pfcp-path.h" +#include "gy-handler.h" +#include "binding.h" + +static uint8_t gtp_cause_from_diameter( + const uint32_t *dia_err, const uint32_t *dia_exp_err) +{ + if (dia_exp_err) { + } + if (dia_err) { + switch (*dia_err) { + case OGS_DIAM_UNKNOWN_SESSION_ID: + return OGS_GTP_CAUSE_APN_ACCESS_DENIED_NO_SUBSCRIPTION; + } + } + + ogs_error("Unexpected Diameter Result Code %d/%d, defaulting to severe " + "network failure", + dia_err ? *dia_err : -1, dia_exp_err ? *dia_exp_err : -1); + return OGS_GTP_CAUSE_UE_NOT_AUTHORISED_BY_OCS_OR_EXTERNAL_AAA_SERVER; +} + +static void urr_enable_total_volume_threshold(smf_sess_t *sess, ogs_pfcp_urr_t *urr, + uint64_t total_volume_threshold) +{ + ogs_debug("Adding CC Grant total_octets=%" PRIu64, total_volume_threshold); + urr->meas_method |= OGS_PFCP_MEASUREMENT_METHOD_VOLUME; + urr->rep_triggers.volume_threshold = 1; + urr->vol_threshold.tovol = 1; + urr->vol_threshold.total_volume = total_volume_threshold; + if (sess->pfcp_node->up_function_features.mnop) + urr->meas_info.mnop = 1; +} + +static void urr_disable_total_volume_threshold(ogs_pfcp_urr_t *urr) +{ + urr->meas_method &= ~OGS_PFCP_MEASUREMENT_METHOD_VOLUME; + urr->rep_triggers.volume_threshold = 0; + urr->vol_threshold.tovol = 0; + urr->vol_threshold.total_volume = 0; +} + +static void urr_update_total_volume_threshold(smf_sess_t *sess, ogs_pfcp_urr_t *urr, ogs_diam_gy_message_t *gy_message) +{ + if (gy_message->cca.granted.cc_total_octets_present) + urr_enable_total_volume_threshold(sess, urr, gy_message->cca.granted.cc_total_octets); + else + urr_disable_total_volume_threshold(urr); +} + +static void urr_update_time_threshold(ogs_pfcp_urr_t *urr, ogs_diam_gy_message_t *gy_message) +{ + uint32_t time_threshold; + if (gy_message->cca.validity_time && (gy_message->cca.granted.cc_time_present && gy_message->cca.granted.cc_time > 0)) + time_threshold = (gy_message->cca.validity_time <= gy_message->cca.granted.cc_time) ? + gy_message->cca.validity_time : gy_message->cca.granted.cc_time; + else if (gy_message->cca.validity_time) + time_threshold = gy_message->cca.validity_time; + else if (gy_message->cca.granted.cc_time_present && gy_message->cca.granted.cc_time > 0) + time_threshold = gy_message->cca.granted.cc_time; + else + time_threshold = 0; + if (time_threshold) { + ogs_debug("Adding CC Grant time=%" PRIu32, time_threshold); + urr->meas_method |= OGS_PFCP_MEASUREMENT_METHOD_DURATION; + urr->rep_triggers.time_threshold = 1; + urr->time_threshold = time_threshold; + urr->meas_info.istm = 1; + } else { + urr->meas_method &= ~OGS_PFCP_MEASUREMENT_METHOD_DURATION; + urr->rep_triggers.time_threshold = 0; + urr->time_threshold = 0; + } +} + +void smf_gy_handle_cca_initial_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_gtp_xact_t *gtp_xact) +{ + smf_bearer_t *bearer; + + ogs_assert(sess); + ogs_assert(gy_message); + ogs_assert(gtp_xact); + + ogs_debug("[Gy CCA Initial]"); + ogs_debug(" SGW_S5C_TEID[0x%x] PGW_S5C_TEID[0x%x]", + sess->sgw_s5c_teid, sess->smf_n4_teid); + + if (gy_message->result_code != ER_DIAMETER_SUCCESS) { + uint8_t cause_value = gtp_cause_from_diameter( + gy_message->err, gy_message->exp_err); + + if (gtp_xact->gtp_version == 1) + ogs_gtp1_send_error_message(gtp_xact, sess ? sess->sgw_s5c_teid : 0, + OGS_GTP1_CREATE_PDP_CONTEXT_RESPONSE_TYPE, cause_value); + else + ogs_gtp_send_error_message(gtp_xact, sess ? sess->sgw_s5c_teid : 0, + OGS_GTP_CREATE_SESSION_RESPONSE_TYPE, cause_value); + return; + } + + bearer = smf_default_bearer_in_sess(sess); + ogs_assert(bearer); + + if (!bearer->urr) + bearer->urr = ogs_pfcp_urr_add(&sess->pfcp); + ogs_assert(bearer->urr); + + /* Configure based on what we received from OCS: */ + urr_update_time_threshold(bearer->urr, gy_message); + urr_update_total_volume_threshold(sess, bearer->urr, gy_message); + + /* Associate acconting URR each direction PDR: */ + ogs_pfcp_pdr_associate_urr(bearer->ul_pdr, bearer->urr); + ogs_pfcp_pdr_associate_urr(bearer->dl_pdr, bearer->urr); + + ogs_assert(OGS_OK == + smf_epc_pfcp_send_session_establishment_request(sess, gtp_xact)); +} + +void smf_gy_handle_cca_update_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_pfcp_xact_t *pfcp_xact) +{ + ogs_pfcp_urr_t *urr = NULL; + smf_bearer_t *bearer; + int rv; + uint64_t modify_flags = 0; + ogs_pfcp_measurement_method_t prev_meas_method; + ogs_pfcp_reporting_triggers_t prev_rep_triggers; + ogs_pfcp_volume_threshold_t prev_vol_threshold; + ogs_pfcp_time_threshold_t prev_time_threshold; + + ogs_assert(sess); + ogs_assert(gy_message); + ogs_assert(pfcp_xact); + + ogs_debug("[Gy CCA Update]"); + ogs_debug(" SGW_S5C_TEID[0x%x] PGW_S5C_TEID[0x%x]", + sess->sgw_s5c_teid, sess->smf_n4_teid); + + if (gy_message->result_code != ER_DIAMETER_SUCCESS) { + ogs_warn("Gy CCA Update Diameter failure: res=%u err=%u", + gy_message->result_code, *gy_message->err); + // TODO: generate new gtp_xact from sess here? */ + //ogs_assert(OGS_OK == + // smf_epc_pfcp_send_session_deletion_request(sess, gtp_xact)); + return; + } + + bearer = smf_default_bearer_in_sess(sess); + ogs_assert(bearer); + + urr = bearer->urr; + ogs_assert(urr); + prev_meas_method = urr->meas_method; + prev_rep_triggers = urr->rep_triggers; + prev_vol_threshold = urr->vol_threshold; + prev_time_threshold = urr->time_threshold; + + urr_update_time_threshold(urr, gy_message); + urr_update_total_volume_threshold(sess, urr, gy_message); + ogs_pfcp_pdr_associate_urr(bearer->ul_pdr, urr); + + if (urr->meas_method != prev_meas_method) + modify_flags |= OGS_PFCP_MODIFY_URR_MEAS_METHOD; + if (urr->rep_triggers.time_threshold != prev_rep_triggers.time_threshold || + urr->rep_triggers.volume_threshold != prev_rep_triggers.volume_threshold) + modify_flags |= OGS_PFCP_MODIFY_URR_REPORT_TRIGGER; + + if (urr->time_threshold != prev_time_threshold) + modify_flags |= OGS_PFCP_MODIFY_URR_TIME_THRESH; + + if (urr->vol_threshold.tovol != prev_vol_threshold.tovol || + urr->vol_threshold.total_volume != prev_vol_threshold.total_volume) + modify_flags |= OGS_PFCP_MODIFY_URR_VOLUME_THRESH; + + /* Send PFCP Session Modification Request if we need to update the params. */ + if (modify_flags) { + modify_flags |= OGS_PFCP_MODIFY_URR|OGS_PFCP_MODIFY_UL_ONLY; + rv = smf_epc_pfcp_send_session_modification_request(sess, pfcp_xact, + modify_flags, + OGS_NAS_PROCEDURE_TRANSACTION_IDENTITY_UNASSIGNED, + OGS_GTP1_CAUSE_REACTIACTION_REQUESTED); + ogs_assert(rv == OGS_OK); + } +} + +void smf_gy_handle_cca_termination_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_gtp_xact_t *gtp_xact) +{ + ogs_assert(sess); + ogs_assert(gy_message); + ogs_assert(gtp_xact); + + ogs_debug("[SMF] Delete Session Response"); + ogs_debug(" SGW_S5C_TEID[0x%x] SMF_N4_TEID[0x%x]", + sess->sgw_s5c_teid, sess->smf_n4_teid); + + if (gtp_xact) { + /* + * 1. MME sends Delete Session Request to SGW/SMF. + * 2. SMF sends Delete Session Response to SGW/MME. + */ + switch (gtp_xact->gtp_version) { + case 1: + ogs_assert(OGS_OK == smf_gtp1_send_delete_pdp_context_response(sess, gtp_xact)); + break; + case 2: + ogs_assert(OGS_OK == smf_gtp_send_delete_session_response(sess, gtp_xact)); + break; + } + } else { + /* + * 1. SMF sends Delete Bearer Request(DEFAULT BEARER) to SGW/MME. + * 2. MME sends Delete Bearer Response to SGW/SMF. + * + * OR + * + * 1. SMF sends Delete Bearer Request(DEFAULT BEARER) to ePDG. + * 2. ePDG sends Delete Bearer Response(DEFAULT BEARER) to SMF. + * + * Note that the following messages are not processed here. + * - Bearer Resource Command + * - Delete Bearer Request/Response with DEDICATED BEARER. + */ + } + SMF_SESS_CLEAR(sess); + return; +} + +void smf_gy_handle_re_auth_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message) +{ + /* TODO: find out what to do here */ +} diff --git a/src/smf/gy-handler.h b/src/smf/gy-handler.h new file mode 100644 index 000000000..6dde724eb --- /dev/null +++ b/src/smf/gy-handler.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH + * + * This file is part of Open5GS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SMF_GY_HANDLER_H +#define SMF_GY_HANDLER_H + +#include "context.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void smf_gy_handle_cca_initial_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_gtp_xact_t *gtp_xact); +void smf_gy_handle_cca_update_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_pfcp_xact_t *gtp_xact); +void smf_gy_handle_cca_termination_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message, + ogs_gtp_xact_t *gtp_xact); +void smf_gy_handle_re_auth_request( + smf_sess_t *sess, ogs_diam_gy_message_t *gy_message); + +#ifdef __cplusplus +} +#endif + +#endif /* SMF_GY_HANDLER_H */ diff --git a/src/smf/gy-path.c b/src/smf/gy-path.c new file mode 100644 index 000000000..85e6e02f3 --- /dev/null +++ b/src/smf/gy-path.c @@ -0,0 +1,1129 @@ +/* + * Copyright (C) 2019 by Sukchan Lee + * Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH + * + * This file is part of Open5GS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "fd-path.h" + +static struct session_handler *smf_gy_reg = NULL; +static struct disp_hdl *hdl_gy_fb = NULL; +static struct disp_hdl *hdl_gy_rar = NULL; + +struct sess_state { + os0_t gy_sid; /* Gy Session-Id */ + + os0_t peer_host; /* Peer Host */ + +#define MAX_CC_REQUEST_NUMBER 32 + smf_sess_t *sess; + struct { + bool pfcp; + void *ptr; /* INITIAL: ogs_gtp_xact_t, UPDATE: ogs_pfcp_xact_t */ + } xact_data[MAX_CC_REQUEST_NUMBER]; + uint32_t cc_request_type; + uint32_t cc_request_number; + + struct timespec ts; /* Time of sending the message */ +}; + +static OGS_POOL(sess_state_pool, struct sess_state); +static ogs_thread_mutex_t sess_state_mutex; + +static int decode_granted_service_unit( + ogs_diam_gy_service_unit_t *su, struct avp *avpch1, int *perror); +static void smf_gy_cca_cb(void *data, struct msg **msg); + +static __inline__ struct sess_state *new_state(os0_t sid) +{ + struct sess_state *new = NULL; + + ogs_thread_mutex_lock(&sess_state_mutex); + ogs_pool_alloc(&sess_state_pool, &new); + ogs_expect_or_return_val(new, NULL); + memset(new, 0, sizeof(*new)); + ogs_thread_mutex_unlock(&sess_state_mutex); + + new->gy_sid = (os0_t)ogs_strdup((char *)sid); + ogs_expect_or_return_val(new->gy_sid, NULL); + + return new; +} + +static void state_cleanup(struct sess_state *sess_data, os0_t sid, void *opaque) +{ + if (sess_data->gy_sid) + ogs_free(sess_data->gy_sid); + + if (sess_data->peer_host) + ogs_free(sess_data->peer_host); + + ogs_thread_mutex_lock(&sess_state_mutex); + ogs_pool_free(&sess_state_pool, sess_data); + ogs_thread_mutex_unlock(&sess_state_mutex); +} + +/* TS 32.299 7.1.9 Multiple-Services-Credit-Control AVP for CCR */ +static void fill_multiple_services_credit_control_ccr(smf_sess_t *sess, + uint32_t cc_request_type, struct msg *req) +{ + int ret; + union avp_value val; + struct avp *avp; + struct avp *avpch1, *avpch2, *avpch3; + + /* Multiple-Services-Credit-Control */ + ret = fd_msg_avp_new(ogs_diam_gy_multiple_services_cc, 0, &avp); + ogs_assert(ret == 0); + + /* Requested-Service-Unit, RFC4006 8.18 */ + if (cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_INITIAL_REQUEST || + cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST) { + ret = fd_msg_avp_new(ogs_diam_gy_requested_service_unit, 0, &avpch1); + ogs_assert(ret == 0); + + /* CC-Time, RFC4006 8.21 */ + /* CC-Money, RFC4006 8.22. Not used in 3GPP. */ + /* CC-Total-Octets, RFC4006 8.23 */ + /* CC-Input-Octets, RFC4006 8.24 */ + /* CC-Output-Octets, RFC4006 8.25 */ + /* CC-Service-Specific-Units, RFC4006 8.26 */ + + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + } + + /* Used-Service-Unit, RFC4006 8.18 */ + ret = fd_msg_avp_new(ogs_diam_gy_used_service_unit, 0, &avpch1); + ogs_assert(ret == 0); + + /* Reporting-Reason, TS 32.299 7.2.175 */ + /* Tariff-Change-Usage */ + + /* CC-Time, RFC4006 8.21 */ + ret = fd_msg_avp_new(ogs_diam_gy_cc_time, 0, &avpch2); + ogs_assert(ret == 0); + val.u32 = sess->gy.duration - sess->gy.last_report.duration; + sess->gy.last_report.duration = sess->gy.duration; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + + /* CC-Total-Octets, RFC4006 8.23 */ + + /* CC-Input-Octets, RFC4006 8.24 */ + ret = fd_msg_avp_new(ogs_diam_gy_cc_input_octets, 0, &avpch2); + ogs_assert(ret == 0); + val.u64 = sess->gy.ul_octets - sess->gy.last_report.ul_octets; + sess->gy.last_report.ul_octets = sess->gy.ul_octets; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + + /* CC-Output-Octets, RFC4006 8.25 */ + ret = fd_msg_avp_new(ogs_diam_gy_cc_output_octets, 0, &avpch2); + ogs_assert(ret == 0); + val.u64 = sess->gy.dl_octets - sess->gy.last_report.dl_octets; + sess->gy.last_report.dl_octets = sess->gy.dl_octets; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + + /* CC-Service-Specific-Units, RFC4006 8.26 */ + + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + + /* Service-Identifier, RFC4006 8.28. Not used in Gy. */ + /* Rating-Group */ + + /* Reporting-Reason, TS 32.299 7.2.175 */ + if (cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST || + cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST) { + ret = fd_msg_avp_new(ogs_diam_gy_reporting_reason, 0, &avpch1); + ogs_assert(ret == 0); + val.u32 = OGS_DIAM_GY_REPORTING_REASON_VALIDITY_TIME; // TODO: set value + ret = fd_msg_avp_setvalue (avpch1, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + } + + /* ... lots of AVPs ... */ + + /* QoS-Information */ + ret = fd_msg_avp_new(ogs_diam_gx_qos_information, 0, &avpch1); + ogs_assert(ret == 0); + + /* QoS-Class-Identifier */ + ret = fd_msg_avp_new(ogs_diam_gy_qos_class_identifier, 0, &avpch2); + ogs_assert(ret == 0); + val.u32 = sess->session.qos.index; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + + /* Allocation-Retention-Priority */ + ret = fd_msg_avp_new( + ogs_diam_gy_allocation_retention_priority, 0, &avpch2); + ogs_assert(ret == 0); + + /* Priority-Level */ + ret = fd_msg_avp_new(ogs_diam_gy_priority_level, 0, &avpch3); + ogs_assert(ret == 0); + val.u32 = sess->session.qos.arp.priority_level; + ret = fd_msg_avp_setvalue (avpch3, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch2, MSG_BRW_LAST_CHILD, avpch3); + ogs_assert(ret == 0); + + /* Pre-emption-Capability */ + ret = fd_msg_avp_new(ogs_diam_gy_pre_emption_capability, 0, &avpch3); + ogs_assert(ret == 0); + val.u32 = sess->session.qos.arp.pre_emption_capability; + ret = fd_msg_avp_setvalue (avpch3, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch2, MSG_BRW_LAST_CHILD, avpch3); + ogs_assert(ret == 0); + + /* Pre-emption-Vulnerability */ + ret = fd_msg_avp_new(ogs_diam_gy_pre_emption_vulnerability, 0, &avpch3); + ogs_assert(ret == 0); + val.u32 = sess->session.qos.arp.pre_emption_vulnerability; + ret = fd_msg_avp_setvalue (avpch3, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch2, MSG_BRW_LAST_CHILD, avpch3); + ogs_assert(ret == 0); + + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + + if (sess->session.ambr.uplink) { + ret = fd_msg_avp_new(ogs_diam_gy_apn_aggregate_max_bitrate_ul, + 0, &avpch2); + ogs_assert(ret == 0); + val.u32 = sess->session.ambr.uplink; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + } + if (sess->session.ambr.downlink) { + ret = fd_msg_avp_new( + ogs_diam_gy_apn_aggregate_max_bitrate_dl, 0, &avpch2); + ogs_assert(ret == 0); + val.u32 = sess->session.ambr.downlink; + ret = fd_msg_avp_setvalue (avpch2, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avpch1, MSG_BRW_LAST_CHILD, avpch2); + ogs_assert(ret == 0); + } + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + + /* Multiple Services AVP add to req: */ + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); +} + +/* 3GPP TS 32.299 6.4.2 Credit-Control-Request message */ +void smf_gy_send_ccr(smf_sess_t *sess, void *xact, + uint32_t cc_request_type) +{ + int ret; + smf_ue_t *smf_ue = NULL; + + struct msg *req = NULL; + struct avp *avp; + struct avp *avpch1; + union avp_value val; + struct sess_state *sess_data = NULL, *svg; + struct session *session = NULL; + int new; + char buf[OGS_PLMNIDSTRLEN]; + const char *service_context_id = "open5gs-smfd@open5gs.org"; + uint32_t timestamp; + + ogs_assert(xact); + ogs_assert(sess); + + ogs_assert(sess->ipv4 || sess->ipv6); + smf_ue = sess->smf_ue; + ogs_assert(smf_ue); + + ogs_debug("[Gy][Credit-Control-Request]"); + + /* Create the request */ + ret = fd_msg_new(ogs_diam_gy_cmd_ccr, MSGFL_ALLOC_ETEID, &req); + ogs_assert(ret == 0); + { + struct msg_hdr *h; + ret = fd_msg_hdr(req, &h); + ogs_assert(ret == 0); + h->msg_appl = OGS_DIAM_GY_APPLICATION_ID; + } + + /* Find Diameter Gy Session */ + if (sess->gy_sid) { + /* Retrieve session by Session-Id */ + size_t sidlen = strlen(sess->gy_sid); + ret = fd_sess_fromsid_msg((os0_t)sess->gy_sid, sidlen, &session, &new); + ogs_assert(ret == 0); + ogs_assert(new == 0); + + ogs_debug(" Found Gy Session-Id: [%s]", sess->gy_sid); + + /* Add Session-Id to the message */ + ret = ogs_diam_message_session_id_set(req, (os0_t)sess->gy_sid, sidlen); + ogs_assert(ret == 0); + /* Save the session associated with the message */ + ret = fd_msg_sess_set(req, session); + } else { + /* Create a new session */ + #define OGS_DIAM_GY_APP_SID_OPT "app_gy" + ret = fd_msg_new_session(req, (os0_t)OGS_DIAM_GY_APP_SID_OPT, + CONSTSTRLEN(OGS_DIAM_GY_APP_SID_OPT)); + ogs_assert(ret == 0); + ret = fd_msg_sess_get(fd_g_config->cnf_dict, req, &session, NULL); + ogs_assert(ret == 0); + ogs_debug(" Create a New Gy Session"); + } + + /* Retrieve session state in this session */ + ret = fd_sess_state_retrieve(smf_gy_reg, session, &sess_data); + if (!sess_data) { + os0_t sid; + size_t sidlen; + + ret = fd_sess_getsid(session, &sid, &sidlen); + ogs_assert(ret == 0); + + /* Allocate new session state memory */ + sess_data = new_state(sid); + ogs_assert(sess_data); + + ogs_debug(" Allocate new Gy session: [%s]", sess_data->gy_sid); + + /* Save Session-Id to SMF Session Context */ + sess->gy_sid = (char *)sess_data->gy_sid; + } else + ogs_debug(" Retrieve Gy session: [%s]", sess_data->gy_sid); + + /* + * 8.2. CC-Request-Number AVP + * + * The CC-Request-Number AVP (AVP Code 415) is of type Unsigned32 and + * identifies this request within one session. As Session-Id AVPs are + * globally unique, the combination of Session-Id and CC-Request-Number + * AVPs is also globally unique and can be used in matching credit- + * control messages with confirmations. An easy way to produce unique + * numbers is to set the value to 0 for a credit-control request of type + * INITIAL_REQUEST and EVENT_REQUEST and to set the value to 1 for the + * first UPDATE_REQUEST, to 2 for the second, and so on until the value + * for TERMINATION_REQUEST is one more than for the last UPDATE_REQUEST. + */ + + sess_data->cc_request_type = cc_request_type; + if (cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_INITIAL_REQUEST || + cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_EVENT_REQUEST) + sess_data->cc_request_number = 0; + else + sess_data->cc_request_number++; + + ogs_debug(" CC Request Type[%d] Number[%d]", + sess_data->cc_request_type, sess_data->cc_request_number); + ogs_assert(sess_data->cc_request_number <= MAX_CC_REQUEST_NUMBER); + + /* Update session state */ + sess_data->sess = sess; + if (cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST) + sess_data->xact_data[sess_data->cc_request_number].pfcp = true; + else + sess_data->xact_data[sess_data->cc_request_number].pfcp = false; + sess_data->xact_data[sess_data->cc_request_number].ptr = xact; + + /* Origin-Host & Origin-Realm */ + ret = fd_msg_add_origin(req, 0); + ogs_assert(ret == 0); + + /* the Destination-Realm AVP */ + ret = fd_msg_avp_new(ogs_diam_destination_realm, 0, &avp); + ogs_assert(ret == 0); + val.os.data = (unsigned char *)(fd_g_config->cnf_diamrlm); + val.os.len = strlen(fd_g_config->cnf_diamrlm); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* the Auth-Application-Id AVP */ + ret = fd_msg_avp_new(ogs_diam_auth_application_id, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_GY_APPLICATION_ID; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Service-Context-Id */ + ret = fd_msg_avp_new(ogs_diam_service_context_id, 0, &avp); + ogs_assert(ret == 0); + val.os.data = (unsigned char *)service_context_id; + val.os.len = strlen(service_context_id); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* CC-Request-Type, CC-Request-Number */ + ret = fd_msg_avp_new(ogs_diam_gy_cc_request_type, 0, &avp); + ogs_assert(ret == 0); + val.i32 = sess_data->cc_request_type; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + ret = fd_msg_avp_new(ogs_diam_gy_cc_request_number, 0, &avp); + ogs_assert(ret == 0); + val.i32 = sess_data->cc_request_number; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Set the Destination-Host AVP */ + if (sess_data->peer_host) { + ret = fd_msg_avp_new(ogs_diam_destination_host, 0, &avp); + ogs_assert(ret == 0); + val.os.data = sess_data->peer_host; + val.os.len = strlen((char *)sess_data->peer_host); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + } + + /* User-Name */ +#if 0 + ret = fd_msg_avp_new(ogs_diam_user_name, 0, &avp); + ogs_assert(ret == 0); + val.os.data = ?; + val.os.len = strlen(?); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); +#endif + + /* Origin-State-Id */ +#if 0 + /* TODO: implement restart counter */ + ret = fd_msg_avp_new(ogs_diam_gy_origin_state_id, 0, &avp); + ogs_assert(ret == 0); + val.i32 = ?; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); +#endif + + /* Event-Timestamp (rfc6733 8.21, type in 4.3.1) */ + ret = fd_msg_avp_new(ogs_diam_event_timestamp, 0, &avp); + ogs_assert(ret == 0); + timestamp = htobe32(ogs_time_ntp32_now()); + val.os.data = (unsigned char *)×tamp; + val.os.len = 4; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Subscription-Id */ + ret = fd_msg_avp_new(ogs_diam_subscription_id, 0, &avp); + ogs_assert(ret == 0); + + ret = fd_msg_avp_new(ogs_diam_subscription_id_type, 0, &avpch1); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_SUBSCRIPTION_ID_TYPE_END_USER_IMSI; + ret = fd_msg_avp_setvalue (avpch1, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + + ret = fd_msg_avp_new(ogs_diam_subscription_id_data, 0, &avpch1); + ogs_assert(ret == 0); + val.os.data = (uint8_t *)smf_ue->imsi_bcd; + val.os.len = strlen(smf_ue->imsi_bcd); + ret = fd_msg_avp_setvalue (avpch1, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add (avp, MSG_BRW_LAST_CHILD, avpch1); + ogs_assert(ret == 0); + + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Termination-Cause */ + if (cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST) { + ret = fd_msg_avp_new(ogs_diam_termination_cause, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_TERMINATION_CAUSE_DIAMETER_LOGOUT; /* TODO: set specific cause */ + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + } + + /* Requested-Action */ + ret = fd_msg_avp_new(ogs_diam_gy_requested_action, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_GY_REQUESTED_ACTION_DIRECT_DEBITING; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* AoC-Request-Type */ + ret = fd_msg_avp_new(ogs_diam_gy_aoc_request_type, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_GY_AoC_FULL; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Multiple-Services-Indicator */ + ret = fd_msg_avp_new(ogs_diam_gy_multiple_services_ind, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_GY_MULTIPLE_SERVICES_NOT_SUPPORTED; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* TS 32.299 7.1.9 Multiple-Services-Credit-Control AVP */ + fill_multiple_services_credit_control_ccr(sess, cc_request_type, req); + + /* OC-Supported-Features */ + + /* 3GPP-User-Location-Info */ + if (sess->gtp.user_location_information.presence) { + ogs_gtp_uli_t uli; + int16_t uli_len; + + uint8_t uli_buf[OGS_GTP_MAX_ULI_LEN]; + + uli_len = ogs_gtp_parse_uli( + &uli, &sess->gtp.user_location_information); + ogs_assert(sess->gtp.user_location_information.len == uli_len); + + ogs_assert(sess->gtp.user_location_information.data); + ogs_assert(sess->gtp.user_location_information.len); + memcpy(&uli_buf, sess->gtp.user_location_information.data, + sess->gtp.user_location_information.len); + + /* Update Gy ULI Type */ + if (uli.flags.tai && uli.flags.e_cgi) + uli_buf[0] = + OGS_DIAM_GY_3GPP_USER_LOCATION_INFO_TYPE_TAI_AND_ECGI; + else if (uli.flags.tai) + uli_buf[0] = OGS_DIAM_GY_3GPP_USER_LOCATION_INFO_TYPE_TAI; + else if (uli.flags.e_cgi) + uli_buf[0] = OGS_DIAM_GY_3GPP_USER_LOCATION_INFO_TYPE_ECGI; + + if (uli_buf[0]) { + ret = fd_msg_avp_new( + ogs_diam_gy_3gpp_user_location_info, 0, &avp); + ogs_assert(ret == 0); + val.os.data = (uint8_t *)&uli_buf; + val.os.len = sess->gtp.user_location_information.len; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + } + } + + /* 3GPP-MS-Timezone */ + if (sess->gtp.ue_timezone.presence && + sess->gtp.ue_timezone.len && sess->gtp.ue_timezone.data) { + ret = fd_msg_avp_new(ogs_diam_gy_3gpp_ms_timezone, 0, &avp); + ogs_assert(ret == 0); + val.os.data = sess->gtp.ue_timezone.data; + val.os.len = sess->gtp.ue_timezone.len; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + } + + /* 3GPP-SGSN-MCC-MNC */ + ret = fd_msg_avp_new(ogs_diam_gy_3gpp_sgsn_mcc_mnc, 0, &avp); + ogs_assert(ret == 0); + val.os.data = (uint8_t *)ogs_plmn_id_to_string(&sess->plmn_id, buf); + val.os.len = strlen(buf); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Called-Station-Id */ + ret = fd_msg_avp_new(ogs_diam_gy_called_station_id, 0, &avp); + ogs_assert(ret == 0); + ogs_assert(sess->session.name); + val.os.data = (uint8_t*)sess->session.name; + val.os.len = strlen(sess->session.name); + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(req, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + + ret = clock_gettime(CLOCK_REALTIME, &sess_data->ts); + + /* Keep a pointer to the session data for debug purpose, + * in real life we would not need it */ + svg = sess_data; + + /* Store this value in the session */ + ret = fd_sess_state_store(smf_gy_reg, session, &sess_data); + ogs_assert(ret == 0); + ogs_assert(sess_data == NULL); + + /* Send the request */ + ret = fd_msg_send(&req, smf_gy_cca_cb, svg); + ogs_assert(ret == 0); + + /* Increment the counter */ + ogs_assert(pthread_mutex_lock(&ogs_diam_logger_self()->stats_lock) == 0); + ogs_diam_logger_self()->stats.nb_sent++; + ogs_assert(pthread_mutex_unlock(&ogs_diam_logger_self()->stats_lock) == 0); +} + +static void smf_gy_cca_cb(void *data, struct msg **msg) +{ + int rv; + int ret; + + struct sess_state *sess_data = NULL; + struct timespec ts; + struct session *session; + struct avp *avp, *avpch1; + struct avp_hdr *hdr; + unsigned long dur; + int error = 0; + int new; + struct msg *req = NULL; + smf_event_t *e = NULL; + void *xact = NULL; + smf_sess_t *sess = NULL; + ogs_diam_gy_message_t *gy_message = NULL; + uint32_t cc_request_number = 0; + + ogs_debug("[Gy][Credit-Control-Answer]"); + + ret = clock_gettime(CLOCK_REALTIME, &ts); + ogs_assert(ret == 0); + + /* Get originating request of received message, if any */ + ret = fd_msg_answ_getq(*msg, &req); + ogs_assert(ret == 0); + + /* Search the session, retrieve its data */ + ret = fd_msg_sess_get(fd_g_config->cnf_dict, *msg, &session, &new); + ogs_assert(ret == 0); + ogs_assert(new == 0); + + ogs_debug(" Search the Gy session"); + + ret = fd_sess_state_retrieve(smf_gy_reg, session, &sess_data); + ogs_assert(ret == 0); + ogs_assert(sess_data); + ogs_assert((void *)sess_data == data); + + ogs_debug(" Retrieve its data: [%s]", sess_data->gy_sid); + + /* Value of CC-Request-Number */ + ret = fd_msg_search_avp(*msg, ogs_diam_gy_cc_request_number, &avp); + ogs_assert(ret == 0); + if (!avp && req) { + /* Attempt searching for CC-Request-* in original request. Error + * messages (like DIAMETER_UNABLE_TO_DELIVER) crafted internally may not + * have them. */ + ret = fd_msg_search_avp(req, ogs_diam_gy_cc_request_number, &avp); + ogs_assert(ret == 0); + } + if (!avp) { + ogs_error("no_CC-Request-Number"); + ogs_assert_if_reached(); + } + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + cc_request_number = hdr->avp_value->i32; + + ogs_debug(" CC-Request-Number[%d]", cc_request_number); + + xact = sess_data->xact_data[cc_request_number].ptr; + ogs_assert(xact); + sess = sess_data->sess; + ogs_assert(sess); + + gy_message = ogs_calloc(1, sizeof(ogs_diam_gy_message_t)); + ogs_assert(gy_message); + + /* Set Credit Control Command */ + gy_message->cmd_code = OGS_DIAM_GY_CMD_CODE_CREDIT_CONTROL; + + /* Value of Result Code */ + ret = fd_msg_search_avp(*msg, ogs_diam_result_code, &avp); + ogs_assert(ret == 0); + if (avp) { + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + gy_message->result_code = hdr->avp_value->i32; + gy_message->err = &gy_message->result_code; + ogs_debug(" Result Code: %d", hdr->avp_value->i32); + } else { + ret = fd_msg_search_avp(*msg, ogs_diam_experimental_result, &avp); + ogs_assert(ret == 0); + if (avp) { + ret = fd_avp_search_avp( + avp, ogs_diam_experimental_result_code, &avpch1); + ogs_assert(ret == 0); + if (avpch1) { + ret = fd_msg_avp_hdr(avpch1, &hdr); + ogs_assert(ret == 0); + gy_message->result_code = hdr->avp_value->i32; + gy_message->exp_err = &gy_message->result_code; + ogs_debug(" Experimental Result Code: %d", + gy_message->result_code); + } + } else { + ogs_error("no Result-Code"); + error++; + } + } + + /* Value of Origin-Host */ + ret = fd_msg_search_avp(*msg, ogs_diam_origin_host, &avp); + ogs_assert(ret == 0); + if (avp) { + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + ogs_debug(" From '%.*s'", + (int)hdr->avp_value->os.len, hdr->avp_value->os.data); + } else { + ogs_error("no_Origin-Host"); + error++; + } + + /* Value of Origin-Realm */ + ret = fd_msg_search_avp(*msg, ogs_diam_origin_realm, &avp); + ogs_assert(ret == 0); + if (avp) { + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + ogs_debug(" ('%.*s')", + (int)hdr->avp_value->os.len, hdr->avp_value->os.data); + } else { + ogs_error("no_Origin-Realm"); + error++; + } + + /* Value of CC-Request-Type */ + ret = fd_msg_search_avp(*msg, ogs_diam_gy_cc_request_type, &avp); + ogs_assert(ret == 0); + if (!avp && req) { + /* Attempt searching for CC-Request-* in original request. Error + * messages (like DIAMETER_UNABLE_TO_DELIVER) crafted internally may not + * have them. */ + ret = fd_msg_search_avp(req, ogs_diam_gy_cc_request_type, &avp); + ogs_assert(ret == 0); + } + if (!avp) { + ogs_error("no_CC-Request-Number"); + error++; + } + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + gy_message->cc_request_type = hdr->avp_value->i32; + + if (gy_message->result_code != ER_DIAMETER_SUCCESS) { + ogs_warn("ERROR DIAMETER Result Code(%d)", gy_message->result_code); + goto out; + } + + ret = fd_msg_browse(*msg, MSG_BRW_FIRST_CHILD, &avp, NULL); + ogs_assert(ret == 0); + while (avp) { + ret = fd_msg_avp_hdr(avp, &hdr); + ogs_assert(ret == 0); + switch (hdr->avp_code) { + case AC_SESSION_ID: + case AC_ORIGIN_HOST: + if (sess_data->peer_host) + ogs_free(sess_data->peer_host); + sess_data->peer_host = + (os0_t)ogs_strdup((char *)hdr->avp_value->os.data); + ogs_assert(sess_data->peer_host); + break; + case AC_ORIGIN_REALM: + case AC_DESTINATION_REALM: + case AC_RESULT_CODE: + case AC_ROUTE_RECORD: + case AC_PROXY_INFO: + case AC_AUTH_APPLICATION_ID: + break; + case OGS_DIAM_GY_AVP_CODE_CC_REQUEST_TYPE: + case OGS_DIAM_GY_AVP_CODE_CC_REQUEST_NUMBER: + case OGS_DIAM_GY_AVP_CODE_SUPPORTED_FEATURES: + break; + case OGS_DIAM_GY_AVP_CODE_MULTIPLE_SERVICES_CREDIT_CONTROL: + ret = fd_msg_browse(avp, MSG_BRW_FIRST_CHILD, &avpch1, NULL); + ogs_assert(ret == 0); + while (avpch1) { + ret = fd_msg_avp_hdr(avpch1, &hdr); + ogs_assert(ret == 0); + switch (hdr->avp_code) { + case OGS_DIAM_GY_AVP_CODE_GRANTED_SERVICE_UNIT: + rv = decode_granted_service_unit( + &gy_message->cca.granted, avpch1, &error); + ogs_assert(rv == OGS_OK); + /*TODO: apply gsu */ + break; + case OGS_DIAM_GY_AVP_CODE_VALIDITY_TIME: + gy_message->cca.validity_time = hdr->avp_value->u32; + break; + default: + ogs_warn("Not supported(%d)", hdr->avp_code); + break; + } + fd_msg_browse(avpch1, MSG_BRW_NEXT, &avpch1, NULL); + } + break; + default: + ogs_warn("Not supported(%d)", hdr->avp_code); + break; + } + fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL); + } + +out: + if (!error) { + e = smf_event_new(SMF_EVT_GY_MESSAGE); + ogs_assert(e); + + e->sess = sess; + e->gy_message = gy_message; + if (gy_message->cc_request_type == OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST) { + ogs_assert(sess_data->xact_data[sess_data->cc_request_number].pfcp == true); + e->pfcp_xact = xact; + } else { + ogs_assert(sess_data->xact_data[sess_data->cc_request_number].pfcp == false); + e->gtp_xact = xact; + } + rv = ogs_queue_push(ogs_app()->queue, e); + if (rv != OGS_OK) { + ogs_warn("ogs_queue_push() failed:%d", (int)rv); + ogs_free(gy_message); + smf_event_free(e); + } else { + ogs_pollset_notify(ogs_app()->pollset); + } + } else { + ogs_free(gy_message); + } + + /* Free the message */ + ogs_assert(pthread_mutex_lock(&ogs_diam_logger_self()->stats_lock) == 0); + dur = ((ts.tv_sec - sess_data->ts.tv_sec) * 1000000) + + ((ts.tv_nsec - sess_data->ts.tv_nsec) / 1000); + if (ogs_diam_logger_self()->stats.nb_recv) { + /* Ponderate in the avg */ + ogs_diam_logger_self()->stats.avg = (ogs_diam_logger_self()->stats.avg * + ogs_diam_logger_self()->stats.nb_recv + dur) / + (ogs_diam_logger_self()->stats.nb_recv + 1); + /* Min, max */ + if (dur < ogs_diam_logger_self()->stats.shortest) + ogs_diam_logger_self()->stats.shortest = dur; + if (dur > ogs_diam_logger_self()->stats.longest) + ogs_diam_logger_self()->stats.longest = dur; + } else { + ogs_diam_logger_self()->stats.shortest = dur; + ogs_diam_logger_self()->stats.longest = dur; + ogs_diam_logger_self()->stats.avg = dur; + } + if (error) + ogs_diam_logger_self()->stats.nb_errs++; + else + ogs_diam_logger_self()->stats.nb_recv++; + + ogs_assert(pthread_mutex_unlock(&ogs_diam_logger_self()->stats_lock) == 0); + + /* Display how long it took */ + if (ts.tv_nsec > sess_data->ts.tv_nsec) + ogs_trace("in %d.%06ld sec", + (int)(ts.tv_sec - sess_data->ts.tv_sec), + (long)(ts.tv_nsec - sess_data->ts.tv_nsec) / 1000); + else + ogs_trace("in %d.%06ld sec", + (int)(ts.tv_sec + 1 - sess_data->ts.tv_sec), + (long)(1000000000 + ts.tv_nsec - sess_data->ts.tv_nsec) / 1000); + + ogs_debug(" CC-Request-Type[%d] Number[%d] in Session Data", + sess_data->cc_request_type, sess_data->cc_request_number); + ogs_debug(" Current CC-Request-Number[%d]", cc_request_number); + if (sess_data->cc_request_type == + OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST && + sess_data->cc_request_number <= cc_request_number) { + ogs_debug(" [LAST] state_cleanup(): [%s]", sess_data->gy_sid); + state_cleanup(sess_data, NULL, NULL); + } else { + ogs_debug(" fd_sess_state_store(): [%s]", sess_data->gy_sid); + ret = fd_sess_state_store(smf_gy_reg, session, &sess_data); + ogs_assert(ret == 0); + ogs_assert(sess_data == NULL); + } + + ret = fd_msg_free(*msg); + ogs_assert(ret == 0); + *msg = NULL; + + return; +} + +static int smf_gy_fb_cb(struct msg **msg, struct avp *avp, + struct session *sess, void *opaque, enum disp_action *act) +{ + /* This CB should never be called */ + ogs_warn("Unexpected message received!"); + + return ENOTSUP; +} + +static int smf_gy_rar_cb( struct msg **msg, struct avp *avp, + struct session *session, void *opaque, enum disp_action *act) +{ + int rv; + int ret; + + struct msg *ans; + union avp_value val; + struct sess_state *sess_data = NULL; + + smf_event_t *e = NULL; + smf_sess_t *sess = NULL; + ogs_diam_gy_message_t *gy_message = NULL; + + uint32_t result_code = OGS_DIAM_UNKNOWN_SESSION_ID; + + ogs_assert(msg); + + ogs_debug("Re-Auth-Request"); + + gy_message = ogs_calloc(1, sizeof(ogs_diam_gy_message_t)); + ogs_assert(gy_message); + + /* Set Credit Control Command */ + gy_message->cmd_code = OGS_DIAM_GY_CMD_RE_AUTH; + + /* Create answer header */ + ret = fd_msg_new_answer_from_req(fd_g_config->cnf_dict, msg, 0); + ogs_assert(ret == 0); + ans = *msg; + + ret = fd_sess_state_retrieve(smf_gy_reg, session, &sess_data); + ogs_assert(ret == 0); + if (!sess_data) { + ogs_error("No Session Data"); + goto out; + } + + /* Get Session Information */ + sess = sess_data->sess; + ogs_assert(sess); + + /* TODO: parsing of msg into gy_message */ + + /* Send Gy Event to SMF State Machine */ + e = smf_event_new(SMF_EVT_GY_MESSAGE); + ogs_assert(e); + + e->sess = sess; + e->gy_message = gy_message; + rv = ogs_queue_push(ogs_app()->queue, e); + if (rv != OGS_OK) { + ogs_warn("ogs_queue_push() failed:%d", (int)rv); + ogs_free(gy_message); + smf_event_free(e); + } else { + ogs_pollset_notify(ogs_app()->pollset); + } + + /* Set the Auth-Application-Id AVP */ + ret = fd_msg_avp_new(ogs_diam_auth_application_id, 0, &avp); + ogs_assert(ret == 0); + val.i32 = OGS_DIAM_GY_APPLICATION_ID; + ret = fd_msg_avp_setvalue(avp, &val); + ogs_assert(ret == 0); + ret = fd_msg_avp_add(ans, MSG_BRW_LAST_CHILD, avp); + ogs_assert(ret == 0); + + /* Set the Origin-Host, Origin-Realm, andResult-Code AVPs */ + ret = fd_msg_rescode_set(ans, (char *)"DIAMETER_SUCCESS", NULL, NULL, 1); + ogs_assert(ret == 0); + + /* Store this value in the session */ + ret = fd_sess_state_store(smf_gy_reg, session, &sess_data); + ogs_assert(ret == 0); + ogs_assert(sess_data == NULL); + + /* Send the answer */ + ret = fd_msg_send(msg, NULL, NULL); + ogs_assert(ret == 0); + + ogs_debug("Re-Auth-Answer"); + + /* Add this value to the stats */ + ogs_assert(pthread_mutex_lock(&ogs_diam_logger_self()->stats_lock) == 0); + ogs_diam_logger_self()->stats.nb_echoed++; + ogs_assert(pthread_mutex_unlock(&ogs_diam_logger_self()->stats_lock) == 0); + + return 0; + +out: + if (result_code == OGS_DIAM_UNKNOWN_SESSION_ID) { + ret = fd_msg_rescode_set(ans, + (char *)"DIAMETER_UNKNOWN_SESSION_ID", NULL, NULL, 1); + ogs_assert(ret == 0); + } else { + ret = ogs_diam_message_experimental_rescode_set(ans, result_code); + ogs_assert(ret == 0); + } + + /* Store this value in the session */ + ret = fd_sess_state_store(smf_gy_reg, session, &sess_data); + ogs_assert(sess_data == NULL); + + ret = fd_msg_send(msg, NULL, NULL); + ogs_assert(ret == 0); + + ogs_free(gy_message); + + return 0; +} + +int smf_gy_init(void) +{ + int ret; + struct disp_when data; + + ogs_thread_mutex_init(&sess_state_mutex); + ogs_pool_init(&sess_state_pool, ogs_app()->pool.sess); + + /* Install objects definitions for this application */ + ret = ogs_diam_gy_init(); + ogs_assert(ret == 0); + + /* Create handler for sessions */ + ret = fd_sess_handler_create(&smf_gy_reg, state_cleanup, NULL, NULL); + ogs_assert(ret == 0); + + memset(&data, 0, sizeof(data)); + data.app = ogs_diam_gy_application; + + ret = fd_disp_register(smf_gy_fb_cb, DISP_HOW_APPID, &data, NULL, + &hdl_gy_fb); + ogs_assert(ret == 0); + + data.command = ogs_diam_gy_cmd_rar; + ret = fd_disp_register(smf_gy_rar_cb, DISP_HOW_CC, &data, NULL, + &hdl_gy_rar); + ogs_assert(ret == 0); + + /* Advertise the support for the application in the peer */ + ret = fd_disp_app_support(ogs_diam_gy_application, NULL, 1, 0); + ogs_assert(ret == 0); + + return OGS_OK; +} + +void smf_gy_final(void) +{ + int ret; + + ret = fd_sess_handler_destroy(&smf_gy_reg, NULL); + ogs_assert(ret == 0); + + if (hdl_gy_fb) + (void) fd_disp_unregister(&hdl_gy_fb, NULL); + if (hdl_gy_rar) + (void) fd_disp_unregister(&hdl_gy_rar, NULL); + + ogs_pool_final(&sess_state_pool); + ogs_thread_mutex_destroy(&sess_state_mutex); +} + +static int decode_granted_service_unit( + ogs_diam_gy_service_unit_t *su, struct avp *avpch1, int *perror) +{ + int ret = 0, error = 0; + struct avp *avpch2; + struct avp_hdr *hdr; + + ogs_assert(su); + ogs_assert(avpch1); + memset(su, 0, sizeof(*su)); + + ret = fd_msg_browse(avpch1, MSG_BRW_FIRST_CHILD, &avpch2, NULL); + ogs_assert(ret == 0); + while (avpch2) { + ret = fd_msg_avp_hdr(avpch2, &hdr); + ogs_assert(ret == 0); + switch (hdr->avp_code) { + case OGS_DIAM_GY_AVP_CODE_CC_TIME: + su->cc_time_present = true; + su->cc_time = hdr->avp_value->u32; + break; + case OGS_DIAM_GY_AVP_CODE_CC_TOTAL_OCTETS: + su->cc_total_octets_present = true; + su->cc_total_octets = hdr->avp_value->u64; + break; + case OGS_DIAM_GY_AVP_CODE_CC_INPUT_OCTETS: + su->cc_input_octets_present = true; + su->cc_input_octets = hdr->avp_value->u64; + break; + case OGS_DIAM_GY_AVP_CODE_CC_OUTPUT_OCTETS: + su->cc_output_octets_present = true; + su->cc_output_octets = hdr->avp_value->u64; + break; + default: + ogs_error("Not implemented(%d)", hdr->avp_code); + break; + } + fd_msg_browse(avpch2, MSG_BRW_NEXT, &avpch2, NULL); + } + + if (perror) + *perror = error; + + return OGS_OK; +} diff --git a/src/smf/meson.build b/src/smf/meson.build index 07fd3f849..1bd0b7d08 100644 --- a/src/smf/meson.build +++ b/src/smf/meson.build @@ -46,6 +46,7 @@ libsmf_sources = files(''' s5c-handler.h fd-path.h gx-handler.h + gy-handler.h pfcp-path.h n4-build.h n4-handler.h @@ -82,8 +83,10 @@ libsmf_sources = files(''' s5c-handler.c fd-path.c gx-path.c + gy-path.c s6b-path.c gx-handler.c + gy-handler.c pfcp-path.c n4-build.c n4-handler.c @@ -113,6 +116,7 @@ libsmf = static_library('smf', libngap_dep, libnas_5gs_dep, libdiameter_gx_dep, + libdiameter_gy_dep, libdiameter_s6b_dep, libgtp_dep, libpfcp_dep], @@ -125,6 +129,7 @@ libsmf_dep = declare_dependency( libngap_dep, libnas_5gs_dep, libdiameter_gx_dep, + libdiameter_gy_dep, libdiameter_s6b_dep, libgtp_dep, libpfcp_dep]) diff --git a/src/smf/n4-build.c b/src/smf/n4-build.c index a9d721341..9958adf82 100644 --- a/src/smf/n4-build.c +++ b/src/smf/n4-build.c @@ -114,6 +114,8 @@ ogs_pkbuf_t *smf_n4_build_session_modification_request( uint8_t type, smf_sess_t *sess, uint64_t modify_flags) { ogs_pfcp_pdr_t *pdr = NULL; + ogs_pfcp_urr_t *urr = NULL; + int i; ogs_pfcp_message_t pfcp_message; ogs_pfcp_session_modification_request_t *req = NULL; @@ -201,7 +203,7 @@ ogs_pkbuf_t *smf_n4_build_session_modification_request( &req->update_far[num_of_update_far], num_of_update_far, far); num_of_update_far++; - } else { + } else if (modify_flags == 0) { ogs_fatal("Invalid modify_flags = %lld", (long long)modify_flags); ogs_assert_if_reached(); @@ -210,6 +212,13 @@ ogs_pkbuf_t *smf_n4_build_session_modification_request( } + /* Update URR */ + i = 0; + ogs_list_for_each(&sess->pfcp.urr_list, urr) { + ogs_pfcp_build_update_urr(&req->update_urr[i], i, urr, modify_flags); + i++; + } + pfcp_message.h.type = type; pkbuf = ogs_pfcp_build_msg(&pfcp_message); diff --git a/src/smf/n4-handler.c b/src/smf/n4-handler.c index 4d4204c94..54a58fc08 100644 --- a/src/smf/n4-handler.c +++ b/src/smf/n4-handler.c @@ -26,6 +26,7 @@ #include "binding.h" #include "sbi-path.h" #include "ngap-path.h" +#include "fd-path.h" uint8_t gtp_cause_from_pfcp(uint8_t pfcp_cause, uint8_t gtp_version) { @@ -927,7 +928,6 @@ void smf_epc_n4_handle_session_modification_response( if (flags & OGS_PFCP_MODIFY_SESSION) { /* If smf_epc_pfcp_send_session_modification_request() is called */ - } else { /* If smf_epc_pfcp_send_bearer_modification_request() is called */ bearer = xact->data; @@ -936,9 +936,14 @@ void smf_epc_n4_handle_session_modification_response( flags = xact->modify_flags; ogs_assert(flags); - gtp_xact = xact->assoc_xact; - gtp_pti = xact->gtp_pti; - gtp_cause = xact->gtp_cause; + /* OGS_PFCP_MODIFY_URR: Modification Response was originally triggered by + PFCP Session Report Request, xact->assoc_xact is not a gtp_xact. No + need to do anything. */ + if (!(flags & OGS_PFCP_MODIFY_URR)) { + gtp_xact = xact->assoc_xact; + gtp_pti = xact->gtp_pti; + gtp_cause = xact->gtp_cause; + } ogs_pfcp_xact_commit(xact); @@ -1126,6 +1131,8 @@ void smf_epc_n4_handle_session_deletion_response( uint8_t cause_value = 0; uint8_t resp_type = 0; ogs_gtp_xact_t *gtp_xact = NULL; + smf_bearer_t *bearer = NULL; + unsigned int i; ogs_assert(xact); ogs_assert(rsp); @@ -1176,6 +1183,38 @@ void smf_epc_n4_handle_session_deletion_response( ogs_assert(sess); + bearer = smf_default_bearer_in_sess(sess); + for (i = 0; i < OGS_ARRAY_SIZE(rsp->usage_report); i++) { + ogs_pfcp_tlv_usage_report_session_deletion_response_t *use_rep = &rsp->usage_report[i]; + uint32_t urr_id; + ogs_pfcp_volume_measurement_t volume; + if (use_rep->presence == 0) + break; + if (use_rep->urr_id.presence == 0) + continue; + urr_id = use_rep->urr_id.u32; + if (!bearer || !bearer->urr || bearer->urr->id != urr_id) + continue; + ogs_pfcp_parse_volume_measurement(&volume, &use_rep->volume_measurement); + if (volume.ulvol) + sess->gy.ul_octets += volume.uplink_volume; + if (volume.dlvol) + sess->gy.dl_octets += volume.downlink_volume; + sess->gy.duration += use_rep->duration_measurement.u32; + } + + switch(smf_use_gy_iface()) { + case 1: + /* Gy is available, terminate the Gy session before terminating it towards the UE */ + smf_gy_send_ccr(sess, gtp_xact, + OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST); + return; + case -1: + ogs_error("No Gy Diameter Peer"); + break; /* continue below */ + /* default: continue below */ + } + if (gtp_xact) { /* * 1. MME sends Delete Session Request to SGW/SMF. @@ -1213,11 +1252,13 @@ void smf_n4_handle_session_report_request( ogs_pfcp_session_report_request_t *pfcp_req) { smf_bearer_t *qos_flow = NULL; + smf_bearer_t *bearer = NULL; ogs_pfcp_pdr_t *pdr = NULL; ogs_pfcp_report_type_t report_type; uint8_t cause_value = 0; uint16_t pdr_id = 0; + unsigned int i; ogs_assert(pfcp_xact); ogs_assert(pfcp_req); @@ -1343,8 +1384,42 @@ void smf_n4_handle_session_report_request( } } + if (report_type.usage_report) { + bearer = smf_default_bearer_in_sess(sess); + for (i = 0; i < OGS_ARRAY_SIZE(pfcp_req->usage_report); i++) { + ogs_pfcp_tlv_usage_report_session_report_request_t *use_rep = &pfcp_req->usage_report[i]; + uint32_t urr_id; + ogs_pfcp_volume_measurement_t volume; + if (use_rep->presence == 0) + break; + if (use_rep->urr_id.presence == 0) + continue; + urr_id = use_rep->urr_id.u32; + if (!bearer || !bearer->urr || bearer->urr->id != urr_id) + continue; + ogs_pfcp_parse_volume_measurement(&volume, &use_rep->volume_measurement); + if (volume.ulvol) + sess->gy.ul_octets += volume.uplink_volume; + if (volume.dlvol) + sess->gy.dl_octets += volume.downlink_volume; + sess->gy.duration += use_rep->duration_measurement.u32; + } + switch(smf_use_gy_iface()) { + case 1: + smf_gy_send_ccr(sess, pfcp_xact, OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST); + break; + case -1: + ogs_error("No Gy Diameter Peer"); + /* TODO: terminate connection */ + break; + /* default: continue below */ + } + } + /* TS 29.244 sec 8.2.21: At least one bit shall be set to "1". Several bits may be set to "1". */ - if (report_type.downlink_data_report || report_type.error_indication_report) { + if (report_type.downlink_data_report || + report_type.error_indication_report || + report_type.usage_report) { ogs_assert(OGS_OK == smf_pfcp_send_session_report_response( pfcp_xact, sess, OGS_PFCP_CAUSE_REQUEST_ACCEPTED)); diff --git a/src/smf/smf-sm.c b/src/smf/smf-sm.c index 68ef56279..5e54c0f2c 100644 --- a/src/smf/smf-sm.c +++ b/src/smf/smf-sm.c @@ -25,6 +25,7 @@ #include "s5c-handler.h" #include "gn-handler.h" #include "gx-handler.h" +#include "gy-handler.h" #include "nnrf-handler.h" #include "namf-handler.h" #include "npcf-handler.h" @@ -60,6 +61,7 @@ void smf_state_operational(ogs_fsm_t *s, smf_event_t *e) ogs_gtp1_message_t gtp1_message; ogs_diam_gx_message_t *gx_message = NULL; + ogs_diam_gy_message_t *gy_message = NULL; ogs_pfcp_node_t *pfcp_node = NULL; ogs_pfcp_xact_t *pfcp_xact = NULL; @@ -269,6 +271,50 @@ void smf_state_operational(ogs_fsm_t *s, smf_event_t *e) ogs_session_data_free(&gx_message->session_data); ogs_free(gx_message); break; + + case SMF_EVT_GY_MESSAGE: + ogs_assert(e); + gy_message = e->gy_message; + ogs_assert(gy_message); + + sess = e->sess; + ogs_assert(sess); + + switch(gy_message->cmd_code) { + case OGS_DIAM_GY_CMD_CODE_CREDIT_CONTROL: + switch(gy_message->cc_request_type) { + case OGS_DIAM_GY_CC_REQUEST_TYPE_INITIAL_REQUEST: + ogs_assert(e->gtp_xact); + smf_gy_handle_cca_initial_request( + sess, gy_message, e->gtp_xact); + break; + case OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST: + ogs_assert(e->pfcp_xact); + smf_gy_handle_cca_update_request( + sess, gy_message, e->pfcp_xact); + break; + case OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST: + ogs_assert(e->gtp_xact); + smf_gy_handle_cca_termination_request( + sess, gy_message, e->gtp_xact); + break; + default: + ogs_error("Not implemented(%d)", gy_message->cc_request_type); + break; + } + + break; + case OGS_DIAM_GY_CMD_RE_AUTH: + smf_gy_handle_re_auth_request(sess, gy_message); + break; + default: + ogs_error("Invalid type(%d)", gy_message->cmd_code); + break; + } + + ogs_free(gy_message); + break; + case SMF_EVT_N4_MESSAGE: ogs_assert(e); recvbuf = e->pkbuf;