[UPF] Add initial support for URR Usage Report (#1476)

This commit is contained in:
Pau Espin 2022-04-08 16:10:42 +02:00 committed by GitHub
parent 52672cff65
commit fb8ebcdbea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 289 additions and 22 deletions

View File

@ -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);
}

View File

@ -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
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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;