diff options
39 files changed, 561 insertions, 87 deletions
diff --git a/data/templates/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2 index 7a36fe69d..0daec8fb8 100644 --- a/data/templates/chrony/chrony.conf.j2 +++ b/data/templates/chrony/chrony.conf.j2 @@ -53,8 +53,6 @@ bindaddress {{ address }} {% endfor %} {% endif %} {% if interface is vyos_defined %} -{% for ifname in interface %} -binddevice {{ ifname }} -{% endfor %} +binddevice {{ interface }} {% endif %} {% endif %} diff --git a/data/templates/frr/ospf6d.frr.j2 b/data/templates/frr/ospf6d.frr.j2 index 84394ed1a..b0b5663dd 100644 --- a/data/templates/frr/ospf6d.frr.j2 +++ b/data/templates/frr/ospf6d.frr.j2 @@ -80,6 +80,27 @@ router ospf6 {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if distance.ospfv3 is vyos_defined %} distance ospf6 {{ 'intra-area ' ~ distance.ospfv3.intra_area if distance.ospfv3.intra_area is vyos_defined }} {{ 'inter-area ' ~ distance.ospfv3.inter_area if distance.ospfv3.inter_area is vyos_defined }} {{ 'external ' ~ distance.ospfv3.external if distance.ospfv3.external is vyos_defined }} {% endif %} +{% if graceful_restart is vyos_defined %} +{% if graceful_restart.grace_period is vyos_defined %} + graceful-restart grace-period {{ graceful_restart.grace_period }} +{% endif %} +{% if graceful_restart.helper.enable.router_id is vyos_defined %} +{% for router_id in graceful_restart.helper.enable.router_id %} + graceful-restart helper enable {{ router_id }} +{% endfor %} +{% elif graceful_restart.helper.enable is vyos_defined %} + graceful-restart helper enable +{% endif %} +{% if graceful_restart.helper.planned_only is vyos_defined %} + graceful-restart helper planned-only +{% endif %} +{% if graceful_restart.helper.lsa_check_disable is vyos_defined %} + graceful-restart helper lsa-check-disable +{% endif %} +{% if graceful_restart.helper.supported_grace_time is vyos_defined %} + graceful-restart helper supported-grace-time {{ graceful_restart.helper.supported_grace_time }} +{% endif %} +{% endif %} {% if log_adjacency_changes is vyos_defined %} log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is vyos_defined }} {% endif %} diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2 index 1ee8d8752..040628e82 100644 --- a/data/templates/frr/ospfd.frr.j2 +++ b/data/templates/frr/ospfd.frr.j2 @@ -133,6 +133,9 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if auto_cost.reference_bandwidth is vyos_defined %} auto-cost reference-bandwidth {{ auto_cost.reference_bandwidth }} {% endif %} +{% if capability.opaque is vyos_defined %} + capability opaque +{% endif %} {% if default_information.originate is vyos_defined %} default-information originate {{ 'always' if default_information.originate.always is vyos_defined }} {{ 'metric ' + default_information.originate.metric if default_information.originate.metric is vyos_defined }} {{ 'metric-type ' + default_information.originate.metric_type if default_information.originate.metric_type is vyos_defined }} {{ 'route-map ' + default_information.originate.route_map if default_information.originate.route_map is vyos_defined }} {% endif %} @@ -153,6 +156,27 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% if distance.ospf is vyos_defined %} distance ospf {{ 'intra-area ' + distance.ospf.intra_area if distance.ospf.intra_area is vyos_defined }} {{ 'inter-area ' + distance.ospf.inter_area if distance.ospf.inter_area is vyos_defined }} {{ 'external ' + distance.ospf.external if distance.ospf.external is vyos_defined }} {% endif %} +{% if graceful_restart is vyos_defined %} +{% if graceful_restart.grace_period is vyos_defined %} + graceful-restart grace-period {{ graceful_restart.grace_period }} +{% endif %} +{% if graceful_restart.helper.enable.router_id is vyos_defined %} +{% for router_id in graceful_restart.helper.enable.router_id %} + graceful-restart helper enable {{ router_id }} +{% endfor %} +{% elif graceful_restart.helper.enable is vyos_defined %} + graceful-restart helper enable +{% endif %} +{% if graceful_restart.helper.planned_only is vyos_defined %} + graceful-restart helper planned-only +{% endif %} +{% if graceful_restart.helper.no_strict_lsa_checking is vyos_defined %} + no graceful-restart helper strict-lsa-checking +{% endif %} +{% if graceful_restart.helper.supported_grace_time is vyos_defined %} + graceful-restart helper supported-grace-time {{ graceful_restart.helper.supported_grace_time }} +{% endif %} +{% endif %} {% if log_adjacency_changes is vyos_defined %} log-adjacency-changes {{ "detail" if log_adjacency_changes.detail is vyos_defined }} {% endif %} diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index 6332ed9c2..d144529f3 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -48,6 +48,9 @@ push "redirect-gateway def1" {% if use_lzo_compression is vyos_defined %} compress lzo {% endif %} +{% if offload.dco is not vyos_defined %} +disable-dco +{% endif %} {% if mode is vyos_defined('client') %} # diff --git a/debian/control b/debian/control index 7880bd317..8e9aaa702 100644 --- a/debian/control +++ b/debian/control @@ -116,7 +116,6 @@ Depends: openvpn, openvpn-auth-ldap, openvpn-auth-radius, - openvpn-dco, openvpn-otp, owamp-client, owamp-server, diff --git a/interface-definitions/include/ospf/graceful-restart.xml.i b/interface-definitions/include/ospf/graceful-restart.xml.i new file mode 100644 index 000000000..37d9a7f13 --- /dev/null +++ b/interface-definitions/include/ospf/graceful-restart.xml.i @@ -0,0 +1,67 @@ +<!-- include start from ospf/graceful-restart.xml.i --> +<node name="graceful-restart"> + <properties> + <help>Graceful Restart</help> + </properties> + <children> + <leafNode name="grace-period"> + <properties> + <help>Maximum length of the grace period</help> + <valueHelp> + <format>u32:1-1800</format> + <description>Maximum length of the grace period in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 5-1800"/> + </constraint> + </properties> + <defaultValue>120</defaultValue> + </leafNode> + <node name="helper"> + <properties> + <help>OSPF graceful-restart helpers</help> + </properties> + <children> + <node name="enable"> + <properties> + <help>Enable helper support</help> + </properties> + <children> + <leafNode name="router-id"> + <properties> + <help>Advertising Router-ID</help> + <valueHelp> + <format>ipv4</format> + <description>Router-ID in IP address format</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + <leafNode name="planned-only"> + <properties> + <help>Supported only planned restart</help> + <valueless/> + </properties> + </leafNode> + <leafNode name="supported-grace-time"> + <properties> + <help>Supported grace timer</help> + <valueHelp> + <format>u32:10-1800</format> + <description>Grace interval in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 10-1800"/> + </constraint> + </properties> + </leafNode> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/include/ospf/protocol-common-config.xml.i b/interface-definitions/include/ospf/protocol-common-config.xml.i index 3492b873f..c4778e126 100644 --- a/interface-definitions/include/ospf/protocol-common-config.xml.i +++ b/interface-definitions/include/ospf/protocol-common-config.xml.i @@ -326,6 +326,19 @@ </children> </tagNode> #include <include/ospf/auto-cost.xml.i> +<node name="capability"> + <properties> + <help>Enable specific OSPF features</help> + </properties> + <children> + <leafNode name="opaque"> + <properties> + <help>Opaque LSA</help> + <valueless/> + </properties> + </leafNode> + </children> +</node> #include <include/ospf/default-information.xml.i> <leafNode name="default-metric"> <properties> @@ -339,6 +352,21 @@ </constraint> </properties> </leafNode> +#include <include/ospf/graceful-restart.xml.i> +<node name="graceful-restart"> + <children> + <node name="helper"> + <children> + <leafNode name="no-strict-lsa-checking"> + <properties> + <help>Disable strict LSA check</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> <leafNode name="maximum-paths"> <properties> <help>Maximum multiple paths (ECMP)</help> @@ -928,4 +956,4 @@ </node> </children> </node> -<!-- include end -->
\ No newline at end of file +<!-- include end --> diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i index a7de50638..4c3ca68e1 100644 --- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i +++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i @@ -107,6 +107,21 @@ </node> </children> </node> +#include <include/ospf/graceful-restart.xml.i> +<node name="graceful-restart"> + <children> + <node name="helper"> + <children> + <leafNode name="lsa-check-disable"> + <properties> + <help>Disable strict LSA check</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> + </children> +</node> <tagNode name="interface"> <properties> <help>Enable routing on an IPv6 interface</help> diff --git a/interface-definitions/include/version/ntp-version.xml.i b/interface-definitions/include/version/ntp-version.xml.i index 9eafbf7f0..155c824dc 100644 --- a/interface-definitions/include/version/ntp-version.xml.i +++ b/interface-definitions/include/version/ntp-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/ntp-version.xml.i --> -<syntaxVersion component='ntp' version='2'></syntaxVersion> +<syntaxVersion component='ntp' version='3'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in index 4e061c3e6..127a8179b 100644 --- a/interface-definitions/interfaces-openvpn.xml.in +++ b/interface-definitions/interfaces-openvpn.xml.in @@ -285,6 +285,19 @@ </constraint> </properties> </leafNode> + <node name="offload"> + <properties> + <help>Configurable offload options</help> + </properties> + <children> + <leafNode name="dco"> + <properties> + <help>Enable data channel offload on this interface</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> <leafNode name="openvpn-option"> <properties> <help>Additional OpenVPN options. You must use the syntax of openvpn.conf in this text-field. Using this without proper knowledge may result in a crashed OpenVPN server. Check system log to look for errors.</help> diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in index 501ff05d3..a06ceefb6 100644 --- a/interface-definitions/nat.xml.in +++ b/interface-definitions/nat.xml.in @@ -44,6 +44,14 @@ </leafNode> #include <include/nat-translation-port.xml.i> #include <include/nat-translation-options.xml.i> + <node name="redirect"> + <properties> + <help>Redirect to local host</help> + </properties> + <children> + #include <include/nat-translation-port.xml.i> + </children> + </node> </children> </node> </children> diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in index 2275dd61c..4e874434b 100644 --- a/interface-definitions/ntp.xml.in +++ b/interface-definitions/ntp.xml.in @@ -57,7 +57,7 @@ </children> </tagNode> #include <include/allow-client.xml.i> - #include <include/generic-interface-multi.xml.i> + #include <include/generic-interface.xml.i> #include <include/listen-address.xml.i> #include <include/interface/vrf.xml.i> </children> diff --git a/op-mode-definitions/include/ospf-common.xml.i b/op-mode-definitions/include/ospf/common.xml.i index 979ffb07e..c8341bd3e 100644 --- a/op-mode-definitions/include/ospf-common.xml.i +++ b/op-mode-definitions/include/ospf/common.xml.i @@ -502,6 +502,7 @@ </tagNode> </children> </node> +#include <include/ospf/graceful-restart.xml.i> <node name="interface"> <properties> <help>Show IPv4 OSPF interface information</help> @@ -556,4 +557,3 @@ </children> </node> <!-- included end --> - diff --git a/op-mode-definitions/include/ospf/graceful-restart.xml.i b/op-mode-definitions/include/ospf/graceful-restart.xml.i new file mode 100644 index 000000000..736d8f951 --- /dev/null +++ b/op-mode-definitions/include/ospf/graceful-restart.xml.i @@ -0,0 +1,13 @@ +<node name="graceful-restart"> + <properties> + <help>Show IPv4 OSPF Graceful Restart</help> + </properties> + <children> + <leafNode name="helper"> + <properties> + <help>OSPF Graceful Restart helper details</help> + </properties> + <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> + </leafNode> + </children> + </node> diff --git a/op-mode-definitions/show-bridge.xml.in b/op-mode-definitions/show-bridge.xml.in index acf3a00c7..a272ea204 100644 --- a/op-mode-definitions/show-bridge.xml.in +++ b/op-mode-definitions/show-bridge.xml.in @@ -42,6 +42,18 @@ </properties> <command>${vyos_op_scripts_dir}/bridge.py show_fdb --interface=$3</command> </leafNode> + <leafNode name="detail"> + <properties> + <help>Display bridge interface details</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_detail --interface=$3</command> + </leafNode> + <leafNode name="nexthop-group"> + <properties> + <help>Display bridge interface nexthop-group</help> + </properties> + <command>${vyos_op_scripts_dir}/bridge.py show_detail --nexthop_group --interface=$3</command> + </leafNode> </children> </tagNode> </children> diff --git a/op-mode-definitions/show-ip-ospf.xml.in b/op-mode-definitions/show-ip-ospf.xml.in index 704ed984f..f3b9da90c 100644 --- a/op-mode-definitions/show-ip-ospf.xml.in +++ b/op-mode-definitions/show-ip-ospf.xml.in @@ -13,7 +13,7 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> - #include <include/ospf-common.xml.i> + #include <include/ospf/common.xml.i> <tagNode name="vrf"> <properties> <help>Show OSPF routing protocol for given VRF</help> @@ -24,7 +24,7 @@ </properties> <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command> <children> - #include <include/ospf-common.xml.i> + #include <include/ospf/common.xml.i> </children> </tagNode> </children> diff --git a/op-mode-definitions/show-ipv6-ospfv3.xml.in b/op-mode-definitions/show-ipv6-ospfv3.xml.in index a63465472..e1fcf470f 100644 --- a/op-mode-definitions/show-ipv6-ospfv3.xml.in +++ b/op-mode-definitions/show-ipv6-ospfv3.xml.in @@ -41,6 +41,7 @@ </tagNode> #include <include/ospfv3/border-routers.xml.i> #include <include/ospfv3/database.xml.i> + #include <include/ospf/graceful-restart.xml.i> #include <include/ospfv3/interface.xml.i> #include <include/ospfv3/linkstate.xml.i> #include <include/ospfv3/neighbor.xml.i> @@ -94,6 +95,7 @@ </tagNode> #include <include/ospfv3/border-routers.xml.i> #include <include/ospfv3/database.xml.i> + #include <include/ospf/graceful-restart.xml.i> #include <include/ospfv3/interface.xml.i> #include <include/ospfv3/linkstate.xml.i> #include <include/ospfv3/neighbor.xml.i> diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 5b8d5d1a3..603fedb9b 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -54,28 +54,32 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): translation_str = 'return' log_suffix = '-EXCL' elif 'translation' in rule_conf: - translation_prefix = nat_type[:1] - translation_output = [f'{translation_prefix}nat'] addr = dict_search_args(rule_conf, 'translation', 'address') port = dict_search_args(rule_conf, 'translation', 'port') - - if addr and is_ip_network(addr): - if not ipv6: - map_addr = dict_search_args(rule_conf, nat_type, 'address') - translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}') - ignore_type_addr = True - else: - translation_output.append(f'prefix to {addr}') - elif addr == 'masquerade': - if port: - addr = f'{addr} to ' - translation_output = [addr] - log_suffix = '-MASQ' + redirect_port = dict_search_args(rule_conf, 'translation', 'redirect', 'port') + if redirect_port: + translation_output = [f'redirect to {redirect_port}'] else: - translation_output.append('to') - if addr: - addr = bracketize_ipv6(addr) - translation_output.append(addr) + translation_prefix = nat_type[:1] + translation_output = [f'{translation_prefix}nat'] + + if addr and is_ip_network(addr): + if not ipv6: + map_addr = dict_search_args(rule_conf, nat_type, 'address') + translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}') + ignore_type_addr = True + else: + translation_output.append(f'prefix to {addr}') + elif addr == 'masquerade': + if port: + addr = f'{addr} to ' + translation_output = [addr] + log_suffix = '-MASQ' + else: + translation_output.append('to') + if addr: + addr = bracketize_ipv6(addr) + translation_output.append(addr) options = [] addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping') diff --git a/python/vyos/remote.py b/python/vyos/remote.py index 16fe2b2c2..cf731c881 100644 --- a/python/vyos/remote.py +++ b/python/vyos/remote.py @@ -25,7 +25,7 @@ import urllib.parse from ftplib import FTP from ftplib import FTP_TLS -from paramiko import SSHClient +from paramiko import SSHClient, SSHException from paramiko import MissingHostKeyPolicy from requests import Session @@ -50,7 +50,7 @@ class InteractivePolicy(MissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): print_error(f"Host '{hostname}' not found in known hosts.") print_error('Fingerprint: ' + key.get_fingerprint().hex()) - if ask_yes_no('Do you wish to continue?'): + if sys.stdout.isatty() and ask_yes_no('Do you wish to continue?'): if client._host_keys_filename\ and ask_yes_no('Do you wish to permanently add this host/key pair to known hosts?'): client._host_keys.add(hostname, key.get_name(), key) @@ -96,7 +96,13 @@ def check_storage(path, size): class FtpC: - def __init__(self, url, progressbar=False, check_space=False, source_host='', source_port=0): + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10): self.secure = url.scheme == 'ftps' self.hostname = url.hostname self.path = url.path @@ -106,12 +112,15 @@ class FtpC: self.source = (source_host, source_port) self.progressbar = progressbar self.check_space = check_space + self.timeout = timeout def _establish(self): if self.secure: - return FTP_TLS(source_address=self.source, context=ssl.create_default_context()) + return FTP_TLS(source_address=self.source, + context=ssl.create_default_context(), + timeout=self.timeout) else: - return FTP(source_address=self.source) + return FTP(source_address=self.source, timeout=self.timeout) def download(self, location: str): # Open the file upfront before establishing connection. @@ -150,7 +159,13 @@ class FtpC: class SshC: known_hosts = os.path.expanduser('~/.ssh/known_hosts') - def __init__(self, url, progressbar=False, check_space=False, source_host='', source_port=0): + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10.0): self.hostname = url.hostname self.path = url.path self.username = url.username or os.getenv('REMOTE_USERNAME') @@ -159,6 +174,7 @@ class SshC: self.source = (source_host, source_port) self.progressbar = progressbar self.check_space = check_space + self.timeout = timeout def _establish(self): ssh = SSHClient() @@ -169,7 +185,7 @@ class SshC: ssh.set_missing_host_key_policy(InteractivePolicy()) # `socket.create_connection()` automatically picks a NIC and an IPv4/IPv6 address family # for us on dual-stack systems. - sock = socket.create_connection((self.hostname, self.port), socket.getdefaulttimeout(), self.source) + sock = socket.create_connection((self.hostname, self.port), self.timeout, self.source) ssh.connect(self.hostname, self.port, self.username, self.password, sock=sock) return ssh @@ -198,13 +214,20 @@ class SshC: class HttpC: - def __init__(self, url, progressbar=False, check_space=False, source_host='', source_port=0): + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host='', + source_port=0, + timeout=10.0): self.urlstring = urllib.parse.urlunsplit(url) self.progressbar = progressbar self.check_space = check_space self.source_pair = (source_host, source_port) self.username = url.username or os.getenv('REMOTE_USERNAME') self.password = url.password or os.getenv('REMOTE_PASSWORD') + self.timeout = timeout def _establish(self): session = Session() @@ -220,8 +243,11 @@ class HttpC: # Not only would it potentially mess up with the progress bar but # `shutil.copyfileobj(request.raw, file)` does not handle automatic decoding. s.headers.update({'Accept-Encoding': 'identity'}) - with s.head(self.urlstring, allow_redirects=True) as r: + with s.head(self.urlstring, + allow_redirects=True, + timeout=self.timeout) as r: # Abort early if the destination is inaccessible. + print('pre-3') r.raise_for_status() # If the request got redirected, keep the last URL we ended up with. final_urlstring = r.url @@ -235,7 +261,8 @@ class HttpC: size = None if self.check_space: check_storage(location, size) - with s.get(final_urlstring, stream=True) as r, open(location, 'wb') as f: + with s.get(final_urlstring, stream=True, + timeout=self.timeout) as r, open(location, 'wb') as f: if self.progressbar and size: progress = make_incremental_progressbar(CHUNK_SIZE / size) next(progress) @@ -249,7 +276,10 @@ class HttpC: def upload(self, location: str): # Does not yet support progressbars. with self._establish() as s, open(location, 'rb') as f: - s.post(self.urlstring, data=f, allow_redirects=True) + s.post(self.urlstring, + data=f, + allow_redirects=True, + timeout=self.timeout) class TftpC: @@ -258,10 +288,16 @@ class TftpC: # 2. Since there's no concept authentication, we don't need to deal with keys/passwords. # 3. It would be a waste to import, audit and maintain a third-party library for TFTP. # 4. I'd rather not implement the entire protocol here, no matter how simple it is. - def __init__(self, url, progressbar=False, check_space=False, source_host=None, source_port=0): + def __init__(self, + url, + progressbar=False, + check_space=False, + source_host=None, + source_port=0, + timeout=10): source_option = f'--interface {source_host} --local-port {source_port}' if source_host else '' progress_flag = '--progress-bar' if progressbar else '-s' - self.command = f'curl {source_option} {progress_flag}' + self.command = f'curl {source_option} {progress_flag} --connect-timeout {timeout}' self.urlstring = urllib.parse.urlunsplit(url) def download(self, location: str): @@ -286,10 +322,16 @@ def urlc(urlstring, *args, **kwargs): raise ValueError(f'Unsupported URL scheme: "{url.scheme}"') def download(local_path, urlstring, *args, **kwargs): - urlc(urlstring, *args, **kwargs).download(local_path) + try: + urlc(urlstring, *args, **kwargs).download(local_path) + except Exception as err: + print_error(f'Unable to download "{urlstring}": {err}') def upload(local_path, urlstring, *args, **kwargs): - urlc(urlstring, *args, **kwargs).upload(local_path) + try: + urlc(urlstring, *args, **kwargs).upload(local_path) + except Exception as err: + print_error(f'Unable to upload "{urlstring}": {err}') def get_remote_config(urlstring, source_host='', source_port=0): """ diff --git a/python/vyos/utils/kernel.py b/python/vyos/utils/kernel.py index 0eb113174..1f3bbdffe 100644 --- a/python/vyos/utils/kernel.py +++ b/python/vyos/utils/kernel.py @@ -25,3 +25,14 @@ def check_kmod(k_mod): if not os.path.exists(f'/sys/module/{module}'): if call(f'modprobe {module}') != 0: raise ConfigError(f'Loading Kernel module {module} failed') + +def unload_kmod(k_mod): + """ Common utility function to unload required kernel modules on demand """ + from vyos import ConfigError + from vyos.utils.process import call + if isinstance(k_mod, str): + k_mod = k_mod.split() + for module in k_mod: + if os.path.exists(f'/sys/module/{module}'): + if call(f'rmmod {module}') != 0: + raise ConfigError(f'Unloading Kernel module {module} failed') diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 02fa03f7b..28d566eba 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -231,5 +231,26 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_static_nat') + def test_dnat_redirect(self): + dst_addr_1 = '10.0.1.1' + dest_port = '5122' + protocol = 'tcp' + redirected_port = '22' + ifname = 'eth0' + + self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1]) + self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port]) + self.cli_set(dst_path + ['rule', '10', 'protocol', protocol]) + self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname]) + self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port]) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 977376bdd..80befbfd6 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -479,5 +479,32 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' ip ospf dead-interval 40', config) self.assertIn(f' no ip ospf mpls ldp-sync', config) + def test_ospf_16_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['capability', 'opaque']) + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'no-strict-lsa-checking']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf') + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' capability opaque', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' no graceful-restart helper strict-lsa-checking', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index b67bfaac7..64dfa18db 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -281,5 +281,31 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_delete(['vrf', 'name', vrf]) self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + + def test_ospfv3_09_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'lsa-check-disable']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6') + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' graceful-restart helper lsa-check-disable', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ntp.py b/smoketest/scripts/cli/test_service_ntp.py index 9a63fbbd1..5e385d5ad 100755 --- a/smoketest/scripts/cli/test_service_ntp.py +++ b/smoketest/scripts/cli/test_service_ntp.py @@ -108,7 +108,7 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'bindaddress {listen}', config) def test_03_ntp_interface(self): - interfaces = ['eth0', 'eth1'] + interfaces = ['eth0'] for interface in interfaces: self.cli_set(base_path + ['interface', interface]) diff --git a/smoketest/scripts/system/test_module_load.py b/smoketest/scripts/system/test_module_load.py index 69365761f..9d94f01e6 100755 --- a/smoketest/scripts/system/test_module_load.py +++ b/smoketest/scripts/system/test_module_load.py @@ -23,7 +23,8 @@ modules = { "intel_qat": ["qat_200xx", "qat_200xxvf", "qat_c3xxx", "qat_c3xxxvf", "qat_c62x", "qat_c62xvf", "qat_d15xx", "qat_d15xxvf", "qat_dh895xcc", "qat_dh895xccvf"], - "accel_ppp": ["ipoe", "vlan_mon"] + "accel_ppp": ["ipoe", "vlan_mon"], + "openvpn": ["ovpn-dco-v2"] } class TestKernelModules(unittest.TestCase): diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 607a19385..3bef9b8f6 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -56,6 +56,8 @@ from vyos.utils.list import is_list_equal from vyos.utils.file import makedir from vyos.utils.file import read_file from vyos.utils.file import write_file +from vyos.utils.kernel import check_kmod +from vyos.utils.kernel import unload_kmod from vyos.utils.process import call from vyos.utils.permission import chown from vyos.utils.process import cmd @@ -86,30 +88,45 @@ def get_config(config=None): conf = Config() base = ['interfaces', 'openvpn'] - tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) - ifname, openvpn = get_interface_dict(conf, base) - - if 'deleted' not in openvpn: - openvpn['pki'] = tmp_pki - if is_node_changed(conf, base + [ifname, 'openvpn-option']): - openvpn.update({'restart_required': {}}) - - # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' - # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. - tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) - - # We have to cleanup the config dict, as default values could enable features - # which are not explicitly enabled on the CLI. Example: server mfa totp - # originate comes with defaults, which will enable the - # totp plugin, even when not set via CLI so we - # need to check this first and drop those keys - if dict_search('server.mfa.totp', tmp) == None: - del openvpn['server']['mfa'] - openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn) + if 'deleted' in openvpn: + return openvpn + + openvpn['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + + if is_node_changed(conf, base + [ifname, 'openvpn-option']): + openvpn.update({'restart_required': {}}) + if is_node_changed(conf, base + [ifname, 'enable-dco']): + openvpn.update({'restart_required': {}}) + + # We have to get the dict using 'get_config_dict' instead of 'get_interface_dict' + # as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there. + tmp = conf.get_config_dict(base + [openvpn['ifname']], get_first_key=True) + + # We have to cleanup the config dict, as default values could enable features + # which are not explicitly enabled on the CLI. Example: server mfa totp + # originate comes with defaults, which will enable the + # totp plugin, even when not set via CLI so we + # need to check this first and drop those keys + if dict_search('server.mfa.totp', tmp) == None: + del openvpn['server']['mfa'] + + # OpenVPN Data-Channel-Offload (DCO) is a Kernel module. If loaded it applies to all + # OpenVPN interfaces. Check if DCO is used by any other interface instance. + tmp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + for interface, interface_config in tmp.items(): + # If one interface has DCO configured, enable it. No need to further check + # all other OpenVPN interfaces. We must use a dedicated key to indicate + # the Kernel module must be loaded or not. The per interface "offload.dco" + # key is required per OpenVPN interface instance. + if dict_search('offload.dco', interface_config) != None: + openvpn['module_load_dco'] = {} + break + return openvpn def is_ec_private_key(pki, cert_name): @@ -670,6 +687,15 @@ def apply(openvpn): if interface in interfaces(): VTunIf(interface).remove() + # dynamically load/unload DCO Kernel extension if requested + dco_module = 'ovpn_dco_v2' + if 'module_load_dco' in openvpn: + check_kmod(dco_module) + else: + unload_kmod(dco_module) + + # Now bail out early if interface is disabled or got deleted + if 'deleted' in openvpn or 'disable' in openvpn: return None # verify specified IP address is present on any interface on this system diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py index 6658ca86a..2515dc838 100755 --- a/src/conf_mode/interfaces-wwan.py +++ b/src/conf_mode/interfaces-wwan.py @@ -75,7 +75,6 @@ def get_config(config=None): # We need to know the amount of other WWAN interfaces as ModemManager needs # to be started or stopped. - conf.set_level(base) wwan['other_interfaces'] = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 5f4b658f8..e19b12937 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -72,6 +72,7 @@ def verify_rule(config, err_msg, groups_dict): """ Common verify steps used for both source and destination NAT """ if (dict_search('translation.port', config) != None or + dict_search('translation.redirect.port', config) != None or dict_search('destination.port', config) != None or dict_search('source.port', config)): @@ -221,7 +222,7 @@ def verify(nat): elif config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces(): Warning(f'rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system') - if not dict_search('translation.address', config) and not dict_search('translation.port', config): + if not dict_search('translation.address', config) and not dict_search('translation.port', config) and not dict_search('translation.redirect.port', config): if 'exclude' not in config: raise ConfigError(f'{err_msg} translation requires address and/or port') diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index 917f6e058..1cc23a7df 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -24,6 +24,7 @@ from vyos.utils.process import call from vyos.utils.permission import chmod_750 from vyos.utils.network import get_interface_config from vyos.template import render +from vyos.template import is_ipv4 from vyos import ConfigError from vyos import airbag airbag.enable() @@ -62,16 +63,29 @@ def verify(ntp): if 'interface' in ntp: # If ntpd should listen on a given interface, ensure it exists - for interface in ntp['interface']: - verify_interface_exists(interface) - - # If we run in a VRF, our interface must belong to this VRF, too - if 'vrf' in ntp: - tmp = get_interface_config(interface) - vrf_name = ntp['vrf'] - if 'master' not in tmp or tmp['master'] != vrf_name: - raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ - f'does not belong to this VRF!') + interface = ntp['interface'] + verify_interface_exists(interface) + + # If we run in a VRF, our interface must belong to this VRF, too + if 'vrf' in ntp: + tmp = get_interface_config(interface) + vrf_name = ntp['vrf'] + if 'master' not in tmp or tmp['master'] != vrf_name: + raise ConfigError(f'NTP runs in VRF "{vrf_name}" - "{interface}" '\ + f'does not belong to this VRF!') + + if 'listen_address' in ntp: + ipv4_addresses = 0 + ipv6_addresses = 0 + for address in ntp['listen_address']: + if is_ipv4(address): + ipv4_addresses += 1 + else: + ipv6_addresses += 1 + if ipv4_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv4 value for listen-address parameter ') + if ipv6_addresses > 1: + raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ') return None diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index cec025fea..7b9f15505 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -475,6 +475,8 @@ def verify(bgp): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): raise ConfigError( 'Command "import vrf" conflicts with "rd vpn export" command!') + if not dict_search('parameters.router_id', bgp): + Warning(f'BGP "router-id" is required when using "rd" and "route-target"!') if dict_search('route_target.vpn.both', afi_config): if verify_vrf_as_import(vrf_name, afi, bgp['dependent_vrfs']): diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py index 509d4f501..f2075d25b 100755 --- a/src/conf_mode/protocols_ospf.py +++ b/src/conf_mode/protocols_ospf.py @@ -88,6 +88,8 @@ def get_config(config=None): del default_values['area']['area_type']['nssa'] if 'mpls_te' not in ospf: del default_values['mpls_te'] + if 'graceful_restart' not in ospf: + del default_values['graceful_restart'] for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static', 'table']: # table is a tagNode thus we need to clean out all occurances for the diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py index 7f50d8624..fbea51f56 100755 --- a/src/conf_mode/protocols_ospfv3.py +++ b/src/conf_mode/protocols_ospfv3.py @@ -83,6 +83,8 @@ def get_config(config=None): # need to check this first and probably drop that key. if dict_search('default_information.originate', ospfv3) is None: del default_values['default_information'] + if 'graceful_restart' not in ospfv3: + del default_values['graceful_restart'] # XXX: T2665: we currently have no nice way for defaults under tag nodes, # clean them out and add them manually :( diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py index 7b6150696..5def8d645 100755 --- a/src/conf_mode/protocols_static.py +++ b/src/conf_mode/protocols_static.py @@ -47,7 +47,7 @@ def get_config(config=None): base_path = ['protocols', 'static'] # eqivalent of the C foo ? 'a' : 'b' statement base = vrf and ['vrf', 'name', vrf, 'protocols', 'static'] or base_path - static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + static = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # Assign the name of our VRF context if vrf: static['vrf'] = vrf diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 273475c18..afd75913e 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -389,7 +389,7 @@ def apply(login): # command until user is removed - userdel might return 8 as # SSH sessions are not all yet properly cleaned away, thus we # simply re-run the command until the account wen't away - while run(f'userdel --remove {user}', stderr=DEVNULL): + while run(f'userdel {user}', stderr=DEVNULL): sleep(0.250) except Exception as e: diff --git a/src/etc/modprobe.d/openvpn.conf b/src/etc/modprobe.d/openvpn.conf new file mode 100644 index 000000000..a9259fea2 --- /dev/null +++ b/src/etc/modprobe.d/openvpn.conf @@ -0,0 +1 @@ +blacklist ovpn-dco-v2 diff --git a/src/migration-scripts/container/0-to-1 b/src/migration-scripts/container/0-to-1 index 9fcf295e8..86f89ee04 100755 --- a/src/migration-scripts/container/0-to-1 +++ b/src/migration-scripts/container/0-to-1 @@ -39,12 +39,12 @@ config = ConfigTree(config_file) if config.exists(base): for container in config.list_nodes(base): # Stop any given container first - call(f'systemctl stop vyos-container-{container}.service') + call(f'sudo systemctl stop vyos-container-{container}.service') # Export container image for later re-import to new filesystem. We store # the backup on a real disk as a tmpfs (like /tmp) could probably lack # memory if a host has too many containers stored. image_name = config.return_value(base + [container, 'image']) - call(f'podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') + call(f'sudo podman image save --quiet --output /root/{container}.tar --format oci-archive {image_name}') # No need to adjust the strage driver online (this is only used for testing and # debugging on a live system) - it is already overlay2 when the migration script @@ -66,10 +66,10 @@ if config.exists(base): # Export container image for later re-import to new filesystem image_name = config.return_value(base + [container, 'image']) image_path = f'/root/{container}.tar' - call(f'podman image load --quiet --input {image_path}') + call(f'sudo podman image load --quiet --input {image_path}') # Start any given container first - call(f'systemctl start vyos-container-{container}.service') + call(f'sudo systemctl start vyos-container-{container}.service') # Delete temporary container image if os.path.exists(image_path): diff --git a/src/migration-scripts/ntp/2-to-3 b/src/migration-scripts/ntp/2-to-3 new file mode 100755 index 000000000..7d4e0bd83 --- /dev/null +++ b/src/migration-scripts/ntp/2-to-3 @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 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/>. + +# T5154: allow only one ip address per family for parameter 'listen-address' +# Allow only one interface for parameter 'interface' +# If more than one are specified, remove such entries + +import sys + +from vyos.configtree import ConfigTree +from vyos.template import is_ipv4 +from vyos.template import is_ipv6 + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base_path = ['service', 'ntp'] +if not config.exists(base_path): + # Nothing to do + sys.exit(0) + +if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv4(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv4(addr): + config.delete_value(base_path + ['listen-address'], addr) + +if config.exists(base_path + ['listen-address']) and (len([addr for addr in config.return_values(base_path + ['listen-address']) if is_ipv6(addr)]) > 1): + for addr in config.return_values(base_path + ['listen-address']): + if is_ipv6(addr): + config.delete_value(base_path + ['listen-address'], addr) + +if config.exists(base_path + ['interface']): + if len(config.return_values(base_path + ['interface'])) > 1: + config.delete(base_path + ['interface']) + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) diff --git a/src/op_mode/bridge.py b/src/op_mode/bridge.py index 5531c41d0..1834b9cc9 100755 --- a/src/op_mode/bridge.py +++ b/src/op_mode/bridge.py @@ -24,6 +24,7 @@ from tabulate import tabulate from vyos.utils.process import cmd from vyos.utils.process import rc_cmd +from vyos.utils.process import call from vyos.utils.dict import dict_search import vyos.opmode @@ -129,7 +130,8 @@ def _get_formatted_output_vlan(data): if vlan_entry.get('vlanEnd'): vlan_end = vlan_entry.get('vlanEnd') vlan = f'{vlan}-{vlan_end}' - flags = ', '.join(vlan_entry.get('flags')).lower() + flags_raw = vlan_entry.get('flags') + flags = ', '.join(flags_raw if isinstance(flags_raw,list) else "").lower() data_entries.append([interface, vlan, flags]) headers = ["Interface", "Vlan", "Flags"] @@ -164,6 +166,23 @@ def _get_formatted_output_mdb(data): output = tabulate(data_entries, headers) return output +def _get_bridge_detail(iface): + """Get interface detail statistics""" + return call(f'vtysh -c "show interface {iface}"') + +def _get_bridge_detail_nexthop_group(iface): + """Get interface detail nexthop_group statistics""" + return call(f'vtysh -c "show interface {iface} nexthop-group"') + +def _get_bridge_detail_nexthop_group_raw(iface): + out = cmd(f'vtysh -c "show interface {iface} nexthop-group"') + return out + +def _get_bridge_detail_raw(iface): + """Get interface detail json statistics""" + data = cmd(f'vtysh -c "show interface {iface} json"') + data_dict = json.loads(data) + return data_dict def show(raw: bool): bridge_data = _get_raw_data_summary() @@ -196,6 +215,17 @@ def show_mdb(raw: bool, interface: str): else: return _get_formatted_output_mdb(mdb_data) +def show_detail(raw: bool, nexthop_group: typing.Optional[bool], interface: str): + if raw: + if nexthop_group: + return _get_bridge_detail_nexthop_group_raw(interface) + else: + return _get_bridge_detail_raw(interface) + else: + if nexthop_group: + return _get_bridge_detail_nexthop_group(interface) + else: + return _get_bridge_detail(interface) if __name__ == '__main__': try: diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh index 85f8eda15..4b59b801e 100755 --- a/src/op_mode/show_ntp.sh +++ b/src/op_mode/show_ntp.sh @@ -18,7 +18,7 @@ if ! ps -C chronyd &>/dev/null; then fi PID=$(pgrep chronyd | head -n1) -VRF_NAME=$(ip vrf identify ) +VRF_NAME=$(ip vrf identify ${PID}) if [ ! -z ${VRF_NAME} ]; then VRF_CMD="sudo ip vrf exec ${VRF_NAME}" |