diff options
| author | Nicolás Fort <95703796+nicolas-fort@users.noreply.github.com> | 2023-07-31 15:22:51 -0300 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-31 15:22:51 -0300 | 
| commit | 657a566df58478c2f5d4bccad952bfcb7991e847 (patch) | |
| tree | 1cf6ab7548286f358d05389132cd82bc177c676a /src | |
| parent | 7ae9d8953ddc9ba38d62400187ce1ec44abb5a6e (diff) | |
| parent | df33f450b4e8b7e0286e36540de81edfb5f52e73 (diff) | |
| download | vyos-1x-657a566df58478c2f5d4bccad952bfcb7991e847.tar.gz vyos-1x-657a566df58478c2f5d4bccad952bfcb7991e847.zip | |
Merge branch 'current' into T5014-dnat
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 68 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces-wwan.py | 1 | ||||
| -rwxr-xr-x | src/conf_mode/nat.py | 1 | ||||
| -rwxr-xr-x | src/conf_mode/ntp.py | 34 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_bgp.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_ospf.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_ospfv3.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/protocols_static.py | 2 | ||||
| -rwxr-xr-x | src/conf_mode/service_ids_fastnetmon.py | 14 | ||||
| -rwxr-xr-x | src/conf_mode/system-login.py | 4 | ||||
| -rw-r--r-- | src/etc/modprobe.d/openvpn.conf | 1 | ||||
| -rwxr-xr-x | src/migration-scripts/container/0-to-1 | 8 | ||||
| -rwxr-xr-x | src/migration-scripts/ntp/2-to-3 | 62 | ||||
| -rwxr-xr-x | src/op_mode/bridge.py | 32 | ||||
| -rwxr-xr-x | src/op_mode/dhcp.py | 88 | ||||
| -rwxr-xr-x | src/op_mode/show_ntp.sh | 2 | ||||
| -rwxr-xr-x | src/op_mode/webproxy_update_blacklist.sh | 9 | ||||
| -rw-r--r-- | src/systemd/dhcp6c@.service | 4 | 
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 | 
