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;