diff --git a/src/Makefile.am b/src/Makefile.am index fba84cf0..799be365 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,7 +34,8 @@ libgateway_a_SOURCES = commandline.c \ httpd_thread.c \ simple_http.c \ pstring.c \ - wd_util.c + wd_util.c \ + bw_shaping.c noinst_HEADERS = commandline.h \ common.h \ @@ -55,7 +56,8 @@ noinst_HEADERS = commandline.h \ httpd_thread.h \ simple_http.h \ pstring.h \ - wd_util.h + wd_util.h \ + bw_shaping.h wdctl_LDADD = libgateway.a diff --git a/src/bw_shaping.c b/src/bw_shaping.c new file mode 100644 index 00000000..ab908e8a --- /dev/null +++ b/src/bw_shaping.c @@ -0,0 +1,573 @@ +/* vim: set et ts=4 sts=4 sw=4 : */ +/********************************************************************\ + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * 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, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * +\********************************************************************/ + +/* $Id$ */ +/** @file bw_shaping.c + @brief Bandwidth shaping functions + @author Copyright (C) 2015 Neutron Soutmun +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bw_shaping.h" +#include "client_list.h" +#include "conf.h" +#include "util.h" +#include "debug.h" +#include "safe.h" + +#define BW_PROG_TC "tc" +#define BW_PROG_IFCONFIG "ifconfig" +#define BW_RATE_MAX UINT32_MAX + +static int bw_shaping_do_command(const char *prog, const char *format, ...); +static int bw_shaping_egress_init(void); +static int bw_shaping_ingress_init(void); +static int bw_shaping_client_add_class(t_client *client); +static int bw_shaping_client_add_filter(t_client *client); +static int bw_shaping_client_remove_class(t_client *client); +static int bw_shaping_client_remove_filter(t_client *client); +static uint16_t bw_shaping_burst_calc(uint32_t speed); +static uint32_t bw_shaping_get_rate_from_httpvar(request *r, + const char *varname, + uint32_t def_rate); + +/** +Used to supress the error output during destruction */ +static int bw_quiet = 0; + +/** +Used for htb burst calculation, program will try to get value from system. +Default value is 100 +*/ +static int bw_hz = 100; + +/** +Define default class id, default is 0xffff +It will be reduced by 1 (0xfffe) if BandwidthShapingGatewaySpeedLimit is set +*/ +static uint32_t bw_default_classid = 0xffff; + +/** +Define maximum class id, default is 0xfffe +It will be reduced by 1 (0xfffc) if BandwidthShapingGatewaySpeedLimit is set +*/ +static uint32_t bw_max_classid = 0xfffe; + +/** +Define parent class id, default is 0 +It will be 0xffff if BandwidthShapingGatewaySpeedLimit is set +*/ +static uint32_t bw_parent_classid = 0; + +/** @internal +*/ +static int +bw_shaping_do_command(const char *prog, const char *format, ...) +{ + va_list valist; + char *fmt_cmd; + char *cmd; + int rc; + + va_start(valist, format); + safe_vasprintf(&fmt_cmd, format, valist); + va_end(valist); + + safe_asprintf(&cmd, "%s %s", prog, fmt_cmd); + free(fmt_cmd); + + debug(LOG_DEBUG, "Executing command: %s", cmd); + + rc = execute(cmd, bw_quiet); + + if (rc != 0) { + if (bw_quiet == 0) + debug(LOG_ERR, "bandwidth shaping command failed(%d): %s", rc, cmd); + else if (bw_quiet == 1) + debug(LOG_DEBUG, "bandwidth shaping command failed(%d): %s", rc, cmd); + } + + free(cmd); + + return rc; +} + +/** @internal +*/ +static int +bw_shaping_egress_init(void) +{ + s_config *config = config_get_config(); + int rc; + + /** Set gateway interface txqueuelen */ + rc = bw_shaping_do_command(BW_PROG_IFCONFIG, "%s txqueuelen %" PRIu32, + config->gw_interface, config->bw_shaping_gw_interface_txqueuelen); + + /** Initialize the shaping setting */ + rc = bw_shaping_do_command(BW_PROG_TC, + "qdisc add dev %s root handle 1: htb default %x", + config->gw_interface, bw_default_classid); + + if (rc != 0) + return rc; + + /** Gateway speed limit enabled */ + if (bw_parent_classid != 0) { + rc = bw_shaping_do_command(BW_PROG_TC, + "class add dev %s parent 1: classid 1:%" PRIx32 + " htb rate %dkbit burst %uk", + config->gw_interface, bw_parent_classid, + config->bw_shaping_gw_max_down, + bw_shaping_burst_calc(config->bw_shaping_gw_max_down)); + + if (rc != 0) + return rc; + } + + /** Default class */ + rc = bw_shaping_do_command(BW_PROG_TC, + "class add dev %s parent 1:%" PRIx32 " classid 1:%" PRIx32 + " htb rate %dkbit burst %uk", + config->gw_interface, bw_parent_classid, bw_default_classid, + config->bw_shaping_gw_max_down, + bw_shaping_burst_calc(config->bw_shaping_gw_max_down)); + + if (rc != 0) + return rc; + + /** Add FQ_CODEL qdisc without ECN support */ + rc = bw_shaping_do_command(BW_PROG_TC, + "qdisc add dev %s parent 1:%" PRIx32 " fq_codel quantum 300 noecn", + config->gw_interface, bw_default_classid); + + if (rc != 0) + return rc; + + return 0; +} + +/** @internal +*/ +static int +bw_shaping_ingress_init(void) +{ + s_config *config = config_get_config(); + int rc; + + /** Create ingress on gw_interface */ + rc = bw_shaping_do_command(BW_PROG_TC, + "qdisc add dev %s handle ffff: ingress", config->gw_interface); + + if (rc != 0) + return rc; + + /** The IFB interface must be up before apply shaping setting */ + bw_shaping_do_command(BW_PROG_IFCONFIG, "%s txqueuelen %" PRIu32, + config->bw_shaping_ifb_interface, + config->bw_shaping_gw_interface_txqueuelen); + + rc = bw_shaping_do_command(BW_PROG_IFCONFIG, "%s up", + config->bw_shaping_ifb_interface); + + if (rc != 0) + return rc; + + /** Forward all ingress traffic to IFB device */ + rc = bw_shaping_do_command(BW_PROG_TC, + "filter add dev %s parent ffff: protocol all u32 match u32 0 0 " + "action mirred egress redirect dev %s", + config->gw_interface, config->bw_shaping_ifb_interface); + + if (rc != 0) + return rc; + + /** Initialize the shaping setting */ + rc = bw_shaping_do_command(BW_PROG_TC, + "qdisc add dev %s root handle 1: htb default %x", + config->bw_shaping_ifb_interface, bw_default_classid); + + if (rc != 0) + return rc; + + /** Gateway speed limit enabled */ + if (bw_parent_classid != 0) { + rc = bw_shaping_do_command(BW_PROG_TC, + "class add dev %s parent 1: classid 1:%" PRIx32 + " htb rate %dkbit burst %uk", + config->bw_shaping_ifb_interface, bw_parent_classid, + config->bw_shaping_gw_max_up, + bw_shaping_burst_calc(config->bw_shaping_gw_max_up)); + + if (rc != 0) + return rc; + } + + /** Default class */ + rc = bw_shaping_do_command(BW_PROG_TC, + "class add dev %s parent 1:%" PRIx32 " classid 1:%" PRIx32 + " htb rate %dkbit burst %uk", + config->bw_shaping_ifb_interface, bw_parent_classid, + bw_default_classid, config->bw_shaping_gw_max_up, + bw_shaping_burst_calc(config->bw_shaping_gw_max_up)); + + if (rc != 0) + return rc; + + /** Add FQ_CODEL qdisc with ECN support */ + rc = bw_shaping_do_command(BW_PROG_TC, + "qdisc add dev %s parent 1:%" PRIx32 " fq_codel quantum 300 ecn", + config->bw_shaping_ifb_interface, bw_default_classid); + + if (rc != 0) + return rc; + + return 0; +} + +/** @internal +*/ +static int +bw_shaping_client_add_class(t_client *client) +{ + static const char *class_tpl = "class %s dev %s parent 1:%" PRIx32 + " classid 1:%" PRIx32 " htb rate %dkbit burst %" PRIu16 "k"; + s_config *config = config_get_config(); + int rc; + uint32_t id = (client->id % bw_max_classid) + 1; + + /** Egress - Client Download */ + if (client->bw_settings.kbit_max_speed_down > 0) { + uint16_t burst = bw_shaping_burst_calc(client->bw_settings.kbit_max_speed_down); + + rc = bw_shaping_do_command(BW_PROG_TC, class_tpl, + "add", config->gw_interface, bw_parent_classid, id, + client->bw_settings.kbit_max_speed_down, burst); + + if (rc != 0) { + /** Retry replace */ + rc = bw_shaping_do_command(BW_PROG_TC, class_tpl, + "replace", config->gw_interface, bw_parent_classid, id, + client->bw_settings.kbit_max_speed_down, burst); + + if (rc != 0) + return rc; + } + } + + /** Ingress - Client Upload */ + if (client->bw_settings.kbit_max_speed_up > 0) { + uint16_t burst = bw_shaping_burst_calc(client->bw_settings.kbit_max_speed_up); + + rc = bw_shaping_do_command(BW_PROG_TC, class_tpl, + "add", config->bw_shaping_ifb_interface, bw_parent_classid, id, + client->bw_settings.kbit_max_speed_up, burst); + + if (rc != 0) { + /** Retry replace */ + rc = bw_shaping_do_command(BW_PROG_TC, class_tpl, + "replace", config->bw_shaping_ifb_interface, + bw_parent_classid, id, + client->bw_settings.kbit_max_speed_up, burst); + + if (rc != 0) + return rc; + } + } + + return 0; +} + +/** @internal +*/ +static int +bw_shaping_client_add_filter(t_client *client) +{ + static const char *filter_tpl = "filter %s dev %s parent 1: pref 5 " + "handle 800::%" PRIx32 " protocol ip u32 match ip %s %s/32 " + "flowid 1:%" PRIx32; + int rc; + s_config *config = config_get_config(); + uint32_t id = (client->id % bw_max_classid) + 1; + + /** Egress - Client Download */ + if (client->bw_settings.kbit_max_speed_down > 0) { + rc = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + "add", config->gw_interface, id, "dst", client->ip, id); + + if (rc != 0) { + /** Retry replace */ + rc = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + "replace", config->gw_interface, id, "dst", client->ip, id); + + if (rc != 0) + return rc; + } + } + + /** Ingress - Client Upload */ + if (client->bw_settings.kbit_max_speed_up > 0) { + rc = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + "add", config->bw_shaping_ifb_interface, id, "src", + client->ip,id); + + if (rc != 0) { + /** Retry replace */ + rc = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + "replace", config->bw_shaping_ifb_interface, id, "src", + client->ip, id); + + if (rc != 0) + return rc; + } + } + + return 0; +} + +/** @internal +*/ +static int +bw_shaping_client_remove_class(t_client *client) +{ + static const char *class_tpl = "class del dev %s parent 1:%" PRIx32 " classid 1:%x"; + s_config *config = config_get_config(); + int rc1 = 0; + int rc2 = 0; + uint32_t id = (client->id % bw_max_classid) + 1; + + /** Egress - Client Download */ + if (client->bw_settings.kbit_max_speed_down > 0) { + rc1 = bw_shaping_do_command(BW_PROG_TC, class_tpl, + config->gw_interface, bw_parent_classid, id); + } + + /** Ingress - Client Upload */ + if (client->bw_settings.kbit_max_speed_up > 0) { + rc2 = bw_shaping_do_command(BW_PROG_TC, class_tpl, + config->bw_shaping_ifb_interface, bw_parent_classid, id); + } + + return rc1 || rc2; +} + +/** @internal +*/ +static int +bw_shaping_client_remove_filter(t_client *client) +{ + static const char *filter_tpl = "filter del dev %s parent 1: pref 5 " + "handle 800::%" PRIx32 " protocol ip u32"; + int rc1 = 0; + int rc2 = 0; + s_config *config = config_get_config(); + uint32_t id = (client->id % bw_max_classid) + 1; + + /** Egress - Client Download */ + if (client->bw_settings.kbit_max_speed_down > 0) { + rc1 = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + config->gw_interface, id); + } + + /** Ingress - Client Upload */ + if (client->bw_settings.kbit_max_speed_up > 0) { + rc2 = bw_shaping_do_command(BW_PROG_TC, filter_tpl, + config->bw_shaping_ifb_interface, id); + } + + return rc1 || rc2; +} + +/** @internal +*/ +static uint16_t +bw_shaping_burst_calc(uint32_t speed) +{ + uint16_t burst = (speed + (8 * bw_hz - 1)) / (8 * bw_hz); + return burst > 2 ? burst : 2; +} + +/** @internal +*/ +static uint32_t +bw_shaping_get_rate_from_httpvar(request *r, const char *varname, + uint32_t def_rate) +{ + uint32_t rate = 0; + httpVar *var = httpdGetVariableByName(r, varname); + + if (var) { + if (sscanf(var->value, "%" SCNu32, &rate) != 1) { + debug(LOG_INFO, + "Bandwidth shaping (%s) %s is invalid (range: 0 - %u) " + "for client %s, fallback to default value %u kbps %s", + varname, var->value, BW_RATE_MAX, + r->clientAddr, def_rate, def_rate == 0 ? "(no shaping)" : ""); + } + } else { + rate = def_rate; + } + + return rate; +} + +/** Initialize the bandwidth shaping +*/ +int +bw_shaping_init(void) +{ + s_config *config = config_get_config(); + long hz = sysconf(_SC_CLK_TCK); + int rc; + + if (hz > 0) { + bw_hz = hz; + debug(LOG_DEBUG, "Bandwidth Shaping HZ: %lu", bw_hz); + } else { + debug(LOG_DEBUG, "Bandwidth Shaping HZ: %lu (fallback)", bw_hz); + } + + if (config->bw_shaping_gw_limit) { + bw_parent_classid = 0xffff; + bw_max_classid = 0xfffc; + bw_default_classid = 0xfffc; + } + + bw_quiet = 0; + + /** Cleanup existing bandwidth shaping settings */ + bw_shaping_destroy(); + + rc = bw_shaping_egress_init(); + + if (rc != 0) + goto error; + + rc = bw_shaping_ingress_init(); + + if (rc != 0) + goto error; + + return rc; + +error: + bw_shaping_destroy(); + return rc; +} + +/** Destroy the bandwidth shaping +*/ +int +bw_shaping_destroy(void) +{ + s_config *config = config_get_config(); + int rc1 = 0; + int rc2 = 0; + int rc3 = 0; + + bw_quiet = 1; + + rc1 = bw_shaping_do_command(BW_PROG_TC, "qdisc del dev %s root", + config->gw_interface); + rc2 = bw_shaping_do_command(BW_PROG_TC, "qdisc del dev %s ingress", + config->gw_interface); + rc3 = bw_shaping_do_command(BW_PROG_TC, "qdisc del dev %s root", + config->bw_shaping_ifb_interface); + + return rc1 || rc2 || rc3; +} + +/** Add new bandwidth shaping settings for specific client +*/ +int +bw_shaping_add(t_client * client) +{ + int rc; + bw_quiet = 0; + + if (client->bw_settings.kbit_max_speed_down == 0 && + client->bw_settings.kbit_max_speed_up == 0) + return 0; + + rc = bw_shaping_client_add_class(client); + + if (rc != 0) + goto error; + + rc = bw_shaping_client_add_filter(client); + + if (rc != 0) + goto error; + + debug(LOG_INFO, "Successfully setup bandwidth shaping %0.1f/%0.1f Mbps " + "for client %s", + (float)client->bw_settings.kbit_max_speed_down / 1024, + (float)client->bw_settings.kbit_max_speed_up / 1024, + client->ip); + + return 0; + +error: + bw_shaping_remove(client); + + debug(LOG_INFO, "Failed to setup bandwidth shaping %0.1f/%0.1f Mbps " + "for client %s", + (float)client->bw_settings.kbit_max_speed_down / 1024, + (float)client->bw_settings.kbit_max_speed_up / 1024, + client->ip); + + return rc; +} + +/** Remove bandwidth shaping settings for specific client +*/ +int +bw_shaping_remove(t_client * client) +{ + bw_quiet = 1; + return bw_shaping_client_remove_filter(client) || + bw_shaping_client_remove_class(client); +} + +/** Client bandwidth shaping setup +*/ +int +bw_shaping_client_setup(t_client * client, request *r) +{ + s_config *config = config_get_config(); + + client->bw_settings.kbit_max_speed_down = + bw_shaping_get_rate_from_httpvar(r, "bandwidth_max_down", + config->bw_shaping_def_max_down); + client->bw_settings.kbit_max_speed_up = + bw_shaping_get_rate_from_httpvar(r, "bandwidth_max_up", + config->bw_shaping_def_max_up); + + return 0; +} diff --git a/src/bw_shaping.h b/src/bw_shaping.h new file mode 100644 index 00000000..d2dad4ad --- /dev/null +++ b/src/bw_shaping.h @@ -0,0 +1,51 @@ +/* vim: set et ts=4 sts=4 sw=4 : */ +/********************************************************************\ + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * 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, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * +\********************************************************************/ + +/* $Id$ */ +/** @file bw_shaping.h + @brief Bandwidth shaping functions + @author Copyright (C) 2015 Neutron Soutmun +*/ + +#ifndef _BW_SHAPING_H_ +#define _BW_SHAPING_H_ + +#include + +#include "httpd.h" +#include "client_list.h" + +/** @brief Initialize the bandwidth shaping */ +int bw_shaping_init(void); + +/** @brief Destroy the bandwidth shaping */ +int bw_shaping_destroy(void); + +/** @brief Add new bandwidth shaping settings for specific client */ +int bw_shaping_add(t_client * client); + +/** @brief Remove bandwidth shaping settings for specific client */ +int bw_shaping_remove(t_client * client); + +/** @brief Client bandwidth shaping setup */ +int bw_shaping_client_setup(t_client * client, request *r); + +#endif /* _BW_SHAPING_H_ */ diff --git a/src/client_list.h b/src/client_list.h index ebc1c192..18672cf3 100644 --- a/src/client_list.h +++ b/src/client_list.h @@ -44,6 +44,15 @@ typedef struct _t_counters { time_t last_updated; /**< @brief Last update of the counters */ } t_counters; +/** Bandwidth shaping settings struct for a client's bandwidth shaping settings + */ +typedef struct _t_bw_settings { + unsigned int kbit_max_speed_down; /**< @brief Maximum download speed + (tc - kbit = 1024 bits) */ + unsigned int kbit_max_speed_up; /**< @brief Maximum upload speed + (tc - kbit = 1024 bits) */ +} t_bw_settings; + /** Client node for the connected client linked list. */ typedef struct _t_client { @@ -59,6 +68,7 @@ typedef struct _t_client { _http_* function is called */ t_counters counters; /**< @brief Counters for input/output of the client. */ + t_bw_settings bw_settings; /**< @brief Bandwidth shaping settings for client */ } t_client; /** @brief Get a new client struct, not added to the list yet */ diff --git a/src/conf.c b/src/conf.c index 89998cd1..994d3155 100644 --- a/src/conf.c +++ b/src/conf.c @@ -37,6 +37,8 @@ #include #include +#include + #include "common.h" #include "safe.h" #include "debug.h" @@ -104,6 +106,14 @@ typedef enum { oSSLCertPath, oSSLAllowedCipherList, oSSLUseSNI, + oBandwidthShaping, + oBandwidthShapingGWLimit, + oBandwidthShapingIFBIface, + oBandwidthShapingGWInterfaceTXQueueLen, + oBandwidthShapingGWMaxDown, + oBandwidthShapingGWMaxUp, + oBandwidthShapingDefMaxDown, + oBandwidthShapingDefMaxUp, } OpCodes; /** @internal @@ -151,6 +161,14 @@ static const struct { "sslcertpath", oSSLCertPath}, { "sslallowedcipherlist", oSSLAllowedCipherList}, { "sslusesni", oSSLUseSNI}, { + "bandwidthshaping", oBandwidthShaping}, { + "bandwidthshapinggatewayspeedlimit", oBandwidthShapingGWLimit}, { + "bandwidthshapingifbinterface", oBandwidthShapingIFBIface}, { + "bandwidthshapinggatewayinterfacetxqueuelen", oBandwidthShapingGWInterfaceTXQueueLen}, { + "bandwidthgatewaymaxdown", oBandwidthShapingGWMaxDown}, { + "bandwidthgatewaymaxup", oBandwidthShapingGWMaxUp}, { + "bandwidthdefaultclientmaxdown", oBandwidthShapingDefMaxDown}, { + "bandwidthdefaultclientmaxup", oBandwidthShapingDefMaxUp}, { NULL, oBadOption},}; static void config_notnull(const void *, const char *); @@ -208,6 +226,14 @@ config_init(void) config.ssl_cipher_list = NULL; config.arp_table_path = safe_strdup(DEFAULT_ARPTABLE); config.ssl_use_sni = DEFAULT_AUTHSERVSSLSNI; + config.bw_shaping = DEFAULT_BWSHAPING; + config.bw_shaping_gw_limit = DEFAULT_BWSHAPING_GW_LIMIT; + config.bw_shaping_ifb_interface = safe_strdup(DEFAULT_BWSHAPING_IFB_IFACE); + config.bw_shaping_gw_interface_txqueuelen = DEFAULT_BWSHAPING_GW_IFACE_TXQUEUELEN; + config.bw_shaping_gw_max_down = DEFAULT_BWSHAPING_GW_MAX_DOWN; + config.bw_shaping_gw_max_up = DEFAULT_BWSHAPING_GW_MAX_UP; + config.bw_shaping_def_max_down = 0; + config.bw_shaping_def_max_up = 0; debugconf.log_stderr = 1; debugconf.debuglevel = DEFAULT_DEBUGLEVEL; @@ -810,6 +836,65 @@ config_read(const char *filename) #endif #endif break; + case oBandwidthShaping: + config.bw_shaping = parse_boolean_value(p1); + if (config.bw_shaping < 0) { + debug(LOG_WARNING, "Bad syntax for Parameter: BandwidthShaping on line %d " "in %s." + "The syntax is yes or no." , linenum, filename); + exit(-1); + } + break; + case oBandwidthShapingGWLimit: + config.bw_shaping_gw_limit = parse_boolean_value(p1); + if (config.bw_shaping_gw_limit < 0) { + debug(LOG_WARNING, "Bad syntax for Parameter: BandwidthShapingGatewaySpeedLimit on line %d " "in %s." + "The syntax is yes or no." , linenum, filename); + exit(-1); + } + break; + case oBandwidthShapingIFBIface: + free(config.bw_shaping_ifb_interface); + config.bw_shaping_ifb_interface = safe_strdup(p1); + break; + case oBandwidthShapingGWInterfaceTXQueueLen: + if (sscanf(p1, "%" SCNu32, &config.bw_shaping_gw_interface_txqueuelen) != 1) { + debug(LOG_WARNING, "BandwidthGatewayInterfaceTXQueueLen - %s is invalid. " + "It should be in the range of 0 to %" PRIu32 ". " + "Fallback to default %" PRIu32, + p1, UINT32_MAX, DEFAULT_BWSHAPING_GW_IFACE_TXQUEUELEN); + config.bw_shaping_gw_interface_txqueuelen = DEFAULT_BWSHAPING_GW_IFACE_TXQUEUELEN; + } + break; + case oBandwidthShapingGWMaxDown: + if (sscanf(p1, "%" SCNu32, &config.bw_shaping_gw_max_down) != 1) { + debug(LOG_WARNING, "BandwidthGatewayMaxDown - %s is invalid. " + "It should be in the range of 0 to %" PRIu32 ". " + "Fallback to default %u Mbps!", p1, UINT32_MAX, + DEFAULT_BWSHAPING_GW_MAX_DOWN / 1024); + } + break; + case oBandwidthShapingGWMaxUp: + if (sscanf(p1, "%" SCNu32, &config.bw_shaping_gw_max_up) != 1) { + debug(LOG_WARNING, "BandwidthGatewayMaxUp - %s is invalid. " + "It should be in the range of 0 to %" PRIu32 ". " + "Fallback to default %u Mbps!", p1, UINT32_MAX, + DEFAULT_BWSHAPING_GW_MAX_UP / 1024); + } + break; + case oBandwidthShapingDefMaxDown: + if (sscanf(p1, "%" SCNu32, &config.bw_shaping_def_max_down) != 1) { + debug(LOG_WARNING, "BandwidthDefaultClientMaxDown - %s is invalid. " + "It should be in the range of 0 to %" PRIu32 ". " + "Fallback to no shaping!", p1, UINT32_MAX); + } + break; + case oBandwidthShapingDefMaxUp: + if (sscanf(p1, "%" SCNu32, &config.bw_shaping_def_max_up) != 1) { + debug(LOG_WARNING, "BandwidthDefaultClientMaxUp - %s is invalid. " + "It should be in the range of 0 to %" PRIu32 ". " + "Fallback to no shaping!", p1, UINT32_MAX); + } + break; case oBadOption: /* FALL THROUGH */ default: diff --git a/src/conf.h b/src/conf.h index f9ce3404..bf2bae87 100644 --- a/src/conf.h +++ b/src/conf.h @@ -28,6 +28,8 @@ #ifndef _CONFIG_H_ #define _CONFIG_H_ +#include + /*@{*/ /** Defines */ @@ -68,6 +70,13 @@ #define DEFAULT_DELTATRAFFIC 0 /* 0 means: Enable peer verification */ #define DEFAULT_ARPTABLE "/proc/net/arp" #define DEFAULT_AUTHSERVSSLSNI 0 /* 0 means: Disable SNI */ +#define DEFAULT_BWSHAPING 0 /* 0 means: Disable bandwidth shaping */ +#define DEFAULT_BWSHAPING_GW_LIMIT 0 /* 0 means: Disable bandwidth shaping gateway speed limit */ +#define DEFAULT_BWSHAPING_IFB_IFACE "ifb0" +#define DEFAULT_BWSHAPING_GW_IFACE_TXQUEUELEN 200 +#define DEFAULT_BWSHAPING_GW_MAX_DOWN 102400 /* 100 Mbps */ +#define DEFAULT_BWSHAPING_GW_MAX_UP 102400 /* 100 Mbps */ + /*@}*/ /*@{*/ @@ -198,6 +207,25 @@ typedef struct { char *arp_table_path; /**< @brief Path to custom ARP table, formatted like /proc/net/arp */ t_popular_server *popular_servers; /**< @brief list of popular servers */ + int bw_shaping; /**< @brief boolean, whether to enable bandwidth shaping */ + int bw_shaping_gw_limit; /**< @brief boolean, whether to enable bandwidth + shaping for gateway interface with the speed + that settup from gw_max_{down,up} */ + char *bw_shaping_ifb_interface; /**< @brief IFB interface (helper) + for upload shaping */ + uint32_t bw_shaping_gw_interface_txqueuelen; /**< @brief Gateway interface txqueuelen */ + uint32_t bw_shaping_gw_max_down; /**< @brief Default gateway bandwidth + shaping maximum download speed + default: 100 Mbps */ + uint32_t bw_shaping_gw_max_up; /**< @brief Default gateway bandwidth + shaping maximum upload speed + default: 100 Mbps */ + uint32_t bw_shaping_def_max_down; /**< @brief Default client bandwidth + shaping maximum download speed + 0: means no shaping */ + uint32_t bw_shaping_def_max_up; /**< @brief Default client bandwidth + shaping maximum upload speed + 0: means no shaping */ } s_config; /** @brief Get the current gateway configuration */ diff --git a/src/firewall.c b/src/firewall.c index 211af351..476a5486 100644 --- a/src/firewall.c +++ b/src/firewall.c @@ -57,6 +57,7 @@ #include "centralserver.h" #include "client_list.h" #include "commandline.h" +#include "bw_shaping.h" static int _fw_deny_raw(const char *, const char *, const int); @@ -73,6 +74,7 @@ fw_allow(t_client * client, int new_fw_connection_state) { int result; int old_state = client->fw_connection_state; + s_config *config = config_get_config(); debug(LOG_DEBUG, "Allowing %s %s with fw_connection_state %d", client->ip, client->mac, new_fw_connection_state); client->fw_connection_state = new_fw_connection_state; @@ -80,6 +82,10 @@ fw_allow(t_client * client, int new_fw_connection_state) /* Grant first */ result = iptables_fw_access(FW_ACCESS_ALLOW, client->ip, client->mac, new_fw_connection_state); + if (config->bw_shaping) { + bw_shaping_add(client); + } + /* Deny after if needed. */ if (old_state != FW_MARK_NONE) { debug(LOG_DEBUG, "Clearing previous fw_connection_state %d", old_state); @@ -112,6 +118,12 @@ fw_allow_host(const char *host) int fw_deny(t_client * client) { + s_config *config = config_get_config(); + if (config->bw_shaping) { + debug(LOG_DEBUG, "Removing bandwidth shaping for %s", client->ip); + bw_shaping_remove(client); + } + int fw_connection_state = client->fw_connection_state; debug(LOG_DEBUG, "Denying %s %s with fw_connection_state %d", client->ip, client->mac, client->fw_connection_state); @@ -195,6 +207,7 @@ fw_init(void) int result = 0; int new_fw_state; t_client *client = NULL; + s_config *config = config_get_config(); if (!init_icmp_socket()) { return 0; @@ -203,6 +216,11 @@ fw_init(void) debug(LOG_INFO, "Initializing Firewall"); result = iptables_fw_init(); + if (config->bw_shaping) { + debug(LOG_INFO, "Initializing Bandwidth Shaping"); + bw_shaping_init(); + } + if (restart_orig_pid) { debug(LOG_INFO, "Restoring firewall rules for clients inherited from parent"); LOCK_CLIENT_LIST(); @@ -244,6 +262,13 @@ fw_set_authservers(void) int fw_destroy(void) { + s_config *config = config_get_config(); + + if (config->bw_shaping) { + debug(LOG_INFO, "Removing Bandwidth Shaping settings"); + bw_shaping_destroy(); + } + close_icmp_socket(); debug(LOG_INFO, "Removing Firewall rules"); return iptables_fw_destroy(); diff --git a/src/http.c b/src/http.c index ea2c7316..a65a076b 100644 --- a/src/http.c +++ b/src/http.c @@ -54,6 +54,7 @@ #include "centralserver.h" #include "util.h" #include "wd_util.h" +#include "bw_shaping.h" #include "../config.h" @@ -256,6 +257,7 @@ http_callback_auth(httpd * webserver, request * r) httpVar *token; char *mac; httpVar *logout = httpdGetVariableByName(r, "logout"); + const s_config *config = config_get_config(); if ((token = httpdGetVariableByName(r, "token"))) { /* They supplied variable "token" */ @@ -269,7 +271,11 @@ http_callback_auth(httpd * webserver, request * r) if ((client = client_list_find(r->clientAddr, mac)) == NULL) { debug(LOG_DEBUG, "New client for %s", r->clientAddr); - client_list_add(r->clientAddr, mac, token->value); + client = client_list_add(r->clientAddr, mac, token->value); + + if (config->bw_shaping) { + bw_shaping_client_setup(client, r); + } } else if (logout) { logout_client(client); } else { diff --git a/wifidog.conf b/wifidog.conf index fd953f05..05843375 100644 --- a/wifidog.conf +++ b/wifidog.conf @@ -359,3 +359,108 @@ FirewallRuleSet unknown-users { FirewallRuleSet locked-users { FirewallRule block to 0.0.0.0/0 } + +# Parameter: BandwidthShaping +# Default: no +# Optional +# +# Enable per client bandwidth shaping +# +# If this setting is set to yes, then wifidog will prepare the bandwidth shaping +# settings on GatewayInterface and apply the bandwidth policy depends on other +# related bandwidth settings +# +# See: BandwidthShapingGatewaySpeedLimit, BandwidthGatewayMax{Down,Up} +# BandwidthDefaultClientMax{Down,Up} +# +# BandwidthShaping no + +# Parameter: BandwidthShapingGatewaySpeedLimit +# Default: no +# Optional +# +# Enable gateway speed limit, this setting will be ignored if BandwidthShaping +# is set to false. +# +# If this setting is set to yes, the traffic passthrough this GatewayInterface +# will not exeed the limit. +# +# See: BandwidthGatewayMax{Down,Up} +# +# BandwidthShapingGatewaySpeedLimit no + +# Parameter: BandwidthGatewayMaxDown +# Default: 102400 +# Optional +# +# Maximum download speed (kbit = 1024 bits) passthrough this GatewayInterface, +# This setting will be ignored if the BandwidthShapingGatewaySpeedLimit is set +# to false. +# +# BandwidthGatewayMaxDown 102400 + +# Parameter: BandwidthGatewayMaxUp +# Default: 102400 +# Optional +# +# Maximum upload speed (kbit = 1024 bits) passthrough this GatewayInterface, +# This setting will be ignored if the BandwidthShapingGatewaySpeedLimit is set +# to false. +# +# BandwidthGatewayMaxUp 102400 + +# Parameter: BandwidthDefaultClientMaxDown +# Default: 0 +# Optional +# +# Maximum default download speed (kbit = 1024 bits) for each client. +# +# wifidog will apply the policy which the later will override the former, +# * BandwidthDefaultClientMaxDown setting +# * The "bandwidth_max_down" HTTP querystring parameter get from +# the authen server (per client setting) +# +# If this setting is set to 0 that mean no download bandwidth shaping applied. +# It will be ignored if the BandwidthShaping is set to false. +# +# BandwidthDefaultClientMaxDown 0 + +# Parameter: BandwidthDefaultClientMaxUp +# Default: 0 +# Optional +# +# Maximum default upload speed (kbit = 1024 bits) for each client. +# +# wifidog will apply the policy which the later will override the former, +# * BandwidthDefaultClientMaxUp setting +# * The "bandwidth_max_up" HTTP querystring parameter get from +# the authen server (per client setting) +# +# If this setting is set to 0 that mean no download bandwidth shaping applied. +# It will be ignored if the BandwidthShaping is set to false. +# +# BandwidthDefaultClientMaxUp 0 + +# Parameter: BandwidthShapingIFBInterface +# Default: ifb0 +# Optional +# +# The helper interface used for the upload bandwidth shaping. +# +# Technically, Linux could not do any advanced traffic controls on +# traffic-in side of the interface, therefore, we should redirect the traffic +# to the helper interface and apply the advanced trafficc control on it. +# +# BandwidthShapingIFBInterface ifb0 + +# Parameter: BandwidthShapingGatewayInterfaceTxQueueLen +# Default: 200 +# Optional +# +# Control how many Tx queuelen on the GatewayInterface +# +# This setting will be used to fine tune the bandwidth shaping for performance. +# There is no a silver bullet for this setting but appropriated value will make +# a better performance. +# +# BandwidthShapingGatewayInterfaceTxQueueLen 200