diff --git a/lib/pfcp/build.c b/lib/pfcp/build.c index 49053e1af..d6470aa9a 100644 --- a/lib/pfcp/build.c +++ b/lib/pfcp/build.c @@ -858,3 +858,72 @@ ogs_pkbuf_t *ogs_pfcp_build_session_report_response( pfcp_message.h.type = type; return ogs_pfcp_build_msg(&pfcp_message); } + +ogs_pkbuf_t *ogs_pfcp_build_session_deletion_response( uint8_t type, uint8_t cause, + ogs_pfcp_user_plane_report_t *report) +{ + ogs_pfcp_message_t pfcp_message; + ogs_pfcp_session_deletion_response_t *rsp = NULL; + unsigned int i; + + ogs_debug("PFCP session deletion response"); + + rsp = &pfcp_message.pfcp_session_deletion_response; + memset(&pfcp_message, 0, sizeof(ogs_pfcp_message_t)); + + rsp->cause.presence = 1; + rsp->cause.u8 = cause; + + if (report->type.usage_report) { + ogs_assert(report->num_of_usage_report > 0); + for (i = 0; i < report->num_of_usage_report; i++) { + rsp->usage_report[i].presence = 1; + rsp->usage_report[i].urr_id.presence = 1; + rsp->usage_report[i].urr_id.u32 = report->usage_report[i].id; + rsp->usage_report[i].ur_seqn.presence = 1; + rsp->usage_report[i].ur_seqn.u32 = report->usage_report[i].seqn; + rsp->usage_report[i].usage_report_trigger.presence = 1; + rsp->usage_report[i].usage_report_trigger.u24 = + (report->usage_report[i].rep_trigger.reptri_5 << 16) + | (report->usage_report[i].rep_trigger.reptri_6 << 8) + | (report->usage_report[i].rep_trigger.reptri_7); + + if (report->usage_report[i].start_time) { + rsp->usage_report[i].start_time.presence = 1; + rsp->usage_report[i].start_time.u32 = report->usage_report[i].start_time; + } + + if (report->usage_report[i].end_time) { + rsp->usage_report[i].end_time.presence = 1; + rsp->usage_report[i].end_time.u32 = report->usage_report[i].end_time; + } + + if (report->usage_report[i].vol_measurement.flags) { + rsp->usage_report[i].volume_measurement.presence = 1; + ogs_pfcp_build_volume_measurement( + &rsp->usage_report[i].volume_measurement, + &report->usage_report[i].vol_measurement, + &usage_report_buf.vol_meas, + sizeof(usage_report_buf.vol_meas)); + } + + rsp->usage_report[i].duration_measurement.presence = 1; + rsp->usage_report[i].duration_measurement.u32 = + report->usage_report[i].dur_measurement; + + if (report->usage_report[i].time_of_first_packet) { + rsp->usage_report[i].time_of_first_packet.presence = 1; + rsp->usage_report[i].time_of_first_packet.u32 = + report->usage_report[i].time_of_first_packet; + } + + if (report->usage_report[i].time_of_last_packet) { + rsp->usage_report[i].time_of_last_packet.presence = 1; + rsp->usage_report[i].time_of_last_packet.u32 = + report->usage_report[i].time_of_last_packet; + } + } + } + pfcp_message.h.type = type; + return ogs_pfcp_build_msg(&pfcp_message); +} diff --git a/lib/pfcp/build.h b/lib/pfcp/build.h index e8e5a6d72..4eda0f8ee 100644 --- a/lib/pfcp/build.h +++ b/lib/pfcp/build.h @@ -68,6 +68,9 @@ ogs_pkbuf_t *ogs_pfcp_build_session_report_request( ogs_pkbuf_t *ogs_pfcp_build_session_report_response( uint8_t type, uint8_t cause); +ogs_pkbuf_t *ogs_pfcp_build_session_deletion_response( uint8_t type, uint8_t cause, + ogs_pfcp_user_plane_report_t *report); + #ifdef __cplusplus } diff --git a/src/upf/context.c b/src/upf/context.c index e1b688dac..e74590429 100644 --- a/src/upf/context.c +++ b/src/upf/context.c @@ -18,6 +18,7 @@ */ #include "context.h" +#include "pfcp-path.h" static upf_context_t self; @@ -27,6 +28,8 @@ static OGS_POOL(upf_sess_pool, upf_sess_t); static int context_initialized = 0; +static void upf_sess_urr_acc_remove_all(upf_sess_t *sess); + void upf_context_init(void) { ogs_assert(context_initialized == 0); @@ -39,7 +42,8 @@ void upf_context_init(void) /* Setup UP Function Features */ ogs_pfcp_self()->up_function_features.ftup = 1; ogs_pfcp_self()->up_function_features.empu = 1; - ogs_pfcp_self()->up_function_features_len = 2; + ogs_pfcp_self()->up_function_features.mnop = 1; + ogs_pfcp_self()->up_function_features_len = 3; ogs_list_init(&self.sess_list); ogs_pool_init(&upf_sess_pool, ogs_app()->pool.sess); @@ -167,6 +171,8 @@ int upf_sess_remove(upf_sess_t *sess) { ogs_assert(sess); + upf_sess_urr_acc_remove_all(sess); + ogs_list_remove(&self.sess_list, sess); ogs_pfcp_sess_clear(&sess->pfcp); @@ -367,3 +373,128 @@ uint8_t upf_sess_set_ue_ip(upf_sess_t *sess, return cause_value; } + +void upf_sess_urr_acc_add(upf_sess_t *sess, ogs_pfcp_urr_t *urr, size_t size, bool is_uplink) +{ + upf_sess_urr_acc_t *urr_acc = &sess->urr_acc[urr->id]; + /* Increment total & ul octets + pkts */ + urr_acc->total_octets += size; + urr_acc->total_pkts++; + if (is_uplink) { + urr_acc->dl_octets += size; + urr_acc->dl_pkts++; + } else { + urr_acc->ul_octets += size; + urr_acc->ul_pkts++; + } + + urr_acc->time_of_last_packet = ogs_time_now(); + if (urr_acc->time_of_first_packet == 0) + urr_acc->time_of_first_packet = urr_acc->time_of_last_packet; + + /* TODO: generate report if volume threshold/quota is reached, eg sess->urr_acc[urr->id].total_octets - sess->urr_acc[urr->id].last_report.total_octets > threshold */ +} + +/* report struct must be memzeroed before first use of this function. + * report->num_of_usage_report must be set by the caller */ +void upf_sess_urr_acc_fill_usage_report(upf_sess_t *sess, const ogs_pfcp_urr_t *urr, + ogs_pfcp_user_plane_report_t *report, unsigned int idx) +{ + upf_sess_urr_acc_t *urr_acc = &sess->urr_acc[urr->id]; + ogs_time_t last_report_timestamp; + ogs_time_t now; + + now = ogs_time_now(); /* we need UTC for start_time and end_time */ + + if (urr_acc->last_report.timestamp) + last_report_timestamp = urr_acc->last_report.timestamp; + else + last_report_timestamp = ogs_time_from_ntp32(urr_acc->time_threshold_start); + + report->type.usage_report = 1; + report->usage_report[idx].id = urr->id; + report->usage_report[idx].seqn = urr_acc->report_seqn++; + report->usage_report[idx].start_time = urr_acc->time_threshold_start; + report->usage_report[idx].end_time = ogs_time_to_ntp32(now); + report->usage_report[idx].vol_measurement = (ogs_pfcp_volume_measurement_t){ + .dlnop = 1, + .ulnop = 1, + .tonop = 1, + .dlvol = 1, + .ulvol = 1, + .tovol = 1, + .total_volume = urr_acc->total_octets - urr_acc->last_report.total_octets, + .uplink_volume = urr_acc->ul_octets - urr_acc->last_report.ul_octets, + .downlink_volume = urr_acc->dl_octets - urr_acc->last_report.dl_octets, + .total_n_packets = urr_acc->total_pkts - urr_acc->last_report.total_pkts, + .uplink_n_packets = urr_acc->ul_pkts - urr_acc->last_report.ul_pkts, + .downlink_n_packets = urr_acc->dl_pkts - urr_acc->last_report.dl_pkts, + }; + if (now >= last_report_timestamp) + report->usage_report[idx].dur_measurement = ((now - last_report_timestamp) + (OGS_USEC_PER_SEC/2)) / OGS_USEC_PER_SEC; /* FIXME: should use MONOTONIC here */ + /* else memset sets it to 0 */ + report->usage_report[idx].time_of_first_packet = ogs_time_to_ntp32(urr_acc->time_of_first_packet); /* TODO: First since last report? */ + report->usage_report[idx].time_of_last_packet = ogs_time_to_ntp32(urr_acc->time_of_last_packet); + + if (urr->time_threshold > 0 && + report->usage_report[idx].dur_measurement >= urr->time_threshold) + report->usage_report[idx].rep_trigger.time_threshold = 1; +} + +void upf_sess_urr_acc_snapshot(upf_sess_t *sess, ogs_pfcp_urr_t *urr) +{ + upf_sess_urr_acc_t *urr_acc = &sess->urr_acc[urr->id]; + urr_acc->last_report.total_octets = urr_acc->total_octets; + urr_acc->last_report.dl_octets = urr_acc->dl_octets; + urr_acc->last_report.ul_octets = urr_acc->ul_octets; + urr_acc->last_report.total_pkts = urr_acc->total_pkts; + urr_acc->last_report.dl_pkts = urr_acc->dl_pkts; + urr_acc->last_report.ul_pkts = urr_acc->ul_pkts; + urr_acc->last_report.timestamp = ogs_time_now(); +} + +static void upf_sess_urr_acc_time_threshold_cb(void *data) +{ + ogs_pfcp_urr_t *urr = (ogs_pfcp_urr_t *)data; + ogs_pfcp_user_plane_report_t report; + ogs_pfcp_sess_t *pfcp_sess = urr->sess; + upf_sess_t *sess = UPF_SESS(pfcp_sess); + + ogs_warn("upf_time_threshold_cb() triggered! urr=%p", urr); + + if (urr->rep_triggers.time_threshold) { + memset(&report, 0, sizeof(report)); + upf_sess_urr_acc_fill_usage_report(sess, urr, &report, 0); + report.num_of_usage_report = 1; + upf_sess_urr_acc_snapshot(sess, urr); + + ogs_assert(OGS_OK == + upf_pfcp_send_session_report_request(sess, &report)); + } + /* Start new report period/iteration: */ + upf_sess_urr_acc_time_threshold_setup(sess, urr); +} + +void upf_sess_urr_acc_time_threshold_setup(upf_sess_t *sess, ogs_pfcp_urr_t *urr) +{ + upf_sess_urr_acc_t *urr_acc = &sess->urr_acc[urr->id]; + + ogs_debug("Installing URR time threshold timer"); + urr_acc->reporting_enabled = true; + if (!urr_acc->t_time_threshold) + urr_acc->t_time_threshold = ogs_timer_add(ogs_app()->timer_mgr, + upf_sess_urr_acc_time_threshold_cb, urr); + urr_acc->time_threshold_start = ogs_time_ntp32_now(); + ogs_timer_start(urr_acc->t_time_threshold, urr->time_threshold * OGS_USEC_PER_SEC); +} + +static void upf_sess_urr_acc_remove_all(upf_sess_t *sess) +{ + unsigned int i; + for (i = 0; i < OGS_ARRAY_SIZE(sess->urr_acc); i++) { + if (sess->urr_acc[i].t_time_threshold) { + ogs_timer_delete(sess->urr_acc[i].t_time_threshold); + sess->urr_acc[i].t_time_threshold = NULL; + } + } +} diff --git a/src/upf/context.h b/src/upf/context.h index b2604aa73..5f5da3741 100644 --- a/src/upf/context.h +++ b/src/upf/context.h @@ -52,6 +52,32 @@ typedef struct upf_context_s { ogs_list_t sess_list; } upf_context_t; +/* Accounting: */ +typedef struct upf_sess_urr_acc_s { + bool reporting_enabled; + ogs_timer_t *t_time_threshold; /* Time threshold expiration handler */ + uint32_t time_threshold_start; /* When t_time_threshold started */ + ogs_pfcp_urr_ur_seqn_t report_seqn; /* Next seqn to use when reporting */ + uint64_t total_octets; + uint64_t ul_octets; + uint64_t dl_octets; + uint64_t total_pkts; + uint64_t ul_pkts; + uint64_t dl_pkts; + ogs_time_t time_of_first_packet; + ogs_time_t time_of_last_packet; + /* Snapshot of measurement when last report was sent: */ + struct { + uint64_t total_octets; + uint64_t ul_octets; + uint64_t dl_octets; + uint64_t total_pkts; + uint64_t ul_pkts; + uint64_t dl_pkts; + ogs_time_t timestamp; + } last_report; +} upf_sess_urr_acc_t; + #define UPF_SESS(pfcp_sess) ogs_container_of(pfcp_sess, upf_sess_t, pfcp) typedef struct upf_sess_s { ogs_lnode_t lnode; @@ -68,6 +94,9 @@ typedef struct upf_sess_s { char *gx_sid; /* Gx Session ID */ ogs_pfcp_node_t *pfcp_node; + + /* Accounting: */ + upf_sess_urr_acc_t urr_acc[OGS_MAX_NUM_OF_URR]; /* FIXME: This probably needs to be mved to a hashtable or alike */ } upf_sess_t; void upf_context_init(void); @@ -90,6 +119,12 @@ upf_sess_t *upf_sess_find_by_ipv6(uint32_t *addr6); uint8_t upf_sess_set_ue_ip(upf_sess_t *sess, uint8_t session_type, ogs_pfcp_pdr_t *pdr); +void upf_sess_urr_acc_add(upf_sess_t *sess, ogs_pfcp_urr_t *urr, size_t size, bool is_uplink); +void upf_sess_urr_acc_fill_usage_report(upf_sess_t *sess, const ogs_pfcp_urr_t *urr, + ogs_pfcp_user_plane_report_t *report, unsigned int idx); +void upf_sess_urr_acc_snapshot(upf_sess_t *sess, ogs_pfcp_urr_t *urr); +void upf_sess_urr_acc_time_threshold_setup(upf_sess_t *sess, ogs_pfcp_urr_t *urr); + #ifdef __cplusplus } #endif diff --git a/src/upf/gtp-path.c b/src/upf/gtp-path.c index 108cb357b..7e161fad7 100644 --- a/src/upf/gtp-path.c +++ b/src/upf/gtp-path.c @@ -79,6 +79,7 @@ static void _gtpv1_tun_recv_common_cb( ogs_pfcp_pdr_t *fallback_pdr = NULL; ogs_pfcp_far_t *far = NULL; ogs_pfcp_user_plane_report_t report; + int i; recvbuf = ogs_tun_read(fd, packet_pool); if (!recvbuf) { @@ -174,6 +175,10 @@ static void _gtpv1_tun_recv_common_cb( goto cleanup; } + /* Increment total & dl octets + pkts */ + for (i = 0; i < pdr->num_of_urr; i++) + upf_sess_urr_acc_add(sess, pdr->urr[i], recvbuf->len, false); + ogs_assert(true == ogs_pfcp_up_handle_pdr(pdr, recvbuf, &report)); if (report.type.downlink_data_report) { @@ -348,6 +353,7 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data) ogs_pfcp_subnet_t *subnet = NULL; ogs_pfcp_dev_t *dev = NULL; + int i; ip_h = (struct ip *)pkbuf->data; ogs_assert(ip_h); @@ -513,6 +519,10 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data) dev = subnet->dev; ogs_assert(dev); + /* Increment total & ul octets + pkts */ + for (i = 0; i < pdr->num_of_urr; i++) + upf_sess_urr_acc_add(sess, pdr->urr[i], pkbuf->len, true); + if (dev->is_tap) { ogs_assert(eth_type); eth_type = htobe16(eth_type); diff --git a/src/upf/n4-build.c b/src/upf/n4-build.c index f0b9b51b5..7b847a5c9 100644 --- a/src/upf/n4-build.c +++ b/src/upf/n4-build.c @@ -111,18 +111,21 @@ ogs_pkbuf_t *upf_n4_build_session_modification_response(uint8_t type, ogs_pkbuf_t *upf_n4_build_session_deletion_response(uint8_t type, upf_sess_t *sess) { - ogs_pfcp_message_t pfcp_message; - ogs_pfcp_session_deletion_response_t *rsp = NULL; - + ogs_pfcp_urr_t *urr = NULL; + ogs_pfcp_user_plane_report_t report; + size_t num_of_reports = 0; ogs_debug("Session Deletion Response"); - rsp = &pfcp_message.pfcp_session_deletion_response; - memset(&pfcp_message, 0, sizeof(ogs_pfcp_message_t)); + memset(&report, 0, sizeof(report)); + ogs_list_for_each(&sess->pfcp.urr_list, urr) { + ogs_assert(num_of_reports < OGS_ARRAY_SIZE(report.usage_report)); + upf_sess_urr_acc_fill_usage_report(sess, urr, &report, num_of_reports); + report.usage_report[num_of_reports].rep_trigger.termination_report = 1; + num_of_reports++; + upf_sess_urr_acc_snapshot(sess, urr); + } + report.num_of_usage_report = num_of_reports; - /* Cause */ - rsp->cause.presence = 1; - rsp->cause.u8 = OGS_PFCP_CAUSE_REQUEST_ACCEPTED; - - pfcp_message.h.type = type; - return ogs_pfcp_build_msg(&pfcp_message); + return ogs_pfcp_build_session_deletion_response(type, OGS_PFCP_CAUSE_REQUEST_ACCEPTED, + &report); } diff --git a/src/upf/n4-handler.c b/src/upf/n4-handler.c index 0efe1840b..b14e2d1c5 100644 --- a/src/upf/n4-handler.c +++ b/src/upf/n4-handler.c @@ -22,6 +22,30 @@ #include "gtp-path.h" #include "n4-handler.h" +static void upf_n4_handle_create_urr(upf_sess_t *sess, ogs_pfcp_tlv_create_urr_t *create_urr_arr, + uint8_t *cause_value, uint8_t *offending_ie_value) +{ + int i; + ogs_pfcp_urr_t *urr; + + *cause_value = OGS_PFCP_CAUSE_REQUEST_ACCEPTED; + + for (i = 0; i < OGS_MAX_NUM_OF_URR; i++) { + urr = ogs_pfcp_handle_create_urr(&sess->pfcp, &create_urr_arr[i], + cause_value, offending_ie_value); + if (!urr) + return; + + /* TODO: here we should check for Reporting Triggers IMTH=1 instead? */ + if ((urr->meas_method & OGS_PFCP_MEASUREMENT_METHOD_DURATION) && urr->time_threshold > 0) { + /* if ISTM bit set in Measurement Information: */ + if (urr->meas_info.istm) { + upf_sess_urr_acc_time_threshold_setup(sess, urr); + } /* else: TODO: call upf_sess_urr_acc_time_threshold_setup() upon first pkt received */ + } + } +} + void upf_n4_handle_session_establishment_request( upf_sess_t *sess, ogs_pfcp_xact_t *xact, ogs_pfcp_session_establishment_request_t *req) @@ -67,11 +91,7 @@ void upf_n4_handle_session_establishment_request( if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) goto cleanup; - for (i = 0; i < OGS_MAX_NUM_OF_URR; i++) { - if (ogs_pfcp_handle_create_urr(&sess->pfcp, &req->create_urr[i], - &cause_value, &offending_ie_value) == NULL) - break; - } + upf_n4_handle_create_urr(sess, &req->create_urr[0], &cause_value, &offending_ie_value); if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) goto cleanup; @@ -272,11 +292,7 @@ void upf_n4_handle_session_modification_request( if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) goto cleanup; - for (i = 0; i < OGS_MAX_NUM_OF_URR; i++) { - if (ogs_pfcp_handle_create_urr(&sess->pfcp, &req->create_urr[i], - &cause_value, &offending_ie_value) == NULL) - break; - } + upf_n4_handle_create_urr(sess, &req->create_urr[0], &cause_value, &offending_ie_value); if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) goto cleanup;