summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdict.py6
-rw-r--r--python/vyos/configdiff.py30
-rw-r--r--python/vyos/configsource.py2
-rw-r--r--python/vyos/configtree.py65
-rw-r--r--python/vyos/firewall.py250
-rw-r--r--python/vyos/frr.py22
-rw-r--r--python/vyos/ifconfig/__init__.py1
-rwxr-xr-xpython/vyos/ifconfig/interface.py45
-rw-r--r--python/vyos/remote.py10
-rw-r--r--python/vyos/template.py63
-rw-r--r--python/vyos/util.py13
11 files changed, 450 insertions, 57 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index d974a7565..e7f515ea9 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -196,7 +196,7 @@ def is_member(conf, interface, intftype=None):
interface name -> Interface is a member of this interface
False -> interface type cannot have members
"""
- ret_val = None
+ ret_val = {}
intftypes = ['bonding', 'bridge']
if intftype not in intftypes + [None]:
@@ -216,8 +216,8 @@ def is_member(conf, interface, intftype=None):
member = base + [intf, 'member', 'interface', interface]
if conf.exists(member):
tmp = conf.get_config_dict(member, key_mangling=('-', '_'),
- get_first_key=True)
- ret_val = {intf : tmp}
+ get_first_key=True, no_tag_node_value_mangle=True)
+ ret_val.update({intf : tmp})
old_level = conf.set_level(old_level)
return ret_val
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index 0e41fbe27..4ad7443d7 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -17,7 +17,9 @@ from enum import IntFlag, auto
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configdict import list_diff
from vyos.util import get_sub_dict, mangle_dict_keys
+from vyos.util import dict_search_args
from vyos.xml import defaults
class ConfigDiffError(Exception):
@@ -134,6 +136,34 @@ class ConfigDiff(object):
self._key_mangling[1])
return config_dict
+ def get_child_nodes_diff_str(self, path=[]):
+ ret = {'add': {}, 'change': {}, 'delete': {}}
+
+ diff = self.get_child_nodes_diff(path,
+ expand_nodes=Diff.ADD | Diff.DELETE | Diff.MERGE | Diff.STABLE,
+ no_defaults=True)
+
+ def parse_dict(diff_dict, diff_type, prefix=[]):
+ for k, v in diff_dict.items():
+ if isinstance(v, dict):
+ parse_dict(v, diff_type, prefix + [k])
+ else:
+ path_str = ' '.join(prefix + [k])
+ if diff_type == 'add' or diff_type == 'delete':
+ if isinstance(v, list):
+ v = ', '.join(v)
+ ret[diff_type][path_str] = v
+ elif diff_type == 'merge':
+ old_value = dict_search_args(diff['stable'], *prefix, k)
+ if old_value and old_value != v:
+ ret['change'][path_str] = [old_value, v]
+
+ parse_dict(diff['merge'], 'merge')
+ parse_dict(diff['add'], 'add')
+ parse_dict(diff['delete'], 'delete')
+
+ return ret
+
def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
"""
Args:
diff --git a/python/vyos/configsource.py b/python/vyos/configsource.py
index a0f6a46b5..510b5b65a 100644
--- a/python/vyos/configsource.py
+++ b/python/vyos/configsource.py
@@ -162,7 +162,7 @@ class ConfigSourceSession(ConfigSource):
if p.returncode != 0:
raise VyOSError()
else:
- return out.decode('ascii', 'ignore')
+ return out.decode()
def set_level(self, path):
"""
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index d8ffaca99..866f24e47 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -15,8 +15,9 @@
import re
import json
-from ctypes import cdll, c_char_p, c_void_p, c_int
+from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER
+LIBPATH = '/usr/lib/libvyosconfig.so.0'
def escape_backslash(string: str) -> str:
"""Escape single backslashes in string that are not in escape sequence"""
@@ -42,7 +43,9 @@ class ConfigTreeError(Exception):
class ConfigTree(object):
- def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'):
+ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
+ if config_string is None and address is None:
+ raise TypeError("ConfigTree() requires one of 'config_string' or 'address'")
self.__config = None
self.__lib = cdll.LoadLibrary(libpath)
@@ -60,7 +63,7 @@ class ConfigTree(object):
self.__to_string.restype = c_char_p
self.__to_commands = self.__lib.to_commands
- self.__to_commands.argtypes = [c_void_p]
+ self.__to_commands.argtypes = [c_void_p, c_char_p]
self.__to_commands.restype = c_char_p
self.__to_json = self.__lib.to_json
@@ -126,15 +129,19 @@ class ConfigTree(object):
self.__destroy = self.__lib.destroy
self.__destroy.argtypes = [c_void_p]
- config_section, version_section = extract_version(config_string)
- config_section = escape_backslash(config_section)
- config = self.__from_string(config_section.encode())
- if config is None:
- msg = self.__get_error().decode()
- raise ValueError("Failed to parse config: {0}".format(msg))
+ if address is None:
+ config_section, version_section = extract_version(config_string)
+ config_section = escape_backslash(config_section)
+ config = self.__from_string(config_section.encode())
+ if config is None:
+ msg = self.__get_error().decode()
+ raise ValueError("Failed to parse config: {0}".format(msg))
+ else:
+ self.__config = config
+ self.__version = version_section
else:
- self.__config = config
- self.__version = version_section
+ self.__config = address
+ self.__version = ''
def __del__(self):
if self.__config is not None:
@@ -143,13 +150,16 @@ class ConfigTree(object):
def __str__(self):
return self.to_string()
+ def _get_config(self):
+ return self.__config
+
def to_string(self):
config_string = self.__to_string(self.__config).decode()
config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
- def to_commands(self):
- return self.__to_commands(self.__config).decode()
+ def to_commands(self, op="set"):
+ return self.__to_commands(self.__config, op.encode()).decode()
def to_json(self):
return self.__to_json(self.__config).decode()
@@ -281,3 +291,32 @@ class ConfigTree(object):
else:
raise ConfigTreeError("Path [{}] doesn't exist".format(path_str))
+class Diff:
+ def __init__(self, left, right, path=[], libpath=LIBPATH):
+ if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)):
+ raise TypeError("Arguments must be instances of ConfigTree")
+ if path:
+ if not left.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree")
+ if not right.exists(path):
+ raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree")
+ self.left = left
+ self.right = right
+
+ check_path(path)
+ path_str = " ".join(map(str, path)).encode()
+ df = cdll.LoadLibrary(libpath).diffs
+ df.restype = POINTER(c_void_p * 3)
+ res = list(df(path_str, left._get_config(), right._get_config()).contents)
+ self._diff = {'add': ConfigTree(address=res[0]),
+ 'del': ConfigTree(address=res[1]),
+ 'int': ConfigTree(address=res[2]) }
+
+ self.add = self._diff['add']
+ self.delete = self._diff['del']
+ self.inter = self._diff['int']
+
+ def to_commands(self):
+ add = self.add.to_commands()
+ delete = self.delete.to_commands(op="delete")
+ return delete + "\n" + add
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
new file mode 100644
index 000000000..55ce318e7
--- /dev/null
+++ b/python/vyos/firewall.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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
+# published by the Free Software Foundation.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+import re
+
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def find_nftables_rule(table, chain, rule_matches=[]):
+ # Find rule in table/chain that matches all criteria and return the handle
+ results = cmd(f'sudo nft -a list chain {table} {chain}').split("\n")
+ for line in results:
+ if all(rule_match in line for rule_match in rule_matches):
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ return handle_search[1]
+ return None
+
+def remove_nftables_rule(table, chain, handle):
+ cmd(f'sudo nft delete rule {table} {chain} handle {handle}')
+
+# Functions below used by template generation
+
+def nft_action(vyos_action):
+ if vyos_action == 'accept':
+ return 'return'
+ return vyos_action
+
+def parse_rule(rule_conf, fw_name, rule_id, ip_name):
+ output = []
+ def_suffix = '6' if ip_name == 'ip6' else ''
+
+ if 'state' in rule_conf and rule_conf['state']:
+ states = ",".join([s for s, v in rule_conf['state'].items() if v == 'enable'])
+
+ if states:
+ output.append(f'ct state {{{states}}}')
+
+ if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
+ proto = rule_conf['protocol']
+ operator = ''
+ if proto[0] == '!':
+ operator = '!='
+ proto = proto[1:]
+ if proto == 'tcp_udp':
+ proto = '{tcp, udp}'
+ output.append(f'meta l4proto {operator} {proto}')
+
+ for side in ['destination', 'source']:
+ if side in rule_conf:
+ prefix = side[0]
+ side_conf = rule_conf[side]
+
+ if 'address' in side_conf:
+ suffix = side_conf['address']
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'{ip_name} {prefix}addr {suffix}')
+
+ if 'mac_address' in side_conf:
+ suffix = side_conf["mac_address"]
+ if suffix[0] == '!':
+ suffix = f'!= {suffix[1:]}'
+ output.append(f'ether {prefix}addr {suffix}')
+
+ if 'port' in side_conf:
+ proto = rule_conf['protocol']
+ port = side_conf['port'].split(',')
+
+ ports = []
+ negated_ports = []
+
+ for p in port:
+ if p[0] == '!':
+ negated_ports.append(p[1:])
+ else:
+ ports.append(p)
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ if ports:
+ ports_str = ','.join(ports)
+ output.append(f'{proto} {prefix}port {{{ports_str}}}')
+
+ if negated_ports:
+ negated_ports_str = ','.join(negated_ports)
+ output.append(f'{proto} {prefix}port != {{{negated_ports_str}}}')
+
+ if 'group' in side_conf:
+ group = side_conf['group']
+ if 'address_group' in group:
+ group_name = group['address_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $A{def_suffix}_{group_name}')
+ elif 'network_group' in group:
+ group_name = group['network_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'{ip_name} {prefix}addr {operator} $N{def_suffix}_{group_name}')
+ if 'mac_group' in group:
+ group_name = group['mac_group']
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+ output.append(f'ether {prefix}addr {operator} $M_{group_name}')
+ if 'port_group' in group:
+ proto = rule_conf['protocol']
+ group_name = group['port_group']
+
+ if proto == 'tcp_udp':
+ proto = 'th'
+
+ operator = ''
+ if group_name[0] == '!':
+ operator = '!='
+ group_name = group_name[1:]
+
+ output.append(f'{proto} {prefix}port {operator} $P_{group_name}')
+
+ if 'log' in rule_conf and rule_conf['log'] == 'enable':
+ action = rule_conf['action'] if 'action' in rule_conf else 'accept'
+ output.append(f'log prefix "[{fw_name[:19]}-{rule_id}-{action[:1].upper()}] "')
+
+ if 'hop_limit' in rule_conf:
+ operators = {'eq': '==', 'gt': '>', 'lt': '<'}
+ for op, operator in operators.items():
+ if op in rule_conf['hop_limit']:
+ value = rule_conf['hop_limit'][op]
+ output.append(f'ip6 hoplimit {operator} {value}')
+
+ for icmp in ['icmp', 'icmpv6']:
+ if icmp in rule_conf:
+ if 'type_name' in rule_conf[icmp]:
+ output.append(icmp + ' type ' + rule_conf[icmp]['type_name'])
+ else:
+ if 'code' in rule_conf[icmp]:
+ output.append(icmp + ' code ' + rule_conf[icmp]['code'])
+ if 'type' in rule_conf[icmp]:
+ output.append(icmp + ' type ' + rule_conf[icmp]['type'])
+
+ if 'ipsec' in rule_conf:
+ if 'match_ipsec' in rule_conf['ipsec']:
+ output.append('meta ipsec == 1')
+ if 'match_non_ipsec' in rule_conf['ipsec']:
+ output.append('meta ipsec == 0')
+
+ if 'fragment' in rule_conf:
+ # Checking for fragmentation after priority -400 is not possible,
+ # so we use a priority -450 hook to set a mark
+ if 'match_frag' in rule_conf['fragment']:
+ output.append('meta mark 0xffff1')
+ if 'match_non_frag' in rule_conf['fragment']:
+ output.append('meta mark != 0xffff1')
+
+ if 'limit' in rule_conf:
+ if 'rate' in rule_conf['limit']:
+ output.append(f'limit rate {rule_conf["limit"]["rate"]}/second')
+ if 'burst' in rule_conf['limit']:
+ output.append(f'burst {rule_conf["limit"]["burst"]} packets')
+
+ if 'recent' in rule_conf:
+ count = rule_conf['recent']['count']
+ time = rule_conf['recent']['time']
+ output.append(f'add @RECENT{def_suffix}_{fw_name}_{rule_id} {{ {ip_name} saddr limit rate over {count}/{time} burst {count} packets }}')
+
+ if 'time' in rule_conf:
+ output.append(parse_time(rule_conf['time']))
+
+ tcp_flags = dict_search_args(rule_conf, 'tcp', 'flags')
+ if tcp_flags:
+ output.append(parse_tcp_flags(tcp_flags))
+
+ output.append('counter')
+
+ if 'set' in rule_conf:
+ output.append(parse_policy_set(rule_conf['set'], def_suffix))
+
+ if 'action' in rule_conf:
+ output.append(nft_action(rule_conf['action']))
+ else:
+ output.append('return')
+
+ output.append(f'comment "{fw_name}-{rule_id}"')
+ return " ".join(output)
+
+def parse_tcp_flags(flags):
+ include = [flag for flag in flags if flag != 'not']
+ exclude = list(flags['not']) if 'not' in flags else []
+ return f'tcp flags & ({"|".join(include + exclude)}) == {"|".join(include) if include else "0x0"}'
+
+def parse_time(time):
+ out = []
+ if 'startdate' in time:
+ start = time['startdate']
+ if 'T' not in start and 'starttime' in time:
+ start += f' {time["starttime"]}'
+ out.append(f'time >= "{start}"')
+ if 'starttime' in time and 'startdate' not in time:
+ out.append(f'hour >= "{time["starttime"]}"')
+ if 'stopdate' in time:
+ stop = time['stopdate']
+ if 'T' not in stop and 'stoptime' in time:
+ stop += f' {time["stoptime"]}'
+ out.append(f'time < "{stop}"')
+ if 'stoptime' in time and 'stopdate' not in time:
+ out.append(f'hour < "{time["stoptime"]}"')
+ if 'weekdays' in time:
+ days = time['weekdays'].split(",")
+ out_days = [f'"{day}"' for day in days if day[0] != '!']
+ out.append(f'day {{{",".join(out_days)}}}')
+ return " ".join(out)
+
+def parse_policy_set(set_conf, def_suffix):
+ out = []
+ if 'dscp' in set_conf:
+ dscp = set_conf['dscp']
+ out.append(f'ip{def_suffix} dscp set {dscp}')
+ if 'mark' in set_conf:
+ mark = set_conf['mark']
+ out.append(f'meta mark set {mark}')
+ if 'table' in set_conf:
+ table = set_conf['table']
+ if table == 'main':
+ table = '254'
+ mark = 0x7FFFFFFF - int(table)
+ out.append(f'meta mark set {mark}')
+ if 'tcp_mss' in set_conf:
+ mss = set_conf['tcp_mss']
+ out.append(f'tcp option maxseg size set {mss}')
+ return " ".join(out)
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index a8f115d9a..cbba19ab7 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -73,15 +73,15 @@ from vyos.util import cmd
import logging
from logging.handlers import SysLogHandler
import os
+import sys
+
LOG = logging.getLogger(__name__)
+DEBUG = False
-DEBUG = os.path.exists('/tmp/vyos.frr.debug')
-if DEBUG:
- LOG.setLevel(logging.DEBUG)
- ch = SysLogHandler(address='/dev/log')
- ch2 = logging.StreamHandler()
- LOG.addHandler(ch)
- LOG.addHandler(ch2)
+ch = SysLogHandler(address='/dev/log')
+ch2 = logging.StreamHandler(stream=sys.stdout)
+LOG.addHandler(ch)
+LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'bgpd', 'fabricd', 'isisd', 'ospf6d', 'ospfd', 'pbrd',
'pimd', 'ripd', 'ripngd', 'sharpd', 'staticd', 'vrrpd', 'ldpd',
@@ -121,6 +121,12 @@ class ConfigSectionNotFound(FrrError):
"""
pass
+def init_debugging():
+ global DEBUG
+
+ DEBUG = os.path.exists('/tmp/vyos.frr.debug')
+ if DEBUG:
+ LOG.setLevel(logging.DEBUG)
def get_configuration(daemon=None, marked=False):
""" Get current running FRR configuration
@@ -424,6 +430,8 @@ class FRRConfig:
Using this overwrites the current loaded config objects and replaces the original loaded config
'''
+ init_debugging()
+
self.imported_config = get_configuration(daemon=daemon)
if daemon:
LOG.debug(f'load_configuration: Configuration loaded from FRR daemon {daemon}')
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 2d3e406ac..a37615c8f 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -26,6 +26,7 @@ from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig.geneve import GeneveIf
from vyos.ifconfig.loopback import LoopbackIf
from vyos.ifconfig.macvlan import MACVLANIf
+from vyos.ifconfig.input import InputIf
from vyos.ifconfig.vxlan import VXLANIf
from vyos.ifconfig.wireguard import WireGuardIf
from vyos.ifconfig.vtun import VTunIf
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 5fdd27828..91c7f0c33 100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -577,6 +577,15 @@ class Interface(Control):
return None
return self.set_interface('arp_cache_tmo', tmo)
+ def _cleanup_mss_rules(self, table, ifname):
+ commands = []
+ results = self._cmd(f'nft -a list chain {table} VYOS_TCP_MSS').split("\n")
+ for line in results:
+ if f'oifname "{ifname}"' in line:
+ handle_search = re.search('handle (\d+)', line)
+ if handle_search:
+ self._cmd(f'nft delete rule {table} VYOS_TCP_MSS handle {handle_search[1]}')
+
def set_tcp_ipv4_mss(self, mss):
"""
Set IPv4 TCP MSS value advertised when TCP SYN packets leave this
@@ -588,22 +597,14 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_ipv4_mss(1340)
"""
- iptables_bin = 'iptables'
- base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = self._cmd(f'{iptables_bin}-save -t mangle')
- for line in out.splitlines():
- if line.startswith(base_options):
- # remove OLD MSS mangling configuration
- line = line.replace('-A FORWARD', '-D FORWARD')
- self._cmd(f'{iptables_bin} -t mangle {line}')
-
- cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ self._cleanup_mss_rules('raw', self.ifname)
+ nft_prefix = 'nft add rule raw VYOS_TCP_MSS'
+ base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
if mss == 'clamp-mss-to-pmtu':
- self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'")
elif int(mss) > 0:
- # probably add option to clamp only if bigger:
low_mss = str(int(mss) + 1)
- self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'")
def set_tcp_ipv6_mss(self, mss):
"""
@@ -616,22 +617,14 @@ class Interface(Control):
>>> from vyos.ifconfig import Interface
>>> Interface('eth0').set_tcp_mss(1320)
"""
- iptables_bin = 'ip6tables'
- base_options = f'-A FORWARD -o {self.ifname} -p tcp -m tcp --tcp-flags SYN,RST SYN'
- out = self._cmd(f'{iptables_bin}-save -t mangle')
- for line in out.splitlines():
- if line.startswith(base_options):
- # remove OLD MSS mangling configuration
- line = line.replace('-A FORWARD', '-D FORWARD')
- self._cmd(f'{iptables_bin} -t mangle {line}')
-
- cmd_mss = f'{iptables_bin} -t mangle {base_options} --jump TCPMSS'
+ self._cleanup_mss_rules('ip6 raw', self.ifname)
+ nft_prefix = 'nft add rule ip6 raw VYOS_TCP_MSS'
+ base_cmd = f'oifname "{self.ifname}" tcp flags & (syn|rst) == syn'
if mss == 'clamp-mss-to-pmtu':
- self._cmd(f'{cmd_mss} --clamp-mss-to-pmtu')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size set rt mtu'")
elif int(mss) > 0:
- # probably add option to clamp only if bigger:
low_mss = str(int(mss) + 1)
- self._cmd(f'{cmd_mss} -m tcpmss --mss {low_mss}:65535 --set-mss {mss}')
+ self._cmd(f"{nft_prefix} '{base_cmd} tcp option maxseg size {low_mss}-65535 tcp option maxseg size set {mss}'")
def set_arp_filter(self, arp_filter):
"""
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index aa62ac60d..66044fa52 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -83,8 +83,7 @@ def check_storage(path, size):
directory = path if os.path.isdir(path) else (os.path.dirname(os.path.expanduser(path)) or os.getcwd())
# `size` can be None or 0 to indicate unknown size.
if not size:
- print_error('Warning: Cannot determine size of remote file.')
- print_error('Bravely continuing regardless.')
+ print_error('Warning: Cannot determine size of remote file. Bravely continuing regardless.')
return
if size < 1024 * 1024:
@@ -227,7 +226,7 @@ class HttpC:
r.raise_for_status()
# If the request got redirected, keep the last URL we ended up with.
final_urlstring = r.url
- if r.history:
+ if r.history and self.progressbar:
print_error('Redirecting to ' + final_urlstring)
# Check for the prospective file size.
try:
@@ -317,11 +316,12 @@ def friendly_download(local_path, urlstring, source_host='', source_port=0):
sys.exit(1)
except:
import traceback
+ print_error(f'Failed to download {urlstring}.')
# There are a myriad different reasons a download could fail.
# SSH errors, FTP errors, I/O errors, HTTP errors (403, 404...)
# We omit the scary stack trace but print the error nevertheless.
- print_error(f'Failed to download {urlstring}.')
- traceback.print_exception(*sys.exc_info()[:2], None)
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ traceback.print_exception(exc_type, exc_value, None, 0, None, False)
sys.exit(1)
else:
print_error('Download complete.')
diff --git a/python/vyos/template.py b/python/vyos/template.py
index f694b53e0..dabf53692 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -22,6 +22,7 @@ from jinja2 import FileSystemLoader
from vyos.defaults import directories
from vyos.util import chmod
from vyos.util import chown
+from vyos.util import dict_search_args
from vyos.util import makedir
# Holds template filters registered via register_filter()
@@ -126,6 +127,14 @@ def render(
##################################
# Custom template filters follow #
##################################
+@register_filter('force_to_list')
+def force_to_list(value):
+ """ Convert scalars to single-item lists and leave lists untouched """
+ if isinstance(value, list):
+ return value
+ else:
+ return [value]
+
@register_filter('ip_from_cidr')
def ip_from_cidr(prefix):
""" Take an IPv4/IPv6 CIDR host and strip cidr mask.
@@ -503,3 +512,57 @@ def snmp_auth_oid(type):
'none': '.1.3.6.1.6.3.10.1.2.1'
}
return OIDs[type]
+
+@register_filter('nft_action')
+def nft_action(vyos_action):
+ if vyos_action == 'accept':
+ return 'return'
+ return vyos_action
+
+@register_filter('nft_rule')
+def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
+ from vyos.firewall import parse_rule
+ return parse_rule(rule_conf, fw_name, rule_id, ip_name)
+
+@register_filter('nft_default_rule')
+def nft_default_rule(fw_conf, fw_name):
+ output = ['counter']
+ default_action = fw_conf.get('default_action', 'accept')
+
+ if 'enable_default_log' in fw_conf:
+ action_suffix = default_action[:1].upper()
+ output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}] "')
+
+ output.append(nft_action(default_action))
+ output.append(f'comment "{fw_name} default-action {default_action}"')
+ return " ".join(output)
+
+@register_filter('nft_state_policy')
+def nft_state_policy(conf, state, ipv6=False):
+ out = [f'ct state {state}']
+
+ if 'log' in conf and 'enable' in conf['log']:
+ out.append('log')
+
+ out.append('counter')
+
+ if 'action' in conf:
+ out.append(conf['action'])
+
+ return " ".join(out)
+
+@register_filter('nft_intra_zone_action')
+def nft_intra_zone_action(zone_conf, ipv6=False):
+ if 'intra_zone_filtering' in zone_conf:
+ intra_zone = zone_conf['intra_zone_filtering']
+ fw_name = 'ipv6_name' if ipv6 else 'name'
+ name_prefix = 'NAME6_' if ipv6 else 'NAME_'
+
+ if 'action' in intra_zone:
+ if intra_zone['action'] == 'accept':
+ return 'return'
+ return intra_zone['action']
+ elif dict_search_args(intra_zone, 'firewall', fw_name):
+ name = dict_search_args(intra_zone, 'firewall', fw_name)
+ return f'jump {name_prefix}{name}'
+ return 'return'
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 954c6670d..571d43754 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -952,14 +952,23 @@ def install_into_config(conf, config_paths, override_prompt=True):
return None
count = 0
+ failed = []
for path in config_paths:
if override_prompt and conf.exists(path) and not conf.is_multi(path):
if not ask_yes_no(f'Config node "{node}" already exists. Do you want to overwrite it?'):
continue
- cmd(f'/opt/vyatta/sbin/my_set {path}')
- count += 1
+ try:
+ cmd(f'/opt/vyatta/sbin/my_set {path}')
+ count += 1
+ except:
+ failed.append(path)
+
+ if failed:
+ print(f'Failed to install {len(failed)} value(s). Commands to manually install:')
+ for path in failed:
+ print(f'set {path}')
if count > 0:
print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.')