From 106f2729c379c22e9bdd30f607b09abb971b3d6d Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Fri, 8 Apr 2022 17:08:58 +0200 Subject: [PATCH] Introduce Gy interface (#1471) TODO: * Use an event for the report, like SMF_EVT_N4_TIMER? * Properly set Service identifier in Gy CCR * SMF: Properly set pkt/octet volumes in Gy CCR ** Update when receiving PFCP Modify Response. * Figure out best way to require Gy through config file in open5gs-smfd. * Create a new sess-sm.c which handles smf_sess_t state through Gx+Gy+PFCP creation, modification and tear down. This way we can do stuff in parallel, for instance Gx+Gy. It will alsoavoid duplicating some code paths due to Gy being optional. --- lib/pfcp/build.c | 45 ++ lib/pfcp/build.h | 2 + lib/pfcp/xact.h | 5 + src/smf/context.h | 14 + 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 | 19 +- src/smf/gy-handler.c | 260 ++++++++++ src/smf/gy-handler.h | 46 ++ src/smf/gy-path.c | 1137 ++++++++++++++++++++++++++++++++++++++++++ src/smf/meson.build | 5 + src/smf/n4-build.c | 11 +- src/smf/n4-handler.c | 130 +++-- src/smf/smf-sm.c | 46 ++ 16 files changed, 1700 insertions(+), 37 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/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.h b/src/smf/context.h index 3ce15b93a..40e5bb40d 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" @@ -217,6 +218,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 +293,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 */ 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..cff35b728 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,21 @@ 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)); + /* if !Gy */ + if (!ogs_diam_app_connected(OGS_DIAM_GY_APPLICATION_ID)) { + ogs_error("No Gy Diameter Peer"); + //cause_value = OGS_GTP1_CAUSE_NO_RESOURCES_AVAILABLE; + /* FIXME: let's continue on now without Gy (charging features). Ideally + * it should be specified in the config the policy to use if Gy not + * present: either reject the context or continue nevertheless (default) */ + ogs_assert(OGS_OK == + smf_epc_pfcp_send_session_establishment_request(sess, gtp_xact)); + return; + } + + /* 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); } 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..19f99da07 --- /dev/null +++ b/src/smf/gy-path.c @@ -0,0 +1,1137 @@ +/* + * 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 */ + ret = fd_msg_avp_new(ogs_diam_gy_service_id, 0, &avpch1); + ogs_assert(ret == 0); + val.u32 = 123; // 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); + + /* 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..0e667910a 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,36 +1183,64 @@ void smf_epc_n4_handle_session_deletion_response( ogs_assert(sess); - if (gtp_xact) { - /* - * 1. MME sends Delete Session Request to SGW/SMF. - * 2. SMF sends Delete Session Response to SGW/MME. - */ - switch (gtp_version) { - case 1: - ogs_assert(OGS_OK == smf_gtp1_send_delete_pdp_context_response(sess, gtp_xact)); + 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; - 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. - */ + 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; } - SMF_SESS_CLEAR(sess); + /* if !Gy */ + if (!ogs_diam_app_connected(OGS_DIAM_GY_APPLICATION_ID)) { + if (gtp_xact) { + /* + * 1. MME sends Delete Session Request to SGW/SMF. + * 2. SMF sends Delete Session Response to SGW/MME. + */ + switch (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; + } + /* Gy is available, terminate the Gy session before terminating it towards the UE */ + /* TODO: here we iterate rsp->usage_report[i], if (report->type.usage_report) {... here we fill sess statistics...} */ + + smf_gy_send_ccr(sess, gtp_xact, + OGS_DIAM_GY_CC_REQUEST_TYPE_TERMINATION_REQUEST); } void smf_n4_handle_session_report_request( @@ -1213,11 +1248,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 +1380,37 @@ 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; + } + if (ogs_diam_app_connected(OGS_DIAM_GY_APPLICATION_ID)) { + smf_gy_send_ccr(sess, pfcp_xact, OGS_DIAM_GY_CC_REQUEST_TYPE_UPDATE_REQUEST); + } else { + ogs_error("No Gy Diameter Peer"); + } + } + /* 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;