summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/defaults.py3
-rw-r--r--python/vyos/ifconfig/interface.py15
-rw-r--r--python/vyos/ifconfig/wireguard.py3
-rw-r--r--python/vyos/qos/base.py10
-rw-r--r--python/vyos/qos/cake.py47
-rw-r--r--python/vyos/qos/trafficshaper.py116
-rw-r--r--python/vyos/utils/convert.py26
7 files changed, 128 insertions, 92 deletions
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index dec619d3e..425990967 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -36,7 +36,8 @@ directories = {
'isc_dhclient_dir' : '/run/dhclient',
'dhcp6_client_dir' : '/run/dhcp6c',
'vyos_configdir' : '/opt/vyatta/config',
- 'completion_dir' : f'{base_dir}/completion'
+ 'completion_dir' : f'{base_dir}/completion',
+ 'ca_certificates' : '/usr/local/share/ca-certificates/vyos'
}
config_status = '/tmp/vyos-config-status'
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 002d3da9e..cd562e1fe 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -98,6 +98,10 @@ class Interface(Control):
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].ifalias | [0]', json.loads(j)) or '',
},
+ 'ifindex': {
+ 'shellcmd': 'ip -json -detail link list dev {ifname}',
+ 'format': lambda j: jmespath.search('[*].ifindex | [0]', json.loads(j)) or '',
+ },
'mac': {
'shellcmd': 'ip -json -detail link list dev {ifname}',
'format': lambda j: jmespath.search('[*].address | [0]', json.loads(j)),
@@ -428,6 +432,17 @@ class Interface(Control):
nft_command = f'add element inet vrf_zones ct_iface_map {{ "{self.ifname}" : {vrf_table_id} }}'
self._nft_check_and_run(nft_command)
+ def get_ifindex(self):
+ """
+ Get interface index by name
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_ifindex()
+ '2'
+ """
+ return int(self.get_interface('ifindex'))
+
def get_min_mtu(self):
"""
Get hardware minimum supported MTU
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 9030b1302..cccac361d 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -195,6 +195,9 @@ class WireGuardIf(Interface):
base_cmd += f' private-key {tmp_file.name}'
base_cmd = base_cmd.format(**config)
+ # T6490: execute command to ensure interface configured
+ self._cmd(base_cmd)
+
if 'peer' in config:
for peer, peer_config in config['peer'].items():
# T4702: No need to configure this peer when it was explicitly
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 322cdca44..12d940e3c 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -17,6 +17,7 @@ import os
import jmespath
from vyos.base import Warning
+from vyos.ifconfig import Interface
from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
from vyos.utils.file import read_file
@@ -253,19 +254,24 @@ class QoSBase:
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
if not has_filter:
- for key in ['mark', 'vif', 'ip', 'ipv6']:
+ for key in ['mark', 'vif', 'ip', 'ipv6', 'interface']:
if key in match_config:
has_filter = True
break
- if self.qostype == 'shaper' and 'prio ' not in filter_cmd:
+ if self.qostype in ['shaper', 'shaper_hfsc'] and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
if 'mark' in match_config:
mark = match_config['mark']
filter_cmd += f' handle {mark} fw'
+
if 'vif' in match_config:
vif = match_config['vif']
filter_cmd += f' basic match "meta(vlan mask 0xfff eq {vif})"'
+ elif 'interface' in match_config:
+ iif_name = match_config['interface']
+ iif = Interface(iif_name).get_ifindex()
+ filter_cmd += f' basic match "meta(rt_iif eq {iif})"'
for af in ['ip', 'ipv6']:
tc_af = af
diff --git a/python/vyos/qos/cake.py b/python/vyos/qos/cake.py
index 1ee7d0fc3..ca5a26917 100644
--- a/python/vyos/qos/cake.py
+++ b/python/vyos/qos/cake.py
@@ -15,10 +15,25 @@
from vyos.qos.base import QoSBase
+
class CAKE(QoSBase):
+ """
+ https://man7.org/linux/man-pages/man8/tc-cake.8.html
+ """
+
_direction = ['egress']
- # https://man7.org/linux/man-pages/man8/tc-cake.8.html
+ flow_isolation_map = {
+ 'blind': 'flowblind',
+ 'src-host': 'srchost',
+ 'dst-host': 'dsthost',
+ 'dual-dst-host': 'dual-dsthost',
+ 'dual-src-host': 'dual-srchost',
+ 'triple-isolate': 'triple-isolate',
+ 'flow': 'flows',
+ 'host': 'hosts',
+ }
+
def update(self, config, direction):
tmp = f'tc qdisc add dev {self._interface} root handle 1: cake {direction}'
if 'bandwidth' in config:
@@ -30,26 +45,16 @@ class CAKE(QoSBase):
tmp += f' rtt {rtt}ms'
if 'flow_isolation' in config:
- if 'blind' in config['flow_isolation']:
- tmp += f' flowblind'
- if 'dst_host' in config['flow_isolation']:
- tmp += f' dsthost'
- if 'dual_dst_host' in config['flow_isolation']:
- tmp += f' dual-dsthost'
- if 'dual_src_host' in config['flow_isolation']:
- tmp += f' dual-srchost'
- if 'triple_isolate' in config['flow_isolation']:
- tmp += f' triple-isolate'
- if 'flow' in config['flow_isolation']:
- tmp += f' flows'
- if 'host' in config['flow_isolation']:
- tmp += f' hosts'
- if 'nat' in config['flow_isolation']:
- tmp += f' nat'
- if 'src_host' in config['flow_isolation']:
- tmp += f' srchost '
- else:
- tmp += f' nonat'
+ isolation_value = self.flow_isolation_map.get(config['flow_isolation'])
+
+ if isolation_value is not None:
+ tmp += f' {isolation_value}'
+ else:
+ raise ValueError(
+ f'Invalid flow isolation parameter: {config["flow_isolation"]}'
+ )
+
+ tmp += ' nat' if 'flow_isolation_nat' in config else ' nonat'
self._cmd(tmp)
diff --git a/python/vyos/qos/trafficshaper.py b/python/vyos/qos/trafficshaper.py
index 8b0333c21..9f92ccd8b 100644
--- a/python/vyos/qos/trafficshaper.py
+++ b/python/vyos/qos/trafficshaper.py
@@ -126,91 +126,71 @@ class TrafficShaper(QoSBase):
# call base class
super().update(config, direction)
+
class TrafficShaperHFSC(QoSBase):
+ """
+ Traffic shaper using Hierarchical Fair Service Curve (HFSC).
+ Documentation: https://man7.org/linux/man-pages/man8/tc-hfsc.8.html
+ """
+
_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]
+ criteria = ['linkshare', 'realtime', 'upperlimit']
+ short_criterion = {
+ 'linkshare': 'ls',
+ 'realtime': 'rt',
+ 'upperlimit': 'ul',
+ }
+
+ def _gen_class(self, cls: int, cls_config: dict):
+ """
+ Generate HFSC class and add Stochastic Fair Queueing (SFQ) qdisc.
+
+ Args:
+ cls (int): Class ID
+ cls_config (dict): Configuration for the class
+ """
+ tmp = f'tc class replace dev {self._interface} parent {self._parent:x}:1 classid {self._parent:x}:{cls:x} hfsc'
+
+ for crit in self.criteria:
+ param = cls_config.get(crit)
+ if param:
+ tmp += (
+ f' {self.short_criterion[crit]}'
+ f' m1 {self._rate_convert(param["m1"]) if param.get("m1") else 0}'
+ f' d {param.get("d", 0)}ms'
+ f' m2 {self._rate_convert(param["m2"])}'
+ )
- r2q = 10
- # bandwidth is a mandatory CLI node
- speed = self._rate_convert(config['bandwidth'])
- speed_bps = int(speed) // 8
+ self._cmd(tmp)
- # 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
+ tmp = f'tc qdisc replace dev {self._interface} parent {self._parent:x}:{cls:x} sfq perturb 10'
+ self._cmd(tmp)
- while (r2q > 1) and (min_speed // r2q) < MINQUANTUM:
- tmp = r2q -1
- if (speed_bps // tmp) >= MAXQUANTUM:
- break
- r2q = tmp
+ def update(self, config, direction):
+ class_id_max = self._get_class_max_id(config)
+ default_cls_id = int(class_id_max) + 1 if class_id_max else 2
- 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
+ speed = self._rate_convert(config['bandwidth'])
+
+ tmp = f'tc qdisc replace dev {self._interface} root handle {self._parent:x}: hfsc default {default_cls_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)
+ # tmp = f'tc qdisc add dev {self._interface} parent {self._parent:x}:1 handle f1: sfq perturb 10'
+ # 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)
+ self._gen_class(cls=int(cls), cls_config=cls_config)
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)
+ self._gen_class(
+ cls=int(default_cls_id), cls_config=config.get('default', {})
+ )
# call base class
super().update(config, direction)
diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py
index dd4266f57..2f587405d 100644
--- a/python/vyos/utils/convert.py
+++ b/python/vyos/utils/convert.py
@@ -235,3 +235,29 @@ def convert_data(data) -> dict | list | tuple | str | int | float | bool | None:
# which cannot be converted to JSON
# for example: complex | range | memoryview
return
+
+
+def encode_to_base64(input_string):
+ """
+ Encodes a given string to its base64 representation.
+
+ Args:
+ input_string (str): The string to be encoded.
+
+ Returns:
+ str: The base64-encoded version of the input string.
+
+ Example:
+ input_string = "Hello, World!"
+ encoded_string = encode_to_base64(input_string)
+ print(encoded_string) # Output: SGVsbG8sIFdvcmxkIQ==
+ """
+ import base64
+ # Convert the string to bytes
+ byte_string = input_string.encode('utf-8')
+
+ # Encode the byte string to base64
+ encoded_string = base64.b64encode(byte_string)
+
+ # Decode the base64 bytes back to a string
+ return encoded_string.decode('utf-8')