summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/templates/high-availability/keepalived.conf.j26
-rw-r--r--data/templates/login/default_motd.j214
-rw-r--r--data/templates/vyos-hostsd/hosts.j21
-rw-r--r--data/vyos-firewall-init.conf2
-rw-r--r--interface-definitions/high-availability.xml.in1
-rw-r--r--python/vyos/ethtool.py2
-rw-r--r--python/vyos/firewall.py18
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py23
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py24
-rwxr-xr-xsmoketest/scripts/cli/test_high-availability_vrrp.py27
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py13
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py13
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py11
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py12
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py23
-rwxr-xr-xsrc/conf_mode/high-availability.py10
-rwxr-xr-xsrc/conf_mode/system_conntrack.py4
-rwxr-xr-xsrc/conf_mode/system_login_banner.py22
-rwxr-xr-xsrc/conf_mode/vrf.py18
-rwxr-xr-xsrc/op_mode/container.py2
21 files changed, 154 insertions, 95 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index b0586e0bb..6ab36005b 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,6 +1,7 @@
{
"system_conntrack": {
- "conntrack_sync": ["service_conntrack-sync"]
+ "conntrack_sync": ["service_conntrack-sync"],
+ "vrf": ["vrf"]
},
"firewall": {
"conntrack": ["system_conntrack"],
diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2
index d54f575b5..f34ce64e2 100644
--- a/data/templates/high-availability/keepalived.conf.j2
+++ b/data/templates/high-availability/keepalived.conf.j2
@@ -82,7 +82,11 @@ vrrp_instance {{ name }} {
nopreempt
{% endif %}
{% if group_config.peer_address is vyos_defined %}
- unicast_peer { {{ group_config.peer_address }} }
+ unicast_peer {
+{% for peer_address in group_config.peer_address %}
+ {{ peer_address }}
+{% endfor %}
+ }
{% endif %}
{% if group_config.hello_source_address is vyos_defined %}
{% if group_config.peer_address is vyos_defined %}
diff --git a/data/templates/login/default_motd.j2 b/data/templates/login/default_motd.j2
new file mode 100644
index 000000000..8584d261a
--- /dev/null
+++ b/data/templates/login/default_motd.j2
@@ -0,0 +1,14 @@
+Welcome to VyOS!
+
+ ┌── ┐
+ . VyOS {{ version_data.version }}
+ └ ──┘ {{ version_data.release_train }}
+
+ * Documentation: https://docs.vyos.io/en/{{ version_data.release_train | replace('current', 'latest') }}
+ * Project news: https://blog.vyos.io
+ * Bug reports: https://vyos.dev
+
+You can change this banner using "set system login banner post-login" command.
+
+VyOS is a free software distribution that includes multiple components,
+you can check individual component licenses under /usr/share/doc/*/copyright
diff --git a/data/templates/vyos-hostsd/hosts.j2 b/data/templates/vyos-hostsd/hosts.j2
index 5cad983b4..71fa335da 100644
--- a/data/templates/vyos-hostsd/hosts.j2
+++ b/data/templates/vyos-hostsd/hosts.j2
@@ -4,7 +4,6 @@
# Local host
127.0.0.1 localhost
-127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 5a4e03015..3929edf0b 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -65,11 +65,9 @@ table inet vrf_zones {
# Chain for inbound traffic
chain vrf_zones_ct_in {
type filter hook prerouting priority raw; policy accept;
- counter ct original zone set iifname map @ct_iface_map
}
# Chain for locally-generated traffic
chain vrf_zones_ct_out {
type filter hook output priority raw; policy accept;
- counter ct original zone set oifname map @ct_iface_map
}
}
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 59f0f1052..aef57f8ae 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -195,6 +195,7 @@
<constraint>
<validator name="ip-address"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
<leafNode name="no-preempt">
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index ba638b280..f20fa452e 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -150,7 +150,7 @@ class Ethtool:
self._eee = True
# read current EEE setting, this returns:
# EEE status: disabled || EEE status: enabled - inactive || EEE status: enabled - active
- self._eee_enabled = bool('enabled' in out.splitlines()[2])
+ self._eee_enabled = bool('enabled' in out.splitlines()[1])
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index eee11bd2d..49e095946 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -34,6 +34,24 @@ from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
+# Conntrack
+
+def conntrack_required(conf):
+ required_nodes = ['nat', 'nat66', 'load-balancing wan']
+
+ for path in required_nodes:
+ if conf.exists(path):
+ return True
+
+ firewall = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True, get_first_key=True)
+
+ for rules, path in dict_search_recursive(firewall, 'rule'):
+ if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
+ return True
+
+ return False
+
# Domain Resolver
def fqdn_config_parse(firewall):
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index 140642806..c49d3e76c 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -102,6 +102,29 @@ class VyOSUnitTestSHIM:
ssh_client.close()
return output, error
+ # Verify nftables output
+ def verify_nftables(self, nftables_search, table, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
+ def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
# standard construction; typing suggestion: https://stackoverflow.com/a/70292317
def ignore_warning(warning: Type[Warning]):
import warnings
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index bc2848492..be5960bbd 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -22,7 +22,6 @@ from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
from vyos.utils.process import run
sysfs_config = {
@@ -67,28 +66,6 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True)
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
- def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
# Resolver no longer blocks commit, need to wait for daemon to populate set
count = 0
@@ -808,7 +785,6 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ct state related', 'accept']
]
- nftables_output = cmd('sudo nft list table ip vyos_filter')
self.verify_nftables(nftables_search, 'ip vyos_filter')
self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')
diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py
index 98259d830..1bb35e422 100755
--- a/smoketest/scripts/cli/test_high-availability_vrrp.py
+++ b/smoketest/scripts/cli/test_high-availability_vrrp.py
@@ -237,5 +237,32 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'track_interface', config)
self.assertIn(f' {none_vrrp_interface}', config)
+ def test_05_set_multiple_peer_address(self):
+ group = 'VyOS-WAN'
+ vlan_id = '24'
+ vip = '100.64.24.1/24'
+ peer_address_1 = '192.0.2.1'
+ peer_address_2 = '192.0.2.2'
+ vrid = '150'
+ group_base = base_path + ['vrrp', 'group', group]
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24'])
+ self.cli_set(group_base + ['interface', vrrp_interface])
+ self.cli_set(group_base + ['address', vip])
+ self.cli_set(group_base + ['peer-address', peer_address_1])
+ self.cli_set(group_base + ['peer-address', peer_address_2])
+ self.cli_set(group_base + ['vrid', vrid])
+
+ # commit changes
+ self.cli_commit()
+
+ config = getConfig(f'vrrp_instance {group}')
+
+ self.assertIn(f'interface {vrrp_interface}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'unicast_peer', config)
+ self.assertIn(f' {peer_address_1}', config)
+ self.assertIn(f' {peer_address_2}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 1e6435df8..4f1c3cb4f 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -21,8 +21,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
-from vyos.utils.dict import dict_search
base_path = ['nat']
src_path = base_path + ['source']
@@ -47,17 +45,6 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.assertFalse(os.path.exists(nftables_nat_config))
self.assertFalse(os.path.exists(nftables_static_nat_conf))
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
# Resolver no longer blocks commit, need to wait for daemon to populate set
count = 0
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 0607f6616..400a895ff 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -22,8 +22,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
-from vyos.utils.dict import dict_search
base_path = ['nat66']
src_path = base_path + ['source']
@@ -42,17 +40,6 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def test_source_nat66(self):
source_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index c0b7c1fe7..462fc24d0 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -68,17 +68,6 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.verify_rules(ip_rule_search, inverse=True)
- def verify_nftables(self, nftables_search, table, inverse=False):
- nftables_output = cmd(f'sudo nft list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def verify_rules(self, rules_search, inverse=False):
rule_output = cmd('ip rule show')
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index ce237a6e7..f00626b3d 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -21,7 +21,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.firewall import find_nftables_rule
-from vyos.utils.process import cmd
from vyos.utils.file import read_file
base_path = ['system', 'conntrack']
@@ -43,17 +42,6 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def test_conntrack_options(self):
conntrack_config = {
'net.netfilter.nf_conntrack_expect_max' : {
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 438387f2d..c96b8e374 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -529,5 +529,28 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(f' no ip nht resolve-via-default', frrconfig)
self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig)
+ def test_vrf_conntrack(self):
+ table = '1000'
+ nftables_rules = {
+ 'vrf_zones_ct_in': ['ct original zone set iifname map @ct_iface_map'],
+ 'vrf_zones_ct_out': ['ct original zone set oifname map @ct_iface_map']
+ }
+
+ self.cli_set(base_path + ['name', 'blue', 'table', table])
+ self.cli_commit()
+
+ # Conntrack rules should not be present
+ for chain, rule in nftables_rules.items():
+ self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=True)
+
+ self.cli_set(['nat'])
+ self.cli_commit()
+
+ # Conntrack rules should now be present
+ for chain, rule in nftables_rules.items():
+ self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=False)
+
+ self.cli_delete(['nat'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index b3b27b14e..59d49ea67 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -125,8 +125,9 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!')
if 'peer_address' in group_config:
- if is_ipv6(group_config['peer_address']):
- raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
+ for peer_address in group_config['peer_address']:
+ if is_ipv6(peer_address):
+ raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
if vaddrs6:
tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'}
@@ -139,8 +140,9 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!')
if 'peer_address' in group_config:
- if is_ipv4(group_config['peer_address']):
- raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
+ for peer_address in group_config['peer_address']:
+ if is_ipv4(peer_address):
+ raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
# Check sync groups
if 'vrrp' in ha and 'sync_group' in ha['vrrp']:
for sync_group, sync_config in ha['vrrp']['sync_group'].items():
diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py
index 7f6c71440..e075bc928 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -104,6 +104,10 @@ def get_config(config=None):
if conf.exists(['service', 'conntrack-sync']):
set_dependents('conntrack_sync', conf)
+ # If conntrack status changes, VRF zone rules need updating
+ if conf.exists(['vrf']):
+ set_dependents('vrf', conf)
+
return conntrack
def verify(conntrack):
diff --git a/src/conf_mode/system_login_banner.py b/src/conf_mode/system_login_banner.py
index 65fa04417..923e1bf57 100755
--- a/src/conf_mode/system_login_banner.py
+++ b/src/conf_mode/system_login_banner.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 VyOS maintainers and contributors
+# Copyright (C) 2020-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,30 +18,26 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
+from vyos.template import render
from vyos.utils.file import write_file
+from vyos.version import get_version_data
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-try:
- with open('/usr/share/vyos/default_motd') as f:
- motd = f.read()
-except:
- # Use an empty banner if the default banner file cannot be read
- motd = "\n"
-
PRELOGIN_FILE = r'/etc/issue'
PRELOGIN_NET_FILE = r'/etc/issue.net'
POSTLOGIN_FILE = r'/etc/motd'
default_config_data = {
'issue': 'Welcome to VyOS - \\n \\l\n\n',
- 'issue_net': '',
- 'motd': motd
+ 'issue_net': ''
}
def get_config(config=None):
banner = deepcopy(default_config_data)
+ banner['version_data'] = get_version_data()
+
if config:
conf = config
else:
@@ -92,7 +88,11 @@ def generate(banner):
def apply(banner):
write_file(PRELOGIN_FILE, banner['issue'])
write_file(PRELOGIN_NET_FILE, banner['issue_net'])
- write_file(POSTLOGIN_FILE, banner['motd'])
+ if 'motd' in banner:
+ write_file(POSTLOGIN_FILE, banner['motd'])
+ else:
+ render(POSTLOGIN_FILE, 'login/default_motd.j2', banner,
+ permission=0o644, user='root', group='root')
return None
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index a2f4956be..16908100f 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -23,6 +23,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
+from vyos.firewall import conntrack_required
from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import render_to_string
@@ -41,6 +42,12 @@ airbag.enable()
config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf'
k_mod = ['vrf']
+nftables_table = 'inet vrf_zones'
+nftables_rules = {
+ 'vrf_zones_ct_in': 'counter ct original zone set iifname map @ct_iface_map',
+ 'vrf_zones_ct_out': 'counter ct original zone set oifname map @ct_iface_map'
+}
+
def has_rule(af : str, priority : int, table : str=None):
"""
Check if a given ip rule exists
@@ -114,6 +121,9 @@ def get_config(config=None):
routes = vrf_routing(conf, name)
if routes: vrf['vrf_remove'][name]['route'] = routes
+ if 'name' in vrf:
+ vrf['conntrack'] = conntrack_required(conf)
+
# We also need the route-map information from the config
#
# XXX: one MUST always call this without the key_mangling() option! See
@@ -294,6 +304,14 @@ def apply(vrf):
nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'
cmd(f'nft {nft_add_element}')
+ if vrf['conntrack']:
+ for chain, rule in nftables_rules.items():
+ cmd(f'nft add rule inet vrf_zones {chain} {rule}')
+
+ if 'name' not in vrf or not vrf['conntrack']:
+ for chain, rule in nftables_rules.items():
+ cmd(f'nft flush chain inet vrf_zones {chain}')
+
# Apply FRR filters
zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index dcbb4dc55..d29af8821 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -75,7 +75,7 @@ def delete_image(name: str):
if not name: return
# replace newline with whitespace
name = name.replace('\n', ' ')
- rc, output = rc_cmd(f'podman image rm --force {name}')
+ rc, output = rc_cmd(f'podman image rm {name}')
if rc != 0:
raise vyos.opmode.InternalError(output)