/* SPDX-License-Identifier: GPL-2.0 */ /* * qosmngr.c - main qosmngr's code * * Copyright (C) 2020 iopsys Software Solutions AB. All rights reserved. * * Author: Oskar Viljasaar <oskar.viljasaar@iopsys.eu> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ #include <libubox/blobmsg_json.h> #include <libubus.h> #include <uci.h> /* Needed to get the IFNAMSIZ define */ #include <net/if.h> #include "qosmngr.h" /* Used as an internal value to query all the queues */ #define QOS_QUEUE_ANY -1 /* Used for fetching keys from ubus json reqest */ #define SEPERATOR 44 #define QUOTE 34 /* Used to validate requested parameters */ #define PARAM1 "ifname" #define PARAM2 "qid" /** Container for single interface data (with multiple queues) */ typedef struct qos_interface_data { char if_name[IFNAMSIZ]; /**< Cached interface name */ size_t q_count; /**< Cached number of queues */ struct qos_stats *q_stat; /**< Cached queue statistics */ } qos_interface_data; static int init_flag = 1; /** Number of interfaces currently cached by qosmngr */ static size_t interface_count = 0; /** Array of interfaces cached by qosmngr */ static qos_interface_data *interfaces = NULL; /** * get_no_queues function for getting number of queues on device by calling uci qos config * @param ifname input parameter pointer to char for which number of queues needed * retrun integer value 0 on success and -1 on failure */ static int get_no_queues(const char *ifname) { int queues = 0; struct uci_package *uci_pkg = NULL; struct uci_element *uci_elmnt = NULL; struct uci_context *uci_ctx = uci_alloc_context(); uci_load(uci_ctx, "qos", &uci_pkg); if (!uci_pkg) { syslog(LOG_ERR, "Failed to load configuration\n"); queues = -1; goto done; } uci_foreach_element(&uci_pkg->sections, uci_elmnt) { struct uci_section *uci_sec = uci_to_section(uci_elmnt); if (uci_sec && !strcmp(uci_sec->type, "queue")) { struct uci_element *e = NULL; uci_foreach_element(&uci_sec->options, e) { struct uci_option *uci_opn = uci_to_option(e); if (uci_opn && !strcmp(uci_opn->v.string, ifname)) queues++; } } } uci_unload(uci_ctx, uci_pkg); done: uci_free_context(uci_ctx); return queues; } /** * get_interface_index function for getting interface index i.e., eth0 having index 0 * @param ifname input parameter pointer to char for which index needs to be fetched * retrun integer value 0 on success and -1 on failure */ static int get_interface_index(const char *ifname) { size_t i; size_t len = strlen(ifname); for (i = 0; i < interface_count; ++i) { qos_interface_data *cur = &interfaces[i]; if (strncmp(cur->if_name, ifname, len) == 0) { size_t cur_len = strlen(cur->if_name); if (cur_len != len) { if (sscanf(ifname + len, ".%*d") != 1) { continue; } } return (int)i; } } return -1; } /** * init_qstat function for initializing global q_stat structure for sustainable stats * @param none * retrun integer value 0 on success and -1 on failure */ static int init_qstat() { int ret = 0; int index = 0; syslog(LOG_ERR, "Initializing global stat structure.\n"); struct uci_package *uci_pkg = NULL; struct uci_element *uci_elmnt = NULL; struct uci_context *uci_ctx = uci_alloc_context(); interface_count = 0; uci_load(uci_ctx, "ports", &uci_pkg); if (!uci_pkg) { syslog(LOG_ERR, "Failed to load configuration\n"); ret = -1; goto done; } interface_count = 0; uci_foreach_element(&uci_pkg->sections, uci_elmnt) { struct uci_section *uci_sec = uci_to_section(uci_elmnt); if (uci_sec) interface_count++; } interfaces = (qos_interface_data *)calloc(interface_count, sizeof(qos_interface_data)); if (interfaces == NULL) { syslog(LOG_ERR, "Initialization failed during memory allocation.\n"); ret = -1; goto done_unload; } uci_foreach_element(&uci_pkg->sections, uci_elmnt) { struct uci_section *uci_sec = uci_to_section(uci_elmnt); if (uci_sec) { qos_interface_data *data = &interfaces[index]; struct uci_option *uci_opn = uci_lookup_option(uci_ctx, uci_sec, "ifname"); int queues; queues = get_no_queues(uci_opn->v.string); strcpy(data->if_name, uci_opn->v.string); data->q_count = queues; data->q_stat = (struct qos_stats *)calloc(queues, sizeof(struct qos_stats)); if (data->q_stat == NULL) { syslog(LOG_ERR, "Initialization failed during memory allocation.\n"); ret = -1; goto done_unload; } index++; } } done_unload: uci_unload(uci_ctx, uci_pkg); done: uci_free_context(uci_ctx); return ret; } /** * prapare_stats_blob function for getting stats by calling libqos function prepare_stats_blob * @param b output parameter pointer to blob_buf * @param stats output parameter pointer to qos_stats actually queue stats container * @param dd output parameter pointer to void used for blob buffer array elements * @param ifname input parameter pointer to char for which to get stats * @param qid input parameter integer queue identifier for which to get stats * retrun integer value 0 on success and -1 on failure */ static int prepare_stats_blob(struct blob_buf *b, struct qos_stats *stats, void *dd, char *ifname, int qid) { int index; int ret = 0; int is_read_and_reset; qos_interface_data *iface; struct qos_stats *q_stat; // Initialize global q_stat global struct if (init_flag) { ret = init_qstat(); if (ret) { syslog(LOG_ERR, "Failed to initialize gobal q_stat.\n"); } syslog(LOG_ERR, "Initialized gobal q_stat successfully.\n"); init_flag = 0; } ret = qos_get_stats(ifname, qid, stats, &is_read_and_reset); if (ret != 0) { syslog(LOG_ERR, "blob_get_status: ret %d\n", ret); return ret; } index = get_interface_index(ifname); if (index < 0 || index >= interface_count) { syslog(LOG_ERR, "Invalid interface index %d (out of %zu)", index, interface_count); return -1; } iface = &interfaces[index]; if (qid < 0 || qid >= iface->q_count) { syslog(LOG_ERR, "Invalid queue index %d (out of %zu)", qid, iface->q_count); return -1; } q_stat = &iface->q_stat[qid]; /*BCM968 CHIP, stats read from driver is accunulated stats, while in other its read and reset */ if (!is_read_and_reset) { q_stat->tx_packets = stats->tx_packets; q_stat->tx_bytes = stats->tx_bytes; q_stat->tx_dropped_packets = stats->tx_dropped_packets; q_stat->tx_dropped_bytes = stats->tx_dropped_bytes; } else { q_stat->tx_packets += stats->tx_packets; q_stat->tx_bytes += stats->tx_bytes; q_stat->tx_dropped_packets += stats->tx_dropped_packets; q_stat->tx_dropped_bytes += stats->tx_dropped_bytes; } dd = blobmsg_open_table(b, ""); blobmsg_add_string(b, "iface", ifname); blobmsg_add_u32(b, "qid", qid); blobmsg_add_u32(b, "tx_packets", q_stat->tx_packets); blobmsg_add_u32(b, "tx_bytes", q_stat->tx_bytes); blobmsg_add_u32(b, "tx_dropped_packets", q_stat->tx_dropped_packets); blobmsg_add_u32(b, "tx_dropped_bytes", q_stat->tx_dropped_bytes); blobmsg_close_table(b, dd); return ret; } /** * get_stats_by_ifname function for getting specific interface stats * @param b output parameter pointer to blob_buf * @param stats output parameter pointer to qos_stats actually queue stats container * @param dd output parameter pointer to void used for blob buffer array elements * @param ifname input parameter pointer to char for which to get stats * @param qid input parameter integer queue identifier for which to get stats * retrun integer value 0 on success and -1 on failure */ static int get_stats_by_ifname(struct blob_buf *b, struct qos_stats *stats, void *dd, char *ifname, int qid) { int ret = 0; if (qid >= 0) { ret = prepare_stats_blob(b, stats, dd, ifname, qid); } else { int i; int queues = get_no_queues(ifname); for (i = 0; i < queues; i++) { ret = prepare_stats_blob(b, stats, dd, ifname, i); if (ret != 0) return ret; } } return ret; } /** * get_stats_for_all_intf function for getting all interface stats * @param b output parameter pointer to blob_buf * @param stats output parameter pointer to qos_stats actually queue stats container * @param dd output parameter pointer to void used for blob buffer array elements * retrun integer value 0 on success and -1 on failure */ static int get_stats_for_all_intf(struct blob_buf *b, struct qos_stats *stats, void *dd) { int ret = 0; char ifname[IFNAMSIZ] = {0}; struct uci_package *uci_pkg = NULL; struct uci_element *uci_elmnt = NULL; struct uci_context *uci_ctx = uci_alloc_context(); uci_load(uci_ctx, "ports", &uci_pkg); if (!uci_pkg) { syslog(LOG_ERR, "Failed to load configuration\n"); ret = -1; return ret; } uci_foreach_element(&uci_pkg->sections, uci_elmnt) { struct uci_section *uci_sec = uci_to_section(uci_elmnt); if (uci_sec) { struct uci_option *uci_opn = uci_lookup_option(uci_ctx, uci_sec, "ifname"); strncpy(ifname, uci_opn->v.string, sizeof(ifname) - 1); ret = get_stats_by_ifname(b, stats, dd, ifname, QOS_QUEUE_ANY); if (ret != 0) { syslog(LOG_ERR, "get_stats_by_ifname : ret %d\n", ret); return ret; } } } return ret; } /** * validate_keys function to validate requested json keys * @param rea_json parameter pointer to char string containing json request * retrun integer value 0 on success and -1 on failure */ static int validate_keys(char *req_json) { int i; int ret = 0; int len = strlen(req_json); for (i = 0; i < len; i++) { if (req_json[i] == QUOTE) { char key[IFNAMSIZ] = {0}; int j = 0; i++; while ((i < len) && (req_json[i] != QUOTE)) { key[j] = req_json[i]; j++; i++; } i++; while ((i < len) && (req_json[i] != SEPERATOR)) i++; if (!(!strncmp(key, PARAM1, strlen(PARAM1)) || !strncmp(key, PARAM2, strlen(PARAM2)))) { syslog(LOG_ERR, "ERROR :: unknown parameter : %s\n", key); return -1; } } } return ret; } /** * validate_request function to validate requested blob message * @param msg parameter pointer to blob_attr containing blob request * retrun integer value 0 on success and -1 on failure */ static int validate_request(struct blob_attr *msg) { int ret = 0; char *json_blob = NULL; if (msg) { json_blob = blobmsg_format_json(msg, true); ret = validate_keys(json_blob); } return ret; } /** * qosmngr_get_stats function callback on ubus method queue_stats * @param ctx input parameter pointer to ubus context * @param obj input parameter pointer to ubus object in out case qos * @param req input parameter pointer to ubus requested data * @param method input parameter pointer to char method i.e., queue_stats * @param msg input parameter pointer containing qid and ifname * retrun integer value 0 on success and -1 on failure */ int qosmngr_get_stats(struct ubus_context *ctx, struct ubus_object *obj, struct ubus_request_data *req, const char *method, struct blob_attr *msg) { int ret = 0; int qid = QOS_QUEUE_ANY; char ifname[IFNAMSIZ] = {0}; struct blob_attr *tb[NUM_QOS_POLICY]; struct blob_buf b = {0}; struct qos_stats stats = {0}; /* Validate requested parameters, i.e., ifname and qid */ ret = validate_request(msg); if (ret) { syslog(LOG_ERR, "validate_request failed : ret %d\n", ret); return UBUS_STATUS_INVALID_ARGUMENT; } /* These are for the blobbuf array elements */ void *d = NULL, *dd = NULL; blobmsg_parse(get_status_policy, QOS_POLICY_MAX, tb, blob_data(msg), blob_len(msg)); if (tb[QOS_POLICY_IFNAME]) strncpy(ifname, blobmsg_data(tb[QOS_POLICY_IFNAME]), sizeof(ifname)-1); /* Parse optional arguments */ if (tb[QOS_POLICY_QID]) qid = blobmsg_get_u32(tb[QOS_POLICY_QID]); /* Can't have a queue id specified without an interface */ if (tb[QOS_POLICY_QID] && !tb[QOS_POLICY_IFNAME]) return UBUS_STATUS_INVALID_ARGUMENT; blob_buf_init(&b, 0); d = blobmsg_open_array(&b, "queues"); if (tb[QOS_POLICY_IFNAME]) { ret = get_stats_by_ifname(&b, &stats, &dd, ifname, qid); if (ret != 0) { syslog(LOG_ERR, "get_stats_by_ifname : ret %d\n", ret); goto fail_get_status; } } else { ret = get_stats_for_all_intf(&b, &stats, &dd); if (ret != 0) { syslog(LOG_ERR, "get_stats_for_all_intf : ret %d\n", ret); goto fail_get_status; } } blobmsg_close_array(&b, d); ubus_send_reply(ctx, req, b.head); fail_get_status: blob_buf_free(&b); return ret; }