diff options
| -rw-r--r-- | interface-definitions/include/qos/hfsc-m1.xml.i | 2 | ||||
| -rw-r--r-- | interface-definitions/include/qos/hfsc-m2.xml.i | 2 | ||||
| -rw-r--r-- | python/vyos/qos/trafficshaper.py | 89 | ||||
| -rwxr-xr-x | src/conf_mode/qos.py | 4 | 
4 files changed, 90 insertions, 7 deletions
| diff --git a/interface-definitions/include/qos/hfsc-m1.xml.i b/interface-definitions/include/qos/hfsc-m1.xml.i index 677d817ba..21b9c4f32 100644 --- a/interface-definitions/include/qos/hfsc-m1.xml.i +++ b/interface-definitions/include/qos/hfsc-m1.xml.i @@ -27,6 +27,6 @@        <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>      </valueHelp>    </properties> -  <defaultValue>100%%</defaultValue> +  <defaultValue>0bit</defaultValue>  </leafNode>  <!-- include end --> diff --git a/interface-definitions/include/qos/hfsc-m2.xml.i b/interface-definitions/include/qos/hfsc-m2.xml.i index 7690df4b0..24e8f5d63 100644 --- a/interface-definitions/include/qos/hfsc-m2.xml.i +++ b/interface-definitions/include/qos/hfsc-m2.xml.i @@ -27,6 +27,6 @@        <description>bps(8),kbps(8*10^3),mbps(8*10^6), gbps, tbps - Byte/sec</description>      </valueHelp>    </properties> -  <defaultValue>100%%</defaultValue> +  <defaultValue>100%</defaultValue>  </leafNode>  <!-- include end --> diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py index 0d5f9a8a1..1f3b03680 100644 --- a/python/vyos/qos/trafficshaper.py +++ b/python/vyos/qos/trafficshaper.py @@ -1,4 +1,4 @@ -# Copyright 2022-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2022-2024 VyOS maintainers and contributors <maintainers@vyos.io>  #  # This library is free software; you can redistribute it and/or  # modify it under the terms of the GNU Lesser General Public @@ -117,8 +117,91 @@ class TrafficShaper(QoSBase):          # call base class          super().update(config, direction) -class TrafficShaperHFSC(TrafficShaper): +class TrafficShaperHFSC(QoSBase): +    _parent = 1 +    qostype = 'shaper_hfsc' + +    # https://man7.org/linux/man-pages/man8/tc-hfsc.8.html      def update(self, config, direction): +        class_id_max = 0 +        if 'class' in config: +            tmp = list(config['class']) +            tmp.sort() +            class_id_max = tmp[-1] + +        r2q = 10 +        # bandwidth is a mandatory CLI node +        speed = self._rate_convert(config['bandwidth']) +        speed_bps = int(speed) // 8 + +        # need a bigger r2q if going fast than 16 mbits/sec +        if (speed_bps // r2q) >= MAXQUANTUM: # integer division +            r2q = ceil(speed_bps // MAXQUANTUM) +        else: +            # if there is a slow class then may need smaller value +            if 'class' in config: +                min_speed = speed_bps +                for cls, cls_options in config['class'].items(): +                    # find class with the lowest bandwidth used +                    if 'bandwidth' in cls_options: +                        bw_bps = int(self._rate_convert(cls_options['bandwidth'])) // 8 # bandwidth in bytes per second +                        if bw_bps < min_speed: +                            min_speed = bw_bps + +                while (r2q > 1) and (min_speed // r2q) < MINQUANTUM: +                    tmp = r2q -1 +                    if (speed_bps // tmp) >= MAXQUANTUM: +                        break +                    r2q = tmp + +        default_minor_id = int(class_id_max) +1 +        tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_minor_id:x}' # default is in hex +        self._cmd(tmp) + +        tmp = f'tc class replace dev {self._interface} parent {self._parent:x}: classid {self._parent:x}:1 hfsc sc rate {speed} ul rate {speed}' +        self._cmd(tmp) + +        if 'class' in config: +            for cls, cls_config in config['class'].items(): +                # class id is used later on and passed as hex, thus this needs to be an int +                cls = int(cls) +                # ls m1 +                if cls_config.get('linkshare', {}).get('m1').endswith('%'): +                    percent = cls_config['linkshare']['m1'].rstrip('%') +                    m_one_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 +                else: +                    m_one_rate = cls_config['linkshare']['m1'] +                # ls m2 +                if cls_config.get('linkshare', {}).get('m2').endswith('%'): +                    percent = cls_config['linkshare']['m2'].rstrip('%') +                    m_two_rate = self._rate_convert(config['bandwidth']) * int(percent) // 100 +                else: +                    m_two_rate = self._rate_convert(cls_config['linkshare']['m2']) + +                tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} ' +                self._cmd(tmp) + +                tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10' +                self._cmd(tmp) + +        if 'default' in config: +            # ls m1 +            if config.get('default', {}).get('linkshare', {}).get('m1').endswith('%'): +                percent = config['default']['linkshare']['m1'].rstrip('%') +                m_one_rate = self._rate_convert(config['default']['linkshare']['m1']) * int(percent) // 100 +            else: +                m_one_rate = config['default']['linkshare']['m1'] +            # ls m2 +            if config.get('default', {}).get('linkshare', {}).get('m2').endswith('%'): +                percent = config['default']['linkshare']['m2'].rstrip('%') +                m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) * int(percent) // 100 +            else: +                m_two_rate = self._rate_convert(config['default']['linkshare']['m2']) +            tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{default_minor_id:x} hfsc ls m1 {m_one_rate} m2 {m_two_rate} ' +            self._cmd(tmp) + +            tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{default_minor_id:x} sfq perturb 10' +            self._cmd(tmp) +          # call base class          super().update(config, direction) - diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py index ad4121a49..40d7a6c16 100755 --- a/src/conf_mode/qos.py +++ b/src/conf_mode/qos.py @@ -149,7 +149,7 @@ def verify(qos):                  if 'class' in policy_config:                      for cls, cls_config in policy_config['class'].items():                          # bandwidth is not mandatory for priority-queue - that is why this is on the exception list -                        if 'bandwidth' not in cls_config and policy_type not in ['priority_queue', 'round_robin']: +                        if 'bandwidth' not in cls_config and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']:                              raise ConfigError(f'Bandwidth must be defined for policy "{policy}" class "{cls}"!')                      if 'match' in cls_config:                          for match, match_config in cls_config['match'].items(): @@ -173,7 +173,7 @@ def verify(qos):                      if 'default' not in policy_config:                          raise ConfigError(f'Policy {policy} misses "default" class!')                  if 'default' in policy_config: -                    if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin']: +                    if 'bandwidth' not in policy_config['default'] and policy_type not in ['priority_queue', 'round_robin', 'shaper_hfsc']:                          raise ConfigError('Bandwidth not defined for default traffic!')      # we should check interface ingress/egress configuration after verifying that | 
