diff options
| -rw-r--r-- | data/templates/accel-ppp/l2tp.config.j2 | 3 | ||||
| -rw-r--r-- | data/templates/accel-ppp/pptp.config.j2 | 3 | ||||
| -rw-r--r-- | data/templates/high-availability/keepalived.conf.j2 | 8 | ||||
| -rw-r--r-- | interface-definitions/high-availability.xml.in | 19 | ||||
| -rw-r--r-- | interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i | 15 | ||||
| -rw-r--r-- | interface-definitions/vpn-l2tp.xml.in | 1 | ||||
| -rw-r--r-- | python/vyos/utils/convert.py | 145 | ||||
| -rw-r--r-- | python/vyos/utils/dict.py | 21 | ||||
| -rw-r--r-- | python/vyos/utils/file.py | 171 | ||||
| -rw-r--r-- | python/vyos/utils/io.py | 103 | ||||
| -rwxr-xr-x | smoketest/scripts/system/test_kernel_options.py | 13 | ||||
| -rwxr-xr-x | src/conf_mode/high-availability.py | 7 | ||||
| -rwxr-xr-x | src/conf_mode/vpn_l2tp.py | 6 | ||||
| -rwxr-xr-x | src/conf_mode/vpn_pptp.py | 4 | 
14 files changed, 515 insertions, 4 deletions
| diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2 index 5914fd375..a2f9c9fc7 100644 --- a/data/templates/accel-ppp/l2tp.config.j2 +++ b/data/templates/accel-ppp/l2tp.config.j2 @@ -91,6 +91,9 @@ server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_por  {%     if radius_dynamic_author.server is vyos_defined %}  dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }}  {%     endif %} +{%     if radius_acct_interim_interval is vyos_defined %} +acct-interim-interval={{ radius_acct_interim_interval }} +{%     endif %}  {%     if radius_acct_inter_jitter %}  acct-interim-jitter={{ radius_acct_inter_jitter }}  {%     endif %} diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2 index 78a629d2d..0082e55bf 100644 --- a/data/templates/accel-ppp/pptp.config.j2 +++ b/data/templates/accel-ppp/pptp.config.j2 @@ -70,6 +70,9 @@ verbose=1  server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }}  {%     endfor %} +{%     if radius_acct_interim_interval is vyos_defined %} +acct-interim-interval={{ radius_acct_interim_interval }} +{%     endif %}  {%     if radius_acct_inter_jitter %}  acct-interim-jitter={{ radius_acct_inter_jitter }}  {%     endif %} diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2 index 6ea5f91d0..85b89c70c 100644 --- a/data/templates/high-availability/keepalived.conf.j2 +++ b/data/templates/high-availability/keepalived.conf.j2 @@ -32,9 +32,13 @@ global_defs {  {% if vrrp.group is vyos_defined %}  {%     for name, group_config in vrrp.group.items() if group_config.disable is not vyos_defined %} -{%         if group_config.health_check.script is vyos_defined %} +{%         if group_config.health_check is vyos_defined %}  vrrp_script healthcheck_{{ name }} { +{%             if group_config.health_check.script is vyos_defined %}      script "{{ group_config.health_check.script }}" +{%             elif group_config.health_check.ping is vyos_defined %} +    script "/usr/bin/ping -c1 {{ group_config.health_check.ping }}" +{%             endif %}      interval {{ group_config.health_check.interval }}      fall {{ group_config.health_check.failure_count }}      rise 1 @@ -121,7 +125,7 @@ vrrp_instance {{ name }} {  {%             endfor %}      }  {%         endif %} -{%         if group_config.health_check.script is vyos_defined %} +{%         if group_config.health_check is vyos_defined %}      track_script {          healthcheck_{{ name }}      } diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 9b52fe82e..94253def3 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -96,7 +96,7 @@                #include <include/generic-disable-node.xml.i>                <node name="health-check">                  <properties> -                  <help>Health check script</help> +                  <help>Health check</help>                  </properties>                  <children>                    <leafNode name="failure-count"> @@ -117,6 +117,23 @@                      </properties>                      <defaultValue>60</defaultValue>                    </leafNode> +                  <leafNode name="ping"> +                    <properties> +                      <help>ICMP ping health check</help> +                      <valueHelp> +                        <format>ipv4</format> +                        <description>IPv4 ping target address</description> +                      </valueHelp> +                      <valueHelp> +                        <format>ipv6</format> +                        <description>IPv6 ping target address</description> +                      </valueHelp> +                      <constraint> +                        <validator name="ipv4-address"/> +                        <validator name="ipv6-address"/> +                      </constraint> +                    </properties> +                  </leafNode>                    <leafNode name="script">                      <properties>                        <help>Health check script file</help> diff --git a/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i b/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i new file mode 100644 index 000000000..311ef969c --- /dev/null +++ b/interface-definitions/include/accel-ppp/radius-accounting-interim-interval.xml.i @@ -0,0 +1,15 @@ +<!-- include start from accel-ppp/radius-accounting-interim-interval.xml.i --> +<leafNode name="accounting-interim-interval"> +  <properties> +    <help>Interval in seconds to send accounting information</help> +    <valueHelp> +      <format>u32:1-3600</format> +      <description>Interval in seconds to send accounting information</description> +    </valueHelp> +    <constraint> +      <validator name="numeric" argument="--range 1-3600"/> +    </constraint> +    <constraintErrorMessage>Interval value must be between 1 and 3600 seconds</constraintErrorMessage> +  </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in index 6b64c5f5d..ec186cd23 100644 --- a/interface-definitions/vpn-l2tp.xml.in +++ b/interface-definitions/vpn-l2tp.xml.in @@ -177,6 +177,7 @@                    #include <include/radius-auth-server-ipv4.xml.i>                    <node name="radius">                      <children> +                      #include <include/accel-ppp/radius-accounting-interim-interval.xml.i>                        <tagNode name="server">                          <children>                            #include <include/accel-ppp/radius-additions-disable-accounting.xml.i> diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py new file mode 100644 index 000000000..975c67e0a --- /dev/null +++ b/python/vyos/utils/convert.py @@ -0,0 +1,145 @@ +# Copyright 2023 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library.  If not, see <http://www.gnu.org/licenses/>. + +def seconds_to_human(s, separator=""): +    """ Converts number of seconds passed to a human-readable +    interval such as 1w4d18h35m59s +    """ +    s = int(s) + +    week = 60 * 60 * 24 * 7 +    day = 60 * 60 * 24 +    hour = 60 * 60 + +    remainder = 0 +    result = "" + +    weeks = s // week +    if weeks > 0: +        result = "{0}w".format(weeks) +        s = s % week + +    days = s // day +    if days > 0: +        result = "{0}{1}{2}d".format(result, separator, days) +        s = s % day + +    hours = s // hour +    if hours > 0: +        result = "{0}{1}{2}h".format(result, separator, hours) +        s = s % hour + +    minutes = s // 60 +    if minutes > 0: +        result = "{0}{1}{2}m".format(result, separator, minutes) +        s = s % 60 + +    seconds = s +    if seconds > 0: +        result = "{0}{1}{2}s".format(result, separator, seconds) + +    return result + +def bytes_to_human(bytes, initial_exponent=0, precision=2): +    """ Converts a value in bytes to a human-readable size string like 640 KB + +    The initial_exponent parameter is the exponent of 2, +    e.g. 10 (1024) for kilobytes, 20 (1024 * 1024) for megabytes. +    """ + +    if bytes == 0: +        return "0 B" + +    from math import log2 + +    bytes = bytes * (2**initial_exponent) + +    # log2 is a float, while range checking requires an int +    exponent = int(log2(bytes)) + +    if exponent < 10: +        value = bytes +        suffix = "B" +    elif exponent in range(10, 20): +        value = bytes / 1024 +        suffix = "KB" +    elif exponent in range(20, 30): +        value = bytes / 1024**2 +        suffix = "MB" +    elif exponent in range(30, 40): +        value = bytes / 1024**3 +        suffix = "GB" +    else: +        value = bytes / 1024**4 +        suffix = "TB" +    # Add a new case when the first machine with petabyte RAM +    # hits the market. + +    size_string = "{0:.{1}f} {2}".format(value, precision, suffix) +    return size_string + +def human_to_bytes(value): +    """ Converts a data amount with a unit suffix to bytes, like 2K to 2048 """ + +    from re import match as re_match + +    res = re_match(r'^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$', value) + +    if not res: +        raise ValueError(f"'{value}' is not a valid data amount") +    else: +        amount = float(res.group(1)) +        unit = res.group(2).lower() + +        if unit == 'b': +            res = amount +        elif (unit == 'k') or (unit == 'kb'): +            res = amount * 1024 +        elif (unit == 'm') or (unit == 'mb'): +            res = amount * 1024**2 +        elif (unit == 'g') or (unit == 'gb'): +            res = amount * 1024**3 +        elif (unit == 't') or (unit == 'tb'): +            res = amount * 1024**4 +        else: +            raise ValueError(f"Unsupported data unit '{unit}'") + +    # There cannot be fractional bytes, so we convert them to integer. +    # However, truncating causes problems with conversion back to human unit, +    # so we round instead -- that seems to work well enough. +    return round(res) + +def mac_to_eui64(mac, prefix=None): +    """ +    Convert a MAC address to a EUI64 address or, with prefix provided, a full +    IPv6 address. +    Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 +    """ +    import re +    from ipaddress import ip_network +    # http://tools.ietf.org/html/rfc4291#section-2.5.1 +    eui64 = re.sub(r'[.:-]', '', mac).lower() +    eui64 = eui64[0:6] + 'fffe' + eui64[6:] +    eui64 = hex(int(eui64[0:2], 16) ^ 2)[2:].zfill(2) + eui64[2:] + +    if prefix is None: +        return ':'.join(re.findall(r'.{4}', eui64)) +    else: +        try: +            net = ip_network(prefix, strict=False) +            euil = int('0x{0}'.format(eui64), 16) +            return str(net[euil]) +        except:  # pylint: disable=bare-except +            return diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index 66b40d92b..4afc9f54e 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -233,3 +233,24 @@ def dict_to_list(d, save_key_to=None):              collect.append(item)      return collect + +def check_mutually_exclusive_options(d, keys, required=False): +    """ Checks if a dict has at most one or only one of +    mutually exclusive keys. +    """ +    present_keys = [] + +    for k in d: +        if k in keys: +            present_keys.append(k) + +    # Un-mangle the keys to make them match CLI option syntax +    from re import sub +    orig_keys = list(map(lambda s: sub(r'_', '-', s), keys)) +    orig_present_keys = list(map(lambda s: sub(r'_', '-', s), present_keys)) + +    if len(present_keys) > 1: +        raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}") + +    if required and (len(present_keys) < 1): +        raise ValueError(f"At least one of the following options is required: {orig_present_keys}") diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py new file mode 100644 index 000000000..2560a35be --- /dev/null +++ b/python/vyos/utils/file.py @@ -0,0 +1,171 @@ +# Copyright 2023 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library.  If not, see <http://www.gnu.org/licenses/>. + +import os + + +def read_file(fname, defaultonfailure=None): +    """ +    read the content of a file, stripping any end characters (space, newlines) +    should defaultonfailure be not None, it is returned on failure to read +    """ +    try: +        """ Read a file to string """ +        with open(fname, 'r') as f: +            data = f.read().strip() +        return data +    except Exception as e: +        if defaultonfailure is not None: +            return defaultonfailure +        raise e + +def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False): +    """ +    Write content of data to given fname, should defaultonfailure be not None, +    it is returned on failure to read. + +    If directory of file is not present, it is auto-created. +    """ +    dirname = os.path.dirname(fname) +    if not os.path.isdir(dirname): +        os.makedirs(dirname, mode=0o755, exist_ok=False) +        chown(dirname, user, group) + +    try: +        """ Write a file to string """ +        bytes = 0 +        with open(fname, 'w' if not append else 'a') as f: +            bytes = f.write(data) +        chown(fname, user, group) +        chmod(fname, mode) +        return bytes +    except Exception as e: +        if defaultonfailure is not None: +            return defaultonfailure +        raise e + +def read_json(fname, defaultonfailure=None): +    """ +    read and json decode the content of a file +    should defaultonfailure be not None, it is returned on failure to read +    """ +    import json +    try: +        with open(fname, 'r') as f: +            data = json.load(f) +        return data +    except Exception as e: +        if defaultonfailure is not None: +            return defaultonfailure +        raise e + +def chown(path, user, group): +    """ change file/directory owner """ +    from pwd import getpwnam +    from grp import getgrnam + +    if user is None or group is None: +        return False + +    # path may also be an open file descriptor +    if not isinstance(path, int) and not os.path.exists(path): +        return False + +    uid = getpwnam(user).pw_uid +    gid = getgrnam(group).gr_gid +    os.chown(path, uid, gid) +    return True + + +def chmod(path, bitmask): +    # path may also be an open file descriptor +    if not isinstance(path, int) and not os.path.exists(path): +        return +    if bitmask is None: +        return +    os.chmod(path, bitmask) + + +def chmod_600(path): +    """ Make file only read/writable by owner """ +    from stat import S_IRUSR, S_IWUSR + +    bitmask = S_IRUSR | S_IWUSR +    chmod(path, bitmask) + + +def chmod_750(path): +    """ Make file/directory only executable to user and group """ +    from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP + +    bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP +    chmod(path, bitmask) + + +def chmod_755(path): +    """ Make file executable by all """ +    from stat import S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IXGRP, S_IROTH, S_IXOTH + +    bitmask = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | \ +              S_IROTH | S_IXOTH +    chmod(path, bitmask) + + +def makedir(path, user=None, group=None): +    if os.path.exists(path): +        return +    os.makedirs(path, mode=0o755) +    chown(path, user, group) + +def wait_for_inotify(file_path, pre_hook=None, event_type=None, timeout=None, sleep_interval=0.1): +    """ Waits for an inotify event to occur """ +    if not os.path.dirname(file_path): +        raise ValueError( +          "File path {} does not have a directory part (required for inotify watching)".format(file_path)) +    if not os.path.basename(file_path): +        raise ValueError( +          "File path {} does not have a file part, do not know what to watch for".format(file_path)) + +    from inotify.adapters import Inotify +    from time import time +    from time import sleep + +    time_start = time() + +    i = Inotify() +    i.add_watch(os.path.dirname(file_path)) + +    if pre_hook: +        pre_hook() + +    for event in i.event_gen(yield_nones=True): +        if (timeout is not None) and ((time() - time_start) > timeout): +            # If the function didn't return until this point, +            # the file failed to have been written to and closed within the timeout +            raise OSError("Waiting for file {} to be written has failed".format(file_path)) + +        # Most such events don't take much time, so it's better to check right away +        # and sleep later. +        if event is not None: +            (_, type_names, path, filename) = event +            if filename == os.path.basename(file_path): +                if event_type in type_names: +                    return +        sleep(sleep_interval) + +def wait_for_file_write_complete(file_path, pre_hook=None, timeout=None, sleep_interval=0.1): +    """ Waits for a process to close a file after opening it in write mode. """ +    wait_for_inotify(file_path, +      event_type='IN_CLOSE_WRITE', pre_hook=pre_hook, timeout=timeout, sleep_interval=sleep_interval) diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py new file mode 100644 index 000000000..843494855 --- /dev/null +++ b/python/vyos/utils/io.py @@ -0,0 +1,103 @@ +# Copyright 2023 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library.  If not, see <http://www.gnu.org/licenses/>. + +def print_error(str='', end='\n'): +    """ +    Print `str` to stderr, terminated with `end`. +    Used for warnings and out-of-band messages to avoid mangling precious +     stdout output. +    """ +    import sys +    sys.stderr.write(str) +    sys.stderr.write(end) +    sys.stderr.flush() + +def make_progressbar(): +    """ +    Make a procedure that takes two arguments `done` and `total` and prints a +     progressbar based on the ratio thereof, whose length is determined by the +     width of the terminal. +    """ +    import shutil, math +    col, _ = shutil.get_terminal_size() +    col = max(col - 15, 20) +    def print_progressbar(done, total): +        if done <= total: +            increment = total / col +            length = math.ceil(done / increment) +            percentage = str(math.ceil(100 * done / total)).rjust(3) +            print_error(f'[{length * "#"}{(col - length) * "_"}] {percentage}%', '\r') +            # Print a newline so that the subsequent prints don't overwrite the full bar. +        if done == total: +            print_error() +    return print_progressbar + +def make_incremental_progressbar(increment: float): +    """ +    Make a generator that displays a progressbar that grows monotonically with +     every iteration. +    First call displays it at 0% and every subsequent iteration displays it +     at `increment` increments where 0.0 < `increment` < 1.0. +    Intended for FTP and HTTP transfers with stateless callbacks. +    """ +    print_progressbar = make_progressbar() +    total = 0.0 +    while total < 1.0: +        print_progressbar(total, 1.0) +        yield +        total += increment +    print_progressbar(1, 1) +    # Ignore further calls. +    while True: +        yield + +def ask_input(question, default='', numeric_only=False, valid_responses=[]): +    question_out = question +    if default: +        question_out += f' (Default: {default})' +    response = '' +    while True: +        response = input(question_out + ' ').strip() +        if not response and default: +            return default +        if numeric_only: +            if not response.isnumeric(): +                print("Invalid value, try again.") +                continue +            response = int(response) +        if valid_responses and response not in valid_responses: +            print("Invalid value, try again.") +            continue +        break +    return response + +def ask_yes_no(question, default=False) -> bool: +    """Ask a yes/no question via input() and return their answer.""" +    from sys import stdout +    default_msg = "[Y/n]" if default else "[y/N]" +    while True: +        try: +            stdout.write("%s %s " % (question, default_msg)) +            c = input().lower() +            if c == '': +                return default +            elif c in ("y", "ye", "yes"): +                return True +            elif c in ("n", "no"): +                return False +            else: +                stdout.write("Please respond with yes/y or no/n\n") +        except EOFError: +            stdout.write("\nPlease respond with yes/y or no/n\n") diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py index 94be0483a..fe2a1c48a 100755 --- a/smoketest/scripts/system/test_kernel_options.py +++ b/smoketest/scripts/system/test_kernel_options.py @@ -63,6 +63,19 @@ class TestKernelModules(unittest.TestCase):              self.assertIn(option, config_data,                            f"Option {option} is not present in /proc/config.gz") +    def test_synproxy_enabled(self): +        options_to_check = [ +            'CONFIG_NFT_SYNPROXY', +            'CONFIG_IP_NF_TARGET_SYNPROXY' +        ] +        if not os.path.isfile(CONFIG): +            call('sudo modprobe configs') +        with gzip.open(CONFIG, 'rt') as f: +            config_data = f.read() +        for option in options_to_check: +            tmp = re.findall(f'{option}=(y|m)', config_data) +            self.assertTrue(tmp) +      def test_qemu_support(self):          # The bond/lacp interface must be enabled in the OS Kernel          for option in ['CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO', diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 5e76dd9f9..7a63f5b4b 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -106,6 +106,13 @@ def verify(ha):                  if not {'password', 'type'} <= set(group_config['authentication']):                      raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"') +            if 'health_check' in group_config: +                from vyos.utils.dict import check_mutually_exclusive_options +                try: +                    check_mutually_exclusive_options(group_config["health_check"], ["script", "ping"], required=True) +                except ValueError as e: +                    raise ConfigError(f'Health check config is incorrect in VRRP group "{group}": {e}') +              # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction              # We also need to make sure VRID is not used twice on the same interface with the              # same address family. diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py index 65623c2b1..ffac3b023 100755 --- a/src/conf_mode/vpn_l2tp.py +++ b/src/conf_mode/vpn_l2tp.py @@ -1,6 +1,6 @@  #!/usr/bin/env python3  # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -63,6 +63,7 @@ default_config_data = {      'ppp_ipv6_peer_intf_id': None,      'radius_server': [],      'radius_acct_inter_jitter': '', +    'radius_acct_interim_interval': None,      'radius_acct_tmo': '3',      'radius_max_try': '3',      'radius_timeout': '3', @@ -190,6 +191,9 @@ def get_config(config=None):          # advanced radius-setting          conf.set_level(base_path + ['authentication', 'radius']) +        if conf.exists(['accounting-interim-interval']): +            l2tp['radius_acct_interim_interval'] = conf.return_value(['accounting-interim-interval']) +          if conf.exists(['acct-interim-jitter']):              l2tp['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter']) diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py index 986a19972..b9d18110a 100755 --- a/src/conf_mode/vpn_pptp.py +++ b/src/conf_mode/vpn_pptp.py @@ -37,6 +37,7 @@ default_pptp = {      'local_users' : [],      'radius_server' : [],      'radius_acct_inter_jitter': '', +    'radius_acct_interim_interval': None,      'radius_acct_tmo' : '30',      'radius_max_try' : '3',      'radius_timeout' : '30', @@ -145,6 +146,9 @@ def get_config(config=None):          # advanced radius-setting          conf.set_level(base_path + ['authentication', 'radius']) +        if conf.exists(['accounting-interim-interval']): +            pptp['radius_acct_interim_interval'] = conf.return_value(['accounting-interim-interval']) +          if conf.exists(['acct-interim-jitter']):              pptp['radius_acct_inter_jitter'] = conf.return_value(['acct-interim-jitter']) | 
