summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNicolás Fort <95703796+nicolas-fort@users.noreply.github.com>2023-07-31 15:22:51 -0300
committerGitHub <noreply@github.com>2023-07-31 15:22:51 -0300
commit657a566df58478c2f5d4bccad952bfcb7991e847 (patch)
tree1cf6ab7548286f358d05389132cd82bc177c676a /src
parent7ae9d8953ddc9ba38d62400187ce1ec44abb5a6e (diff)
parentdf33f450b4e8b7e0286e36540de81edfb5f52e73 (diff)
downloadvyos-1x-657a566df58478c2f5d4bccad952bfcb7991e847.tar.gz
vyos-1x-657a566df58478c2f5d4bccad952bfcb7991e847.zip
Merge branch 'current' into T5014-dnat
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py68
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py1
-rwxr-xr-xsrc/conf_mode/nat.py1
-rwxr-xr-xsrc/conf_mode/ntp.py34
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py2
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py2
-rwxr-xr-xsrc/conf_mode/protocols_static.py2
-rwxr-xr-xsrc/conf_mode/service_ids_fastnetmon.py14
-rwxr-xr-xsrc/conf_mode/system-login.py4
-rw-r--r--src/etc/modprobe.d/openvpn.conf1
-rwxr-xr-xsrc/migration-scripts/container/0-to-18
-rwxr-xr-xsrc/migration-scripts/ntp/2-to-362
-rwxr-xr-xsrc/op_mode/bridge.py32
-rwxr-xr-xsrc/op_mode/dhcp.py88
-rwxr-xr-xsrc/op_mode/show_ntp.sh2
-rwxr-xr-xsrc/op_mode/webproxy_update_blacklist.sh9
-rw-r--r--src/systemd/dhcp6c@.service4
18 files changed, 289 insertions, 47 deletions
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 fa6fe9bb6..5af06ed16 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)):
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/service_ids_fastnetmon.py b/src/conf_mode/service_ids_fastnetmon.py
index 2e678cf0b..f6b80552b 100755
--- a/src/conf_mode/service_ids_fastnetmon.py
+++ b/src/conf_mode/service_ids_fastnetmon.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -30,6 +30,7 @@ airbag.enable()
config_file = r'/run/fastnetmon/fastnetmon.conf'
networks_list = r'/run/fastnetmon/networks_list'
excluded_networks_list = r'/run/fastnetmon/excluded_networks_list'
+attack_dir = '/var/log/fastnetmon_attacks'
def get_config(config=None):
if config:
@@ -55,8 +56,11 @@ def verify(fastnetmon):
if 'mode' not in fastnetmon:
raise ConfigError('Specify operating mode!')
- if 'listen_interface' not in fastnetmon:
- raise ConfigError('Specify interface(s) for traffic capture')
+ if fastnetmon.get('mode') == 'mirror' and 'listen_interface' not in fastnetmon:
+ raise ConfigError("Incorrect settings for 'mode mirror': must specify interface(s) for traffic mirroring")
+
+ if fastnetmon.get('mode') == 'sflow' and 'listen_address' not in fastnetmon.get('sflow', {}):
+ raise ConfigError("Incorrect settings for 'mode sflow': must specify sFlow 'listen-address'")
if 'alert_script' in fastnetmon:
if os.path.isfile(fastnetmon['alert_script']):
@@ -74,6 +78,10 @@ def generate(fastnetmon):
return None
+ # Create dir for log attack details
+ if not os.path.exists(attack_dir):
+ os.mkdir(attack_dir)
+
render(config_file, 'ids/fastnetmon.j2', fastnetmon)
render(networks_list, 'ids/fastnetmon_networks_list.j2', fastnetmon)
render(excluded_networks_list, 'ids/fastnetmon_excluded_networks_list.j2', fastnetmon)
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 273475c18..82941e0c0 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -54,7 +54,7 @@ MAX_USER_UID: int = 59999
# LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec
MAX_RADIUS_TIMEOUT: int = 50
# MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout)
-MAX_RADIUS_COUNT: int = 25
+MAX_RADIUS_COUNT: int = 8
# Maximum number of supported TACACS servers
MAX_TACACS_COUNT: int = 8
@@ -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/dhcp.py b/src/op_mode/dhcp.py
index 3e51e990b..20ef698bd 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -14,10 +14,12 @@
# 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 os
import sys
import typing
from datetime import datetime
+from glob import glob
from ipaddress import ip_address
from isc_dhcp_leases import IscDhcpLeases
from tabulate import tabulate
@@ -27,10 +29,13 @@ import vyos.opmode
from vyos.base import Warning
from vyos.configquery import ConfigTreeQuery
-from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
+from vyos.utils.file import read_file
+from vyos.utils.process import cmd
from vyos.utils.process import is_systemd_service_running
+time_string = "%a %b %d %H:%M:%S %Z %Y"
+
config = ConfigTreeQuery()
lease_valid_states = ['all', 'active', 'free', 'expired', 'released', 'abandoned', 'reset', 'backup']
sort_valid_inet = ['end', 'mac', 'hostname', 'ip', 'pool', 'remaining', 'start', 'state']
@@ -287,6 +292,87 @@ def show_server_leases(raw: bool, family: ArgFamily, pool: typing.Optional[str],
return _get_formatted_server_leases(lease_data, family=family)
+def _get_raw_client_leases(family='inet', interface=None):
+ from time import mktime
+ from datetime import datetime
+
+ lease_dir = '/var/lib/dhcp'
+ lease_files = []
+ lease_data = []
+
+ if interface:
+ tmp = f'{lease_dir}/dhclient_{interface}.lease'
+ if os.path.exists(tmp):
+ lease_files.append(tmp)
+ else:
+ # All DHCP leases
+ lease_files = glob(f'{lease_dir}/dhclient_*.lease')
+
+ for lease in lease_files:
+ tmp = {}
+ with open(lease, 'r') as f:
+ for line in f.readlines():
+ line = line.rstrip()
+ if 'last_update' not in tmp:
+ # ISC dhcp client contains least_update timestamp in human readable
+ # format this makes less sense for an API and also the expiry
+ # timestamp is provided in UNIX time. Convert string (e.g. Sun Jul
+ # 30 18:13:44 CEST 2023) to UNIX time (1690733624)
+ tmp.update({'last_update' : int(mktime(datetime.strptime(line, time_string).timetuple()))})
+ continue
+
+ k, v = line.split('=')
+ tmp.update({k : v.replace("'", "")})
+
+ lease_data.append(tmp)
+
+ return lease_data
+
+def _get_formatted_client_leases(lease_data, family):
+ from time import localtime
+ from time import strftime
+
+ from vyos.validate import is_intf_addr_assigned
+
+ data_entries = []
+ for lease in lease_data:
+ data_entries.append(["Interface", lease['interface']])
+ if 'new_ip_address' in lease:
+ tmp = '[Active]' if is_intf_addr_assigned(lease['interface'], lease['new_ip_address']) else '[Inactive]'
+ data_entries.append(["IP address", lease['new_ip_address'], tmp])
+ if 'new_subnet_mask' in lease:
+ data_entries.append(["Subnet Mask", lease['new_subnet_mask']])
+ if 'new_domain_name' in lease:
+ data_entries.append(["Domain Name", lease['new_domain_name']])
+ if 'new_routers' in lease:
+ data_entries.append(["Router", lease['new_routers']])
+ if 'new_domain_name_servers' in lease:
+ data_entries.append(["Name Server", lease['new_domain_name_servers']])
+ if 'new_dhcp_server_identifier' in lease:
+ data_entries.append(["DHCP Server", lease['new_dhcp_server_identifier']])
+ if 'new_dhcp_lease_time' in lease:
+ data_entries.append(["DHCP Server", lease['new_dhcp_lease_time']])
+ if 'last_update' in lease:
+ tmp = strftime(time_string, localtime(int(lease['last_update'])))
+ data_entries.append(["Last Update", tmp])
+ if 'new_expiry' in lease:
+ tmp = strftime(time_string, localtime(int(lease['new_expiry'])))
+ data_entries.append(["Expiry", tmp])
+
+ # Add empty marker
+ data_entries.append([''])
+
+ output = tabulate(data_entries, tablefmt='plain')
+
+ return output
+
+def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[str]):
+ lease_data = _get_raw_client_leases(family=family, interface=interface)
+ if raw:
+ return lease_data
+ else:
+ return _get_formatted_client_leases(lease_data, family=family)
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
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}"
diff --git a/src/op_mode/webproxy_update_blacklist.sh b/src/op_mode/webproxy_update_blacklist.sh
index 4fb9a54c6..05ea86f9e 100755
--- a/src/op_mode/webproxy_update_blacklist.sh
+++ b/src/op_mode/webproxy_update_blacklist.sh
@@ -45,6 +45,9 @@ do
--auto-update-blacklist)
auto="yes"
;;
+ --vrf)
+ vrf="yes"
+ ;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
@@ -76,7 +79,11 @@ fi
if [[ -n $update ]] && [[ $update -eq "yes" ]]; then
tmp_blacklists='/tmp/blacklists.gz'
- curl -o $tmp_blacklists $blacklist_url
+ if [[ -n $vrf ]] && [[ $vrf -eq "yes" ]]; then
+ sudo ip vrf exec $1 curl -o $tmp_blacklists $blacklist_url
+ else
+ curl -o $tmp_blacklists $blacklist_url
+ fi
if [ $? -ne 0 ]; then
echo "Unable to download [$blacklist_url]!"
exit 1
diff --git a/src/systemd/dhcp6c@.service b/src/systemd/dhcp6c@.service
index 9a97ee261..495cb7e26 100644
--- a/src/systemd/dhcp6c@.service
+++ b/src/systemd/dhcp6c@.service
@@ -2,14 +2,16 @@
Description=WIDE DHCPv6 client on %i
Documentation=man:dhcp6c(8) man:dhcp6c.conf(5)
ConditionPathExists=/run/dhcp6c/dhcp6c.%i.conf
+ConditionPathExists=/run/dhcp6c/dhcp6c.%i.options
After=vyos-router.service
StartLimitIntervalSec=0
[Service]
WorkingDirectory=/run/dhcp6c
+EnvironmentFile=-/run/dhcp6c/dhcp6c.%i.options
Type=forking
PIDFile=/run/dhcp6c/dhcp6c.%i.pid
-ExecStart=/usr/sbin/dhcp6c -D -k /run/dhcp6c/dhcp6c.%i.sock -c /run/dhcp6c/dhcp6c.%i.conf -p /run/dhcp6c/dhcp6c.%i.pid %i
+ExecStart=/usr/sbin/dhcp6c $DHCP6C_OPTS
Restart=on-failure
RestartSec=20