summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/configdict.py41
-rw-r--r--python/vyos/configverify.py6
-rw-r--r--python/vyos/firewall.py27
-rw-r--r--python/vyos/ifconfig/ethernet.py22
-rw-r--r--python/vyos/ifconfig/wireguard.py85
-rw-r--r--python/vyos/nat.py188
-rw-r--r--python/vyos/template.py26
-rw-r--r--python/vyos/util.py8
-rw-r--r--python/vyos/version.py39
9 files changed, 377 insertions, 65 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 912bc94f2..53decfbf5 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -643,7 +643,9 @@ def get_accel_dict(config, base, chap_secrets):
from vyos.util import get_half_cpus
from vyos.template import is_ipv4
- dict = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
+ dict = config.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
# We have gathered the dict representation of the CLI, but there are default
# options which we need to update into the dictionary retrived.
@@ -663,6 +665,18 @@ def get_accel_dict(config, base, chap_secrets):
# added to individual local users instead - so we can simply delete them
if dict_search('client_ipv6_pool.prefix.mask', default_values):
del default_values['client_ipv6_pool']['prefix']['mask']
+ # delete empty dicts
+ if len (default_values['client_ipv6_pool']['prefix']) == 0:
+ del default_values['client_ipv6_pool']['prefix']
+ if len (default_values['client_ipv6_pool']) == 0:
+ del default_values['client_ipv6_pool']
+
+ # T2665: IPoE only - it has an interface tag node
+ # added to individual local users instead - so we can simply delete them
+ if dict_search('authentication.interface', default_values):
+ del default_values['authentication']['interface']
+ if dict_search('interface', default_values):
+ del default_values['interface']
dict = dict_merge(default_values, dict)
@@ -684,11 +698,9 @@ def get_accel_dict(config, base, chap_secrets):
dict.update({'name_server_ipv4' : ns_v4, 'name_server_ipv6' : ns_v6})
del dict['name_server']
- # Add individual RADIUS server default values
+ # T2665: Add individual RADIUS server default values
if dict_search('authentication.radius.server', dict):
- # T2665
default_values = defaults(base + ['authentication', 'radius', 'server'])
-
for server in dict_search('authentication.radius.server', dict):
dict['authentication']['radius']['server'][server] = dict_merge(
default_values, dict['authentication']['radius']['server'][server])
@@ -698,22 +710,31 @@ def get_accel_dict(config, base, chap_secrets):
if 'disable_accounting' in dict['authentication']['radius']['server'][server]:
dict['authentication']['radius']['server'][server]['acct_port'] = '0'
- # Add individual local-user default values
+ # T2665: Add individual local-user default values
if dict_search('authentication.local_users.username', dict):
- # T2665
default_values = defaults(base + ['authentication', 'local-users', 'username'])
-
for username in dict_search('authentication.local_users.username', dict):
dict['authentication']['local_users']['username'][username] = dict_merge(
default_values, dict['authentication']['local_users']['username'][username])
- # Add individual IPv6 client-pool default mask if required
+ # T2665: Add individual IPv6 client-pool default mask if required
if dict_search('client_ipv6_pool.prefix', dict):
- # T2665
default_values = defaults(base + ['client-ipv6-pool', 'prefix'])
-
for prefix in dict_search('client_ipv6_pool.prefix', dict):
dict['client_ipv6_pool']['prefix'][prefix] = dict_merge(
default_values, dict['client_ipv6_pool']['prefix'][prefix])
+ # T2665: IPoE only - add individual local-user default values
+ if dict_search('authentication.interface', dict):
+ default_values = defaults(base + ['authentication', 'interface'])
+ for interface in dict_search('authentication.interface', dict):
+ dict['authentication']['interface'][interface] = dict_merge(
+ default_values, dict['authentication']['interface'][interface])
+
+ if dict_search('interface', dict):
+ default_values = defaults(base + ['interface'])
+ for interface in dict_search('interface', dict):
+ dict['interface'][interface] = dict_merge(default_values,
+ dict['interface'][interface])
+
return dict
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 447ec795c..afa0c5b33 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -381,14 +381,14 @@ def verify_vlan_config(config):
verify_mtu_parent(c_vlan, config)
verify_mtu_parent(c_vlan, s_vlan)
-def verify_accel_ppp_base_service(config):
+def verify_accel_ppp_base_service(config, local_users=True):
"""
Common helper function which must be used by all Accel-PPP services based
on get_config_dict()
"""
# vertify auth settings
- if dict_search('authentication.mode', config) == 'local':
- if not dict_search('authentication.local_users', config):
+ if local_users and dict_search('authentication.mode', config) == 'local':
+ if dict_search(f'authentication.local_users', config) == None:
raise ConfigError('Authentication mode local requires local users to be configured!')
for user in dict_search('authentication.local_users.username', config):
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index 0bc5378db..f9b7222fd 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -52,9 +52,9 @@ def get_ips_domains_dict(list_domains):
return ip_dict
-def nft_init_set(group_name, table="filter", family="ip"):
+def nft_init_set(group_name, table="vyos_filter", family="ip"):
"""
- table ip filter {
+ table ip vyos_filter {
set GROUP_NAME
type ipv4_addr
flags interval
@@ -63,9 +63,9 @@ def nft_init_set(group_name, table="filter", family="ip"):
return call(f'nft add set ip {table} {group_name} {{ type ipv4_addr\\; flags interval\\; }}')
-def nft_add_set_elements(group_name, elements, table="filter", family="ip"):
+def nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip"):
"""
- table ip filter {
+ table ip vyos_filter {
set GROUP_NAME {
type ipv4_addr
flags interval
@@ -75,18 +75,18 @@ def nft_add_set_elements(group_name, elements, table="filter", family="ip"):
elements = ", ".join(elements)
return call(f'nft add element {family} {table} {group_name} {{ {elements} }} ')
-def nft_flush_set(group_name, table="filter", family="ip"):
+def nft_flush_set(group_name, table="vyos_filter", family="ip"):
"""
Flush elements of nft set
"""
return call(f'nft flush set {family} {table} {group_name}')
-def nft_update_set_elements(group_name, elements, table="filter", family="ip"):
+def nft_update_set_elements(group_name, elements, table="vyos_filter", family="ip"):
"""
Update elements of nft set
"""
- flush_set = nft_flush_set(group_name, table="filter", family="ip")
- nft_add_set = nft_add_set_elements(group_name, elements, table="filter", family="ip")
+ flush_set = nft_flush_set(group_name, table="vyos_filter", family="ip")
+ nft_add_set = nft_add_set_elements(group_name, elements, table="vyos_filter", family="ip")
return flush_set, nft_add_set
# END firewall group domain-group (sets)
@@ -274,6 +274,13 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
negated_lengths_str = ','.join(rule_conf['packet_length_exclude'])
output.append(f'ip{def_suffix} length != {{{negated_lengths_str}}}')
+ if 'dscp' in rule_conf:
+ dscp_str = ','.join(rule_conf['dscp'])
+ output.append(f'ip{def_suffix} dscp {{{dscp_str}}}')
+
+ if 'dscp_exclude' in rule_conf:
+ negated_dscp_str = ','.join(rule_conf['dscp_exclude'])
+ output.append(f'ip{def_suffix} dscp != {{{negated_dscp_str}}}')
if 'ipsec' in rule_conf:
if 'match_ipsec' in rule_conf['ipsec']:
@@ -319,6 +326,10 @@ def parse_rule(rule_conf, fw_name, rule_id, ip_name):
if 'action' in rule_conf:
output.append(nft_action(rule_conf['action']))
+ if 'jump' in rule_conf['action']:
+ target = rule_conf['jump_target']
+ output.append(f'NAME{def_suffix}_{target}')
+
else:
output.append('return')
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index b8deb3311..32e667038 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -16,6 +16,7 @@
import os
import re
+from glob import glob
from vyos.ethtool import Ethtool
from vyos.ifconfig.interface import Interface
from vyos.util import run
@@ -74,6 +75,10 @@ class EthernetIf(Interface):
'convert': lambda cpus: cpus if cpus else '0',
'location': '/sys/class/net/{ifname}/queues/rx-0/rps_cpus',
},
+ 'rfs': {
+ 'convert': lambda num: num if num else '0',
+ 'location': '/proc/sys/net/core/rps_sock_flow_entries',
+ },
}}
def __init__(self, ifname, **kargs):
@@ -258,6 +263,20 @@ class EthernetIf(Interface):
# send bitmask representation as hex string without leading '0x'
return self.set_interface('rps', rps_cpus)
+ def set_rfs(self, state):
+ rfs_flow = 0
+ global_rfs_flow = 0
+ queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*'))
+ if state:
+ global_rfs_flow = 32768
+ rfs_flow = int(global_rfs_flow/queues)
+
+ self.set_interface('rfs', global_rfs_flow)
+ for i in range(0, queues):
+ self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow)
+
+ return True
+
def set_sg(self, state):
"""
Enable Scatter-Gather support. State can be either True or False.
@@ -342,6 +361,9 @@ class EthernetIf(Interface):
# RPS - Receive Packet Steering
self.set_rps(dict_search('offload.rps', config) != None)
+ # RFS - Receive Flow Steering
+ self.set_rfs(dict_search('offload.rfs', config) != None)
+
# scatter-gather option
self.set_sg(dict_search('offload.sg', config) != None)
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 28b5e2991..9a92c71b8 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -167,12 +167,8 @@ class WireGuardIf(Interface):
# remove no longer associated peers first
if 'peer_remove' in config:
- for tmp in config['peer_remove']:
- peer = config['peer_remove'][tmp]
- peer['ifname'] = config['ifname']
-
- cmd = 'wg set {ifname} peer {public_key} remove'
- self._cmd(cmd.format(**peer))
+ for peer, public_key in config['peer_remove'].items():
+ self._cmd(f'wg set {self.ifname} peer {public_key} remove')
config['private_key_file'] = '/tmp/tmp.wireguard.key'
with open(config['private_key_file'], 'w') as f:
@@ -187,44 +183,49 @@ class WireGuardIf(Interface):
base_cmd = base_cmd.format(**config)
- for tmp in config['peer']:
- peer = config['peer'][tmp]
-
- # start of with a fresh 'wg' command
- cmd = base_cmd + ' peer {public_key}'
-
- # If no PSK is given remove it by using /dev/null - passing keys via
- # the shell (usually bash) is considered insecure, thus we use a file
- no_psk_file = '/dev/null'
- psk_file = no_psk_file
- if 'preshared_key' in peer:
- psk_file = '/tmp/tmp.wireguard.psk'
- with open(psk_file, 'w') as f:
- f.write(peer['preshared_key'])
- cmd += f' preshared-key {psk_file}'
-
- # Persistent keepalive is optional
- if 'persistent_keepalive'in peer:
- cmd += ' persistent-keepalive {persistent_keepalive}'
-
- # Multiple allowed-ip ranges can be defined - ensure we are always
- # dealing with a list
- if isinstance(peer['allowed_ips'], str):
- peer['allowed_ips'] = [peer['allowed_ips']]
- cmd += ' allowed-ips ' + ','.join(peer['allowed_ips'])
-
- # Endpoint configuration is optional
- if {'address', 'port'} <= set(peer):
- if is_ipv6(peer['address']):
- cmd += ' endpoint [{address}]:{port}'
- else:
- cmd += ' endpoint {address}:{port}'
+ if 'peer' in config:
+ for peer, peer_config in config['peer'].items():
+ # T4702: No need to configure this peer when it was explicitly
+ # marked as disabled - also active sessions are terminated as
+ # the public key was already removed when entering this method!
+ if 'disable' in peer_config:
+ continue
+
+ # start of with a fresh 'wg' command
+ cmd = base_cmd + ' peer {public_key}'
+
+ # If no PSK is given remove it by using /dev/null - passing keys via
+ # the shell (usually bash) is considered insecure, thus we use a file
+ no_psk_file = '/dev/null'
+ psk_file = no_psk_file
+ if 'preshared_key' in peer_config:
+ psk_file = '/tmp/tmp.wireguard.psk'
+ with open(psk_file, 'w') as f:
+ f.write(peer_config['preshared_key'])
+ cmd += f' preshared-key {psk_file}'
+
+ # Persistent keepalive is optional
+ if 'persistent_keepalive'in peer_config:
+ cmd += ' persistent-keepalive {persistent_keepalive}'
+
+ # Multiple allowed-ip ranges can be defined - ensure we are always
+ # dealing with a list
+ if isinstance(peer_config['allowed_ips'], str):
+ peer_config['allowed_ips'] = [peer_config['allowed_ips']]
+ cmd += ' allowed-ips ' + ','.join(peer_config['allowed_ips'])
+
+ # Endpoint configuration is optional
+ if {'address', 'port'} <= set(peer_config):
+ if is_ipv6(peer_config['address']):
+ cmd += ' endpoint [{address}]:{port}'
+ else:
+ cmd += ' endpoint {address}:{port}'
- self._cmd(cmd.format(**peer))
+ self._cmd(cmd.format(**peer_config))
- # PSK key file is not required to be stored persistently as its backed by CLI
- if psk_file != no_psk_file and os.path.exists(psk_file):
- os.remove(psk_file)
+ # PSK key file is not required to be stored persistently as its backed by CLI
+ if psk_file != no_psk_file and os.path.exists(psk_file):
+ os.remove(psk_file)
# call base class
super().update(config)
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
new file mode 100644
index 000000000..31bbdc386
--- /dev/null
+++ b/python/vyos/nat.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+from vyos.template import is_ip_network
+from vyos.util import dict_search_args
+
+def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
+ output = []
+ ip_prefix = 'ip6' if ipv6 else 'ip'
+ log_prefix = ('DST' if nat_type == 'destination' else 'SRC') + f'-NAT-{rule_id}'
+ log_suffix = ''
+
+ if ipv6:
+ log_prefix = log_prefix.replace("NAT-", "NAT66-")
+
+ ignore_type_addr = False
+ translation_str = ''
+
+ if 'inbound_interface' in rule_conf:
+ ifname = rule_conf['inbound_interface']
+ if ifname != 'any':
+ output.append(f'iifname "{ifname}"')
+
+ if 'outbound_interface' in rule_conf:
+ ifname = rule_conf['outbound_interface']
+ if ifname != 'any':
+ output.append(f'oifname "{ifname}"')
+
+ if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
+ protocol = rule_conf['protocol']
+ if protocol == 'tcp_udp':
+ protocol = '{ tcp, udp }'
+ output.append(f'meta l4proto {protocol}')
+
+ if 'exclude' in rule_conf:
+ 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'
+ else:
+ translation_output.append('to')
+ if addr:
+ translation_output.append(addr)
+
+ options = []
+ addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping')
+ port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping')
+ if addr_mapping == 'persistent':
+ options.append('persistent')
+ if port_mapping and port_mapping != 'none':
+ options.append(port_mapping)
+
+ translation_str = " ".join(translation_output) + (f':{port}' if port else '')
+
+ if options:
+ translation_str += f' {",".join(options)}'
+
+ for target in ['source', 'destination']:
+ prefix = target[:1]
+ addr = dict_search_args(rule_conf, target, 'address')
+ if addr and not (ignore_type_addr and target == nat_type):
+ operator = ''
+ if addr[:1] == '!':
+ operator = '!='
+ addr = addr[1:]
+ output.append(f'{ip_prefix} {prefix}addr {operator} {addr}')
+
+ addr_prefix = dict_search_args(rule_conf, target, 'prefix')
+ if addr_prefix and ipv6:
+ operator = ''
+ if addr_prefix[:1] == '!':
+ operator = '!='
+ addr_prefix = addr[1:]
+ output.append(f'ip6 {prefix}addr {operator} {addr_prefix}')
+
+ port = dict_search_args(rule_conf, target, 'port')
+ if port:
+ protocol = rule_conf['protocol']
+ if protocol == 'tcp_udp':
+ protocol = 'th'
+ operator = ''
+ if port[:1] == '!':
+ operator = '!='
+ port = port[1:]
+ output.append(f'{protocol} {prefix}port {operator} {{ {port} }}')
+
+ output.append('counter')
+
+ if 'log' in rule_conf:
+ output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
+
+ if translation_str:
+ output.append(translation_str)
+
+ output.append(f'comment "{log_prefix}"')
+
+ return " ".join(output)
+
+def parse_nat_static_rule(rule_conf, rule_id, nat_type):
+ output = []
+ log_prefix = ('STATIC-DST' if nat_type == 'destination' else 'STATIC-SRC') + f'-NAT-{rule_id}'
+ log_suffix = ''
+
+ ignore_type_addr = False
+ translation_str = ''
+
+ if 'inbound_interface' in rule_conf:
+ ifname = rule_conf['inbound_interface']
+ ifprefix = 'i' if nat_type == 'destination' else 'o'
+ if ifname != 'any':
+ output.append(f'{ifprefix}ifname "{ifname}"')
+
+ if 'exclude' in rule_conf:
+ 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')
+ map_addr = dict_search_args(rule_conf, 'destination', 'address')
+
+ if nat_type == 'source':
+ addr, map_addr = map_addr, addr # Swap
+
+ if addr and is_ip_network(addr):
+ translation_output.append(f'ip prefix to ip {translation_prefix}addr map {{ {map_addr} : {addr} }}')
+ ignore_type_addr = True
+ elif addr:
+ translation_output.append(f'to {addr}')
+
+ options = []
+ addr_mapping = dict_search_args(rule_conf, 'translation', 'options', 'address_mapping')
+ port_mapping = dict_search_args(rule_conf, 'translation', 'options', 'port_mapping')
+ if addr_mapping == 'persistent':
+ options.append('persistent')
+ if port_mapping and port_mapping != 'none':
+ options.append(port_mapping)
+
+ if options:
+ translation_output.append(",".join(options))
+
+ translation_str = " ".join(translation_output)
+
+ prefix = nat_type[:1]
+ addr = dict_search_args(rule_conf, 'translation' if nat_type == 'source' else nat_type, 'address')
+ if addr and not ignore_type_addr:
+ output.append(f'ip {prefix}addr {addr}')
+
+ output.append('counter')
+
+ if translation_str:
+ output.append(translation_str)
+
+ if 'log' in rule_conf:
+ output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
+
+ output.append(f'comment "{log_prefix}"')
+
+ return " ".join(output)
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 9804308c1..0870a0523 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -548,7 +548,7 @@ def nft_rule(rule_conf, fw_name, rule_id, ip_name='ip'):
return parse_rule(rule_conf, fw_name, rule_id, ip_name)
@register_filter('nft_default_rule')
-def nft_default_rule(fw_conf, fw_name):
+def nft_default_rule(fw_conf, fw_name, ipv6=False):
output = ['counter']
default_action = fw_conf['default_action']
@@ -557,6 +557,11 @@ def nft_default_rule(fw_conf, fw_name):
output.append(f'log prefix "[{fw_name[:19]}-default-{action_suffix}]"')
output.append(nft_action(default_action))
+ if 'default_jump_target' in fw_conf:
+ target = fw_conf['default_jump_target']
+ def_suffix = '6' if ipv6 else ''
+ output.append(f'NAME{def_suffix}_{target}')
+
output.append(f'comment "{fw_name} default-action {default_action}"')
return " ".join(output)
@@ -611,6 +616,25 @@ def nft_nested_group(out_list, includes, groups, key):
add_includes(name)
return out_list
+@register_filter('nat_rule')
+def nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
+ from vyos.nat import parse_nat_rule
+ return parse_nat_rule(rule_conf, rule_id, nat_type, ipv6)
+
+@register_filter('nat_static_rule')
+def nat_static_rule(rule_conf, rule_id, nat_type):
+ from vyos.nat import parse_nat_static_rule
+ return parse_nat_static_rule(rule_conf, rule_id, nat_type)
+
+@register_filter('range_to_regex')
+def range_to_regex(num_range):
+ from vyos.range_regex import range_to_regex
+ if '-' not in num_range:
+ return num_range
+
+ regex = range_to_regex(num_range)
+ return f'({regex})'
+
@register_test('vyos_defined')
def vyos_defined(value, test_value=None, var_type=None):
"""
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 325b630bc..461df9a6e 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -1,4 +1,4 @@
-# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -471,6 +471,12 @@ def process_named_running(name):
return p.pid
return None
+def is_list_equal(first: list, second: list) -> bool:
+ """ Check if 2 lists are equal and list not empty """
+ if len(first) != len(second) or len(first) == 0:
+ return False
+ return sorted(first) == sorted(second)
+
def is_listen_port_bind_service(port: int, service: str) -> bool:
"""Check if listen port bound to expected program name
:param port: Bind port
diff --git a/python/vyos/version.py b/python/vyos/version.py
index 871bb0f1b..fb706ad44 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -31,6 +31,7 @@ Example of the version data dict::
import os
import json
+import requests
import vyos.defaults
from vyos.util import read_file
@@ -105,3 +106,41 @@ def get_full_version_data(fname=version_file):
version_data['hardware_uuid'] = read_file(subsystem + '/product_uuid', 'Unknown')
return version_data
+
+def get_remote_version(url):
+ """
+ Get remote available JSON file from remote URL
+ An example of the image-version.json
+
+ [
+ {
+ "arch":"amd64",
+ "flavors":[
+ "generic"
+ ],
+ "image":"vyos-rolling-latest.iso",
+ "latest":true,
+ "lts":false,
+ "release_date":"2022-09-06",
+ "release_train":"sagitta",
+ "url":"http://xxx/rolling/current/vyos-rolling-latest.iso",
+ "version":"vyos-1.4-rolling-202209060217"
+ }
+ ]
+ """
+ headers = {}
+ try:
+ remote_data = requests.get(url=url, headers=headers)
+ remote_data.raise_for_status()
+ if remote_data.status_code != 200:
+ return False
+ return remote_data.json()
+ except requests.exceptions.HTTPError as errh:
+ print ("HTTP Error:", errh)
+ except requests.exceptions.ConnectionError as errc:
+ print ("Connecting error:", errc)
+ except requests.exceptions.Timeout as errt:
+ print ("Timeout error:", errt)
+ except requests.exceptions.RequestException as err:
+ print ("Unable to get remote data", err)
+ return False