/* 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 getting interface indexes for global q_stat array */ #define MIN_INDEX 48 #define MAX_INDEX 57 /* 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" static int init_flag = 1; static struct qos_stats **q_stat = {0}; /** * 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) { int i; for (i = 0; i < strlen(ifname); i++) { if ((ifname[i] >= MIN_INDEX) && (ifname[i] <= MAX_INDEX)) { return atoi(ifname + i); } } return -1; } /** * get_no_ports function for getting number of ports on device by calling uci port config * @param none * retrun integer value 0 on success and -1 on failure */ static int get_no_ports() { int ports = 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"); ports = -1; goto done; } uci_foreach_element(&uci_pkg->sections, uci_elmnt) { struct uci_section *uci_sec = uci_to_section(uci_elmnt); if (uci_sec) ports++; } done: uci_unload(uci_ctx, uci_pkg); uci_free_context(uci_ctx); return ports; } /** * 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"); int ports = get_no_ports(); q_stat = (struct qos_stats **)calloc(ports, sizeof(struct qos_stats *)); if (q_stat == NULL) { syslog(LOG_ERR, "Initialization failed during memory allocation.\n"); ret = -1; return ret; } 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; goto done; } 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"); index = get_interface_index(uci_opn->v.string); int queues = get_no_queues(uci_opn->v.string); q_stat[index] = (struct qos_stats *)calloc(queues, sizeof(struct qos_stats)); if (q_stat[index] == NULL) { syslog(LOG_ERR, "Initialization failed during memory allocation.\n"); ret = -1; uci_unload(uci_ctx, uci_pkg); goto done; } } } 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; // 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); /*BCM968 CHIP, stats read from driver is accunulated stats, while in other its read and reset */ if (!is_read_and_reset) { q_stat[index][qid].tx_packets = stats->tx_packets; q_stat[index][qid].tx_bytes = stats->tx_bytes; q_stat[index][qid].tx_dropped_packets = stats->tx_dropped_packets; q_stat[index][qid].tx_dropped_bytes = stats->tx_dropped_bytes; } else { q_stat[index][qid].tx_packets += stats->tx_packets; q_stat[index][qid].tx_bytes += stats->tx_bytes; q_stat[index][qid].tx_dropped_packets += stats->tx_dropped_packets; q_stat[index][qid].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[index][qid].tx_packets); blobmsg_add_u32(b, "tx_bytes", q_stat[index][qid].tx_bytes); blobmsg_add_u32(b, "tx_dropped_packets", q_stat[index][qid].tx_dropped_packets); blobmsg_add_u32(b, "tx_dropped_bytes", q_stat[index][qid].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 queues = 0; int i, ret = 0; if (qid >= 0) { ret = prepare_stats_blob(b, stats, dd, ifname, qid); } else { 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++) { char key[IFNAMSIZ] = {0}; if (req_json[i] == QUOTE) { int j = 0; i++; while (req_json[i] != QUOTE) { key[j] = req_json[i]; j++; i++; } i++; while ((req_json[i] != SEPERATOR) && (i < len)) 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; }