Source code for paradrop.confd.qos

import six

from .base import ConfigObject, ConfigOption
from .command import Command


# Map class names to priority band numbers (0 is maximum priority).
DEFAULT_CLASSES = {
    "BestEffort": 2,
    "Bulk": 3,
    "Interactive": 1,
    "IntBulk": 2,
    "Priority": 0
}


[docs]def compute_hfsc_params(classes, capacity): result = dict() sum_priority = 0 sum_avgrate = 0 sum_limitrate = 0 # During the first pass, calculate the sums of rates and priorities and # determine which service curves are present for each class. for cid, cl in classes: params = { "has_rt": False, "has_ls": False, "has_ul": False, "has_bend": False } # The avgrate option adds real-time (rt) service curve. if cl.avgrate is not None: sum_avgrate += cl.avgrate params['has_rt'] = True # The priority option adds link share (ls) service curve. if cl.priority is not None: sum_priority += cl.priority params['has_ls'] = True if cl.limitrate is not None: sum_limitrate += cl.limitrate params['has_ul'] = True # Note: if the class has neither rt nor ls curves, # it will get no service (packets dropped). # The packetsize or packetdelay are not very intuitive names, but we # use them to set the duration of the first bend of the service curve. if (cl.packetsize is not None or cl.packetdelay is not None): params['has_bend'] = True result[cid] = params for cid, cl in classes: params = result[cid] if params['has_rt']: # In steady state, each class with a real-time curve gets at least # its allocated rate. The avgrate option is interpreted as a # percentage of the capacity. params['rt_m2'] = float(cl.avgrate) * capacity / 100.0 if params['has_bend']: # The classes with real-time curves are allowed to temporarily # burst up to the full capacity. params['rt_m1'] = float(cl.avgrate) / sum_avgrate * capacity params['rt_d'] = cl.packetdelay if cl.packetsize is not None: transfer_delay = float(8 * cl.packetsize) / params['rt_m1'] params['rt_d'] = max(params['rt_d'], transfer_delay) if params['has_ls']: # In steady state, each class with a link-share curve gets a # proportion of the capacity defined by its priority. params['ls_m2'] = float(cl.priority) * capacity / 100.0 if params['has_bend']: # Allow temporary burst up to the full capacity. params['ls_m1'] = float(cl.priority) / sum_priority * capacity params['ls_d'] = cl.packetdelay if cl.packetsize is not None: transfer_delay = float(8 * cl.packetsize) / params['ls_m1'] params['ls_d'] = max(params['ls_d'], transfer_delay) if params['has_ul']: # Set steady state upper limit from a percentage of the capacity. params['ul_m2'] = float(cl.limitrate) * capacity / 100.0 return result
[docs]class ConfigInterface(ConfigObject): typename = "interface" options = [ ConfigOption(name="enabled", type=bool, required=True), ConfigOption(name="classgroup", default="Default"), ConfigOption(name="overhead", type=bool, default=True), ConfigOption(name="upload", type=int, default=4096), ConfigOption(name="download", type=int, default=512) ]
[docs] def apply(self, allConfigs): commands = list() if not self.enabled: return commands # Look up the interface section. interface = self.lookup(allConfigs, "network", "interface", self.name) # Store ifname mainly for the revert command. self.config_ifname = interface.config_ifname classes = [] default_classid = None classgroup = self.lookup(allConfigs, "qos", "classgroup", self.classgroup) for class_name, class_id in six.iteritems(classgroup.class_id_map): if class_name == classgroup.default: default_classid = class_id traffic_class = self.lookup(allConfigs, "qos", "class", class_name) classes.append((class_id, traffic_class)) cmd = ["tc", "qdisc", "add", "dev", self.config_ifname, "root", "handle", "1:", "hfsc"] if default_classid is not None: cmd.extend(["default", str(default_classid)]) commands.append((self.PRIO_CREATE_QDISC, Command(cmd, self))) hfsc_params = compute_hfsc_params(classes, self.upload) for class_id, traffic_class in classes: full_class_id = "1:{}".format(class_id) params = hfsc_params[class_id] cmd = [ "tc", "class", "add", "dev", self.config_ifname, "parent", "1:", "classid", full_class_id, "hfsc" ] if params['has_rt']: cmd.append("rt") if params['has_bend']: cmd.extend(["m1", str(params['rt_m1']) + "kbit", "d", str(params['rt_d']) + "ms"]) cmd.extend(["m2", str(params['rt_m2']) + "kbit"]) if params['has_ls']: cmd.append("ls") if params['has_bend']: cmd.extend(["m1", str(params['ls_m1']) + "kbit", "d", str(params['ls_d']) + "ms"]) cmd.extend(["m2", str(params['ls_m2']) + "kbit"]) if params['has_ul']: cmd.extend(["ul", "m2", str(params['ul_m2'])+"kbit"]) commands.append((self.PRIO_CREATE_QDISC, Command(cmd, self))) # Add a fair queue to each class so that flows within the same # class receive fair treatment. cmd = [ "tc", "qdisc", "add", "dev", self.config_ifname, "parent", full_class_id, "fq_codel", "noecn" ] commands.append((self.PRIO_CREATE_QDISC, Command(cmd, self))) return commands
[docs] def revert(self, allConfigs): commands = list() if not self.enabled: return commands cmd = ["tc", "qdisc", "del", "dev", self.config_ifname, "root"] commands.append((-self.PRIO_CREATE_QDISC, Command(cmd, self))) return commands
[docs]class ConfigClassify(ConfigObject): typename = "classify" options = [ ConfigOption(name="target", required=True), ConfigOption(name="proto"), ConfigOption(name="srchost"), ConfigOption(name="dsthost"), ConfigOption(name="ports"), ConfigOption(name="srcports"), ConfigOption(name="dstports"), ConfigOption(name="portrange"), ConfigOption(name="pktsize"), ConfigOption(name="tcpflags"), ConfigOption(name="mark"), ConfigOption(name="connbytes"), ConfigOption(name="tos"), ConfigOption(name="dscp"), ConfigOption(name="direction") ]
[docs] def make_iptables_cmd(self, action, ifname, class_id): cmd = [ "iptables", "--wait", "5", "--table", "mangle", action, "POSTROUTING", "--out-interface", ifname ] if self.proto is not None: cmd.extend(["--protocol", self.proto]) if self.srchost is not None: cmd.extend(["--source", self.srchost]) if self.dsthost is not None: cmd.extend(["--destination", self.dsthost]) if self.ports is not None: cmd.extend(["--match", "multiport", "--ports", self.ports]) if self.srcports is not None: cmd.extend(["--match", "multiport", "--source-ports", self.srcports]) if self.dstports is not None: cmd.extend(["--match", "multiport", "--destination-ports", self.dstports]) if self.portrange is not None: cmd.extend(["--match", "multiport", "--ports", self.portrange]) if self.pktsize is not None: cmd.extend(["--match", "connbytes", "--connbytes", self.pktsize]) if self.tcpflags is not None: cmd.extend(["--protocol", "tcp", "--tcp-flags", self.tcpflags]) if self.mark is not None: cmd.extend(["--match", "mark", "--mark", self.mark]) if self.connbytes is not None: cmd.extend(["--match", "connbytes", "--connbytes", self.connbytes]) if self.tos is not None: cmd.extend(["--match", "tos", "--tos", self.tos]) if self.dscp is not None: cmd.extend(["--match", "dscp", "--dscp-class", self.dscp]) comment = "classify {}".format(self.target) cmd.extend([ "--match", "comment", "--comment", comment, "--jump", "CLASSIFY", "--set-class", class_id ]) return Command(cmd, self)
[docs] def apply(self, allConfigs): self._created_rules = [] commands = [] self.lookup(allConfigs, "qos", "class", self.target) # Find all classgroup sections that contain the target class. for group in self.findByType(allConfigs, "qos", "classgroup"): class_id = group.get_class_id(self.target) if class_id is None: continue group.dependents.add(self) # Find all interfaces that use the classgroup. interface_query = {"classgroup": group.name} for interface in self.findByType(allConfigs, "qos", "interface", where=interface_query): if not interface.enabled: continue interface.dependents.add(self) network_interface = self.lookup(allConfigs, "network", "interface", interface.name) full_class_id = "1:{}".format(class_id) args = (network_interface.config_ifname, full_class_id) cmd = self.make_iptables_cmd("--append", *args) # Save these arguments so that we can recreate the iptables # commands in the revert method. self._created_rules.append(args) commands.append((self.PRIO_IPTABLES_RULE, cmd)) return commands
[docs] def revert(self, allConfigs): commands = [] for args in self._created_rules: cmd = self.make_iptables_cmd("--delete", *args) commands.append((-self.PRIO_IPTABLES_RULE, cmd)) return commands
[docs]class ConfigClassgroup(ConfigObject): typename = "classgroup" options = [ ConfigOption(name="classes", required=True), ConfigOption(name="default", required=True) ]
[docs] def get_class_id(self, class_name): """ Get ID for a traffic class in this group. Returns None if the class is not a member of the group. """ return self.class_id_map.get(class_name, None)
[docs] def setup(self): # Map names to integer ids. self.class_id_map = dict() names = self.classes.split(" ") for i in range(len(names)): self.class_id_map[names[i]] = i+1
[docs]class ConfigClass(ConfigObject): typename = "class" options = [ ConfigOption(name="packetsize", type=int), ConfigOption(name="packetdelay", type=int), ConfigOption(name="maxsize", type=int), ConfigOption(name="avgrate", type=int), ConfigOption(name="limitrate", type=int), ConfigOption(name="priority", type=int) ]