Skip to content
Snippets Groups Projects
qosmngr.c 15.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* 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 <json-c/json.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;
    
    
    
    /** 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;
    
    static int init_interface(const char *ifname, size_t index)
    {
    	qos_interface_data *data = &interfaces[index];
    
    	size_t queues = (size_t)get_no_queues(ifname);
    
    	strncpy(data->if_name, ifname, IFNAMSIZ);
    	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");
    		free(interfaces);
    		return -1;
    	}
    	return 0;
    }
    
    
     *  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(void)
    
    	int ret = 0;
    
    	syslog(LOG_ERR, "Initializing global stat structure.\n");
    
    	long int size;
    	FILE *fp = NULL;
    	char *buffer = NULL;
    
    	fp = fopen("/etc/board.json","r");
    	if (fp == NULL)
    		return -1;
    
    	fseek(fp, 0L, SEEK_END);
    	size = ftell(fp);
    	rewind(fp);
    
    	unsigned int buffer_size = 0;
    	/* Allocate memory for entire content */
    	if (size < 0) {
    		syslog(LOG_ERR, "file read size < 0");
    		fclose(fp);
    		return -1;
    	} else {
    		buffer_size = (unsigned int)size;
    	}
    
    	buffer = calloc(1, buffer_size + 1);
    	if (buffer == NULL) {
    		syslog(LOG_ERR, "memory allocation to read buffer failed");
    		fclose(fp);
    		return -1;
    	}
    
    	/* Copy the file into the buffer */
    	if (fread(buffer, buffer_size, 1, fp) != 1) {
    		syslog(LOG_ERR, "read from board.json file failed");
    		free(buffer);
    		fclose(fp);
    		return -1;
    	}
    
    	struct json_object *board_json = json_tokener_parse(buffer);
    	if (board_json == NULL) {
    		syslog(LOG_ERR, "failed to read board.json as json");
    		free(buffer);
    		fclose(fp);
    		return -1;
    	}
    
    	struct json_object *network = NULL;
    	if (!json_object_object_get_ex(board_json, "network", &network)) {
    		syslog(LOG_ERR, "network object not found in board.json");
    
    		json_object_put(board_json);
    		goto free_and_return;
    
    	struct json_object *lan = NULL;
    	if (!json_object_object_get_ex(network, "lan", &lan)) {
    		syslog(LOG_ERR, "lan object not found in board.json");
    		json_object_put(board_json);
    		ret = -1;
    		goto free_and_return;
    	}
    
    	struct json_object *wan = NULL;
    
    	struct json_object *wan_device = NULL;
    	size_t wan_device_count = 0;
    
    	// its not necessary that board.json has wan section at all
    	if (json_object_object_get_ex(network, "wan", &wan)) {
    		if (!json_object_object_get_ex(wan, "device", &wan_device)) {
    			syslog(LOG_ERR, "wan device object not found in board.json");
    			ret = -1;
    			json_object_put(board_json);
    			goto free_and_return;
    		}
    		wan_device_count = 1;
    
    	struct json_object *lan_ports = NULL;
    	if (!json_object_object_get_ex(lan, "ports", &lan_ports)) {
    
    		syslog(LOG_INFO, "lan ports object not found in board.json");
    		if (!json_object_object_get_ex(lan, "device", &lan_ports)) {
    			syslog(LOG_INFO, "lan device object not found in board.json");
    			ret = -1;
    			json_object_put(board_json);
    			goto free_and_return;
    		}
    
    	// create json object for all ports by reading from lan ports and wan device
    
    	if (json_object_is_type(lan_ports, json_type_array)) {
    		interface_count = json_object_array_length(lan_ports) + wan_device_count;
    	} else {
    		// single lan port device
    		interface_count = 1 + wan_device_count;
    	}
    
            interfaces = (qos_interface_data *)calloc(interface_count,
    	                                          sizeof(qos_interface_data));
    	if (interfaces == NULL) {
    
    		syslog(LOG_ERR, "Initialization failed during memory allocation.");
    
    		json_object_put(board_json);
    		goto free_and_return;
    
    	if (json_object_is_type(lan_ports, json_type_array)) {
    		for (i = 0; i < json_object_array_length(lan_ports); i++) {
    			struct json_object *l_port_name = json_object_array_get_idx(lan_ports, i);
    			if (!l_port_name) {
    				syslog(LOG_ERR, "cannot read lan ports array");
    				ret = -1;
    				json_object_put(board_json);
    				free(interfaces);
    				goto free_and_return;
    			}
    
    			ret = init_interface(json_object_get_string(l_port_name), i);
    
    			if (ret < 0) {
    				syslog(LOG_ERR, "cannot int lan ports qos");
    				json_object_put(board_json);
    				free(interfaces);
    				goto free_and_return;
    			}
    
    		}
    	} else {
    		ret = init_interface(json_object_get_string(lan_ports), i);
    
    			syslog(LOG_ERR, "cannot int lan device qos");
    
    			json_object_put(board_json);
    			free(interfaces);
    			goto free_and_return;
    
    	if (wan_device)
    		ret = init_interface(json_object_get_string(wan_device), i);
    
    
    	json_object_put(board_json);
    
    free_and_return:
    	free(buffer);
    	fclose(fp);
    
    	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;
    
    	qos_interface_data *iface;
    	struct qos_stats   *q_stat;
    
    	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;
    
    		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", (uint32_t)qid);
    
            blobmsg_add_u64(b, "tx_packets", (uint64_t)q_stat->tx_packets);
            blobmsg_add_u64(b, "tx_bytes", (uint64_t)q_stat->tx_bytes);
            blobmsg_add_u64(b, "tx_dropped_packets", (uint64_t)q_stat->tx_dropped_packets);
            blobmsg_add_u64(b, "tx_dropped_bytes", (uint64_t)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;
    
    	size_t i = 0;
    	for (i = 0; i < interface_count; i++) {
    		qos_interface_data *cur = &interfaces[i];
    		ret = get_stats_by_ifname(b, stats, dd,
    				cur->if_name, QOS_QUEUE_ANY);
    
    		if (ret != 0) {
    			syslog(LOG_ERR, "get_stats_by_ifname failed: ret %d", ret);
    			continue;
    
    /**
     *  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)
    {
    
    	size_t len = strlen(req_json);
    
    
    	for (i = 0; i < len; i++) {
    		if (req_json[i] == QUOTE) {
    
    			char key[IFNAMSIZ] = {0};
    
    
    			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),
    
    							(unsigned int)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 = (int)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;
    
    
    	// Initialize global q_stat global struct
    	if (init_flag) {
    		ret = init_qstat();
    		if (ret) {
    			syslog(LOG_ERR, "Failed to initialize gobal q_stat");
    			return ret;
    		}
    		syslog(LOG_INFO, "Initialized gobal q_stat successfully");
    		init_flag = 0;
    	}
    
    
    	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;
    }