diff options
Diffstat (limited to 'src')
| -rwxr-xr-x | src/conf_mode/high-availability.py | 2 | ||||
| -rwxr-xr-x | src/op_mode/config_mgmt.py | 10 | ||||
| -rwxr-xr-x | src/op_mode/generate_interfaces_debug_archive.py | 115 | ||||
| -rwxr-xr-x | src/op_mode/igmp-proxy.py | 99 | ||||
| -rwxr-xr-x | src/op_mode/show_igmpproxy.py | 241 | ||||
| -rwxr-xr-x | src/services/api/graphql/generate/schema_from_op_mode.py | 5 | ||||
| -rw-r--r-- | src/services/api/graphql/graphql/mutations.py | 3 | ||||
| -rw-r--r-- | src/services/api/graphql/graphql/queries.py | 3 | ||||
| -rw-r--r-- | src/services/api/graphql/libs/op_mode.py | 5 | ||||
| -rw-r--r-- | src/services/api/graphql/session/errors/op_mode_errors.py | 4 | 
10 files changed, 228 insertions, 259 deletions
| diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py index 4ed16d0d7..bc3e67b40 100755 --- a/src/conf_mode/high-availability.py +++ b/src/conf_mode/high-availability.py @@ -51,6 +51,8 @@ def get_config(config=None):      if 'vrrp' in ha:          if 'group' in ha['vrrp']:              default_values_vrrp = defaults(base_vrrp + ['group']) +            if 'garp' in default_values_vrrp: +                del default_values_vrrp['garp']              for group in ha['vrrp']['group']:                  ha['vrrp']['group'][group] = dict_merge(default_values_vrrp, ha['vrrp']['group'][group]) diff --git a/src/op_mode/config_mgmt.py b/src/op_mode/config_mgmt.py index 0ffb1468b..66de26d1f 100755 --- a/src/op_mode/config_mgmt.py +++ b/src/op_mode/config_mgmt.py @@ -20,17 +20,17 @@ import typing  import vyos.opmode  from vyos.config_mgmt import ConfigMgmt -def show_commit_diff(raw: bool, rev: int, revb: typing.Optional[int], +def show_commit_diff(raw: bool, rev: int, rev2: typing.Optional[int],                       commands: bool):      config_mgmt = ConfigMgmt() -    config_diff = config_mgmt.show_commit_diff(rev, revb, commands) +    config_diff = config_mgmt.show_commit_diff(rev, rev2, commands)      if raw: -        revb = (rev+1) if revb is None else revb +        rev2 = (rev+1) if rev2 is None else rev2          if commands: -            d = {f'config_command_diff_{revb}_{rev}': config_diff} +            d = {f'config_command_diff_{rev2}_{rev}': config_diff}          else: -            d = {f'config_file_diff_{revb}_{rev}': config_diff} +            d = {f'config_file_diff_{rev2}_{rev}': config_diff}          return d      return config_diff diff --git a/src/op_mode/generate_interfaces_debug_archive.py b/src/op_mode/generate_interfaces_debug_archive.py new file mode 100755 index 000000000..f5767080a --- /dev/null +++ b/src/op_mode/generate_interfaces_debug_archive.py @@ -0,0 +1,115 @@ +#!/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/>. + +from datetime import datetime +from pathlib import Path +from shutil import rmtree +from socket import gethostname +from sys import exit +from tarfile import open as tar_open +from vyos.util import rc_cmd +import os + +# define a list of commands that needs to be executed + +CMD_LIST: list[str] = [ +    "journalctl -b -n 500", +    "journalctl -b -k -n 500", +    "ip -s l", +    "cat /proc/interrupts", +    "cat /proc/softirqs", +    "top -b -d 1 -n 2 -1", +    "netstat -l",              +    "cat /proc/net/dev", +    "cat /proc/net/softnet_stat", +    "cat /proc/net/icmp", +    "cat /proc/net/udp", +    "cat /proc/net/tcp", +    "cat /proc/net/netstat", +    "sysctl net", +    "timeout 10 tcpdump -c 500 -eni any port not 22" +] + +CMD_INTERFACES_LIST: list[str] = [ +    "ethtool -i ", +    "ethtool -S ", +    "ethtool -g ", +    "ethtool -c ", +    "ethtool -a ", +    "ethtool -k ", +    "ethtool -i ", +    "ethtool --phy-statistics " +] + +# get intefaces info +interfaces_list = os.popen('ls /sys/class/net/').read().split() + +# modify CMD_INTERFACES_LIST for all interfaces +CMD_INTERFACES_LIST_MOD=[] +for command_interface in interfaces_list: +    for command_interfacev2 in CMD_INTERFACES_LIST: +        CMD_INTERFACES_LIST_MOD.append (f'{command_interfacev2}{command_interface}') + +# execute a command and save the output to a file + +def save_stdout(command: str, file: Path) -> None: +    rc, stdout = rc_cmd(command) +    body: str = f'''### {command} ### +Command: {command} +Exit code: {rc} +Stdout: +{stdout} + +''' +    with file.open(mode='a') as f: +        f.write(body) + +# get local host name +hostname: str = gethostname() +# get current time +time_now: str = datetime.now().isoformat(timespec='seconds') + +# define a temporary directory for logs and collected data +tmp_dir: Path = Path(f'/tmp/drops-debug_{time_now}') +# set file paths +drops_file: Path = Path(f'{tmp_dir}/drops.txt') +interfaces_file: Path = Path(f'{tmp_dir}/interfaces.txt') +archive_file: str = f'/tmp/packet-drops-debug_{time_now}.tar.bz2' + +# create files +tmp_dir.mkdir() +drops_file.touch() +interfaces_file.touch() + +try: +    # execute all commands +    for command in CMD_LIST: +        save_stdout(command, drops_file) +    for command_interface in CMD_INTERFACES_LIST_MOD: +        save_stdout(command_interface, interfaces_file) + +    # create an archive +    with tar_open(name=archive_file, mode='x:bz2') as tar_file: +        tar_file.add(tmp_dir) + +    # inform user about success +    print(f'Debug file is generated and located in {archive_file}') +except Exception as err: +    print(f'Error during generating a debug file: {err}') +finally: +    # cleanup +    rmtree(tmp_dir) +    exit() diff --git a/src/op_mode/igmp-proxy.py b/src/op_mode/igmp-proxy.py new file mode 100755 index 000000000..0086c9aa6 --- /dev/null +++ b/src/op_mode/igmp-proxy.py @@ -0,0 +1,99 @@ +#!/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/>. + +# File: show_igmpproxy.py +# Purpose: +#    Display istatistics from IPv4 IGMP proxy. +#    Used by the "run show ip multicast" command tree. + +import ipaddress +import json +import socket +import sys +import tabulate + +import vyos.config +import vyos.opmode + +from vyos.util import bytes_to_human, print_error + +def _is_configured(): +    """Check if IGMP proxy is configured""" +    return vyos.config.Config().exists_effective('protocols igmp-proxy') + +def _is_running(): +    """Check if IGMP proxy is currently running""" +    return not vyos.util.run('ps -C igmpproxy') + +def _kernel_to_ip(addr): +    """ +    Convert any given address from Linux kernel to a proper, IPv4 address +    using the correct host byte order. +    """ +    # Convert from hex 'FE000A0A' to decimal '4261415434' +    addr = int(addr, 16) +    # Kernel ABI _always_ uses network byte order. +    addr = socket.ntohl(addr) +    return str(ipaddress.IPv4Address(addr)) + +def _process_mr_vif(): +    """Read rows from /proc/net/ip_mr_vif into dicts.""" +    result = [] +    with open('/proc/net/ip_mr_vif', 'r') as f: +        next(f) +        for line in f: +            result.append({ +                'Interface': line.split()[1], +                'PktsIn'   : int(line.split()[3]), +                'PktsOut'  : int(line.split()[5]), +                'BytesIn'  : int(line.split()[2]), +                'BytesOut' : int(line.split()[4]), +                'Local'    : _kernel_to_ip(line.split()[7]), +            }) +    return result + +def show_interface(raw: bool): +    if data := _process_mr_vif(): +        if raw: +            # Make the interface name the key for each row. +            table = {} +            for v in data: +                table[v.pop('Interface')] = v +            return json.loads(json.dumps(table)) +        # Make byte values human-readable for the table. +        arr = [] +        for x in data: +            arr.append({k: bytes_to_human(v) if k.startswith('Bytes') \ +                        else v for k, v in x.items()}) +        return tabulate.tabulate(arr, headers='keys') + + +if not _is_configured(): +    print_error('IGMP proxy is not configured.') +    sys.exit(0) +if not _is_running(): +    print_error('IGMP proxy is not running.') +    sys.exit(0) + + +if __name__ == "__main__": +    try: +        res = vyos.opmode.run(sys.modules[__name__]) +        if res: +            print(res) +    except (ValueError, vyos.opmode.Error) as e: +        print_error(e) +        sys.exit(1) diff --git a/src/op_mode/show_igmpproxy.py b/src/op_mode/show_igmpproxy.py deleted file mode 100755 index 4714e494b..000000000 --- a/src/op_mode/show_igmpproxy.py +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 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/>. - -# File: show_igmpproxy.py -# Purpose: -#    Display istatistics from IPv4 IGMP proxy. -#    Used by the "run show ip multicast" command tree. - -import sys -import jinja2 -import argparse -import ipaddress -import socket - -import vyos.config - -# Output Template for "show ip multicast interface" command -# -# Example: -# Interface  BytesIn      PktsIn       BytesOut     PktsOut      Local -# eth0       0.0b         0            0.0b         0            xxx.xxx.xxx.65 -# eth1       0.0b         0            0.0b         0            xxx.xxx.xx.201 -# eth0.3     0.0b         0            0.0b         0            xxx.xxx.x.7 -# tun1       0.0b         0            0.0b         0            xxx.xxx.xxx.2 -vif_out_tmpl = """ -{% for r in data %} -{{ "%-10s"|format(r.interface) }} {{ "%-12s"|format(r.bytes_in) }} {{ "%-12s"|format(r.pkts_in) }} {{ "%-12s"|format(r.bytes_out) }} {{ "%-12s"|format(r.pkts_out) }} {{ "%-15s"|format(r.loc) }} -{% endfor %} -""" - -# Output Template for "show ip multicast mfc" command -# -# Example: -# Group             Origin            In    Out           Pkts        Bytes        Wrong -# xxx.xxx.xxx.250   xxx.xx.xxx.75     -- -# xxx.xxx.xx.124    xx.xxx.xxx.26     -- -mfc_out_tmpl = """ -{% for r in data %} -{{ "%-15s"|format(r.group) }} {{ "%-15s"|format(r.origin) }} {{ "%-12s"|format(r.pkts) }} {{ "%-12s"|format(r.bytes) }} {{ "%-12s"|format(r.wrong) }} {{ "%-10s"|format(r.iif) }} {{ "%-20s"|format(r.oifs|join(', ')) }} -{% endfor %} -""" - -parser = argparse.ArgumentParser() -parser.add_argument("--interface", action="store_true", help="Interface Statistics") -parser.add_argument("--mfc", action="store_true", help="Multicast Forwarding Cache") - -def byte_string(size): -    # convert size to integer -    size = int(size) - -    # One Terrabyte -    s_TB = 1024 * 1024 * 1024 * 1024 -    # One Gigabyte -    s_GB = 1024 * 1024 * 1024 -    # One Megabyte -    s_MB = 1024 * 1024 -    # One Kilobyte -    s_KB = 1024 -    # One Byte -    s_B  = 1 - -    if size > s_TB: -        return str(round((size/s_TB), 2)) + 'TB' -    elif size > s_GB: -        return str(round((size/s_GB), 2)) + 'GB' -    elif size > s_MB: -        return str(round((size/s_MB), 2)) + 'MB' -    elif size > s_KB: -        return str(round((size/s_KB), 2)) + 'KB' -    else: -        return str(round((size/s_B), 2)) + 'b' - -    return None - -def kernel2ip(addr): -    """ -    Convert any given addr from Linux Kernel to a proper, IPv4 address -    using the correct host byte order. -    """ - -    # Convert from hex 'FE000A0A' to decimal '4261415434' -    addr = int(addr, 16) -    # Kernel ABI _always_ uses network byteorder -    addr = socket.ntohl(addr) - -    return ipaddress.IPv4Address( addr ) - -def do_mr_vif(): -    """ -    Read contents of file /proc/net/ip_mr_vif and print a more human -    friendly version to the command line. IPv4 addresses present as -    32bit integers in hex format are converted to IPv4 notation, too. -    """ - -    with open('/proc/net/ip_mr_vif', 'r') as f: -        lines = len(f.readlines()) -        if lines < 2: -            return None - -    result = { -        'data': [] -    } - -    # Build up table format string -    table_format = { -        'interface': 'Interface', -        'pkts_in'  : 'PktsIn', -        'pkts_out' : 'PktsOut', -        'bytes_in' : 'BytesIn', -        'bytes_out': 'BytesOut', -        'loc'      : 'Local' -    } -    result['data'].append(table_format) - -    # read and parse information from /proc filesystema -    with open('/proc/net/ip_mr_vif', 'r') as f: -        header_line = next(f) -        for line in f: -            data = { -                'interface': line.split()[1], -                'pkts_in'  : line.split()[3], -                'pkts_out' : line.split()[5], - -                # convert raw byte number to something more human readable -                # Note: could be replaced by Python3 hurry.filesize module -                'bytes_in' : byte_string( line.split()[2] ), -                'bytes_out': byte_string( line.split()[4] ), - -                # convert IP address from hex 'FE000A0A' to decimal '4261415434' -                'loc'      : kernel2ip( line.split()[7] ), -            } -            result['data'].append(data) - -    return result - -def do_mr_mfc(): -    """ -    Read contents of file /proc/net/ip_mr_cache and print a more human -    friendly version to the command line. IPv4 addresses present as -    32bit integers in hex format are converted to IPv4 notation, too. -    """ - -    with open('/proc/net/ip_mr_cache', 'r') as f: -        lines = len(f.readlines()) -        if lines < 2: -            return None - -    # We need this to convert from interface index to a real interface name -    # Thus we also skip the format identifier on list index 0 -    vif = do_mr_vif()['data'][1:] - -    result = { -        'data': [] -    } - -    # Build up table format string -    table_format = { -        'group' : 'Group', -        'origin': 'Origin', -        'iif'   : 'In', -        'oifs'  : ['Out'], -        'pkts'  : 'Pkts', -        'bytes' : 'Bytes', -        'wrong' : 'Wrong' -    } -    result['data'].append(table_format) - -    # read and parse information from /proc filesystem -    with open('/proc/net/ip_mr_cache', 'r') as f: -        header_line = next(f) -        for line in f: -            data = { -                # convert IP address from hex 'FE000A0A' to decimal '4261415434' -                'group' : kernel2ip( line.split()[0] ), -                'origin': kernel2ip( line.split()[1] ), - -                'iif'   : '--', -                'pkts'  : '', -                'bytes' : '', -                'wrong' : '', -                'oifs'  : [] -            } - -            iif = int( line.split()[2] ) -            if not ((iif == -1) or (iif == 65535)): -                data['pkts']  = line.split()[3] -                data['bytes'] = byte_string( line.split()[4] ) -                data['wrong'] = line.split()[5] - -                # convert index to real interface name -                data['iif']  = vif[iif]['interface'] - -                # convert each output interface index to a real interface name -                for oif in line.split()[6:]: -                    idx = int( oif.split(':')[0] ) -                    data['oifs'].append( vif[idx]['interface'] ) - -            result['data'].append(data) - -    return result - -if __name__ == '__main__': -    args = parser.parse_args() - -    # Do nothing if service is not configured -    c = vyos.config.Config() -    if not c.exists_effective('protocols igmp-proxy'): -        print("IGMP proxy is not configured") -        sys.exit(0) - -    if args.interface: -        data = do_mr_vif() -        if data: -            tmpl = jinja2.Template(vif_out_tmpl) -            print(tmpl.render(data)) - -        sys.exit(0) -    elif args.mfc: -        data = do_mr_mfc() -        if data: -            tmpl = jinja2.Template(mfc_out_tmpl) -            print(tmpl.render(data)) - -        sys.exit(0) -    else: -        parser.print_help() -        sys.exit(1) - diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py index fc63b0100..b320a529e 100755 --- a/src/services/api/graphql/generate/schema_from_op_mode.py +++ b/src/services/api/graphql/generate/schema_from_op_mode.py @@ -25,16 +25,17 @@ from inspect import signature, getmembers, isfunction, isclass, getmro  from jinja2 import Template  from vyos.defaults import directories +from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name  from vyos.util import load_as_module  if __package__ is None or __package__ == '':      sys.path.append("/usr/libexec/vyos/services/api") -    from graphql.libs.op_mode import is_op_mode_function_name, is_show_function_name +    from graphql.libs.op_mode import is_show_function_name      from graphql.libs.op_mode import snake_to_pascal_case, map_type_name      from vyos.config import Config      from vyos.configdict import dict_merge      from vyos.xml import defaults  else: -    from .. libs.op_mode import is_op_mode_function_name, is_show_function_name +    from .. libs.op_mode import is_show_function_name      from .. libs.op_mode import snake_to_pascal_case, map_type_name      from .. import state diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 87ea59c43..8254e22b1 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -15,7 +15,7 @@  from importlib import import_module  from typing import Any, Dict, Optional -from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake +from ariadne import ObjectType, convert_camel_case_to_snake  from graphql import GraphQLResolveInfo  from makefun import with_signature @@ -45,7 +45,6 @@ def make_mutation_resolver(mutation_name, class_name, session_func):      func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'      @mutation.field(mutation_name) -    @convert_kwargs_to_snake_case      @with_signature(func_sig, func_name=resolver_name)      async def func_impl(*args, **kwargs):          try: diff --git a/src/services/api/graphql/graphql/queries.py b/src/services/api/graphql/graphql/queries.py index 1ad586428..daccc19b2 100644 --- a/src/services/api/graphql/graphql/queries.py +++ b/src/services/api/graphql/graphql/queries.py @@ -15,7 +15,7 @@  from importlib import import_module  from typing import Any, Dict, Optional -from ariadne import ObjectType, convert_kwargs_to_snake_case, convert_camel_case_to_snake +from ariadne import ObjectType, convert_camel_case_to_snake  from graphql import GraphQLResolveInfo  from makefun import with_signature @@ -45,7 +45,6 @@ def make_query_resolver(query_name, class_name, session_func):      func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Optional[Dict]=None)'      @query.field(query_name) -    @convert_kwargs_to_snake_case      @with_signature(func_sig, func_name=resolver_name)      async def func_impl(*args, **kwargs):          try: diff --git a/src/services/api/graphql/libs/op_mode.py b/src/services/api/graphql/libs/op_mode.py index c1eb493db..c553bbd67 100644 --- a/src/services/api/graphql/libs/op_mode.py +++ b/src/services/api/graphql/libs/op_mode.py @@ -29,11 +29,6 @@ def load_op_mode_as_module(name: str):      name = os.path.splitext(name)[0].replace('-', '_')      return load_as_module(name, path) -def is_op_mode_function_name(name): -    if re.match(r"^(show|clear|reset|restart|add|delete)", name): -        return True -    return False -  def is_show_function_name(name):      if re.match(r"^show", name):          return True diff --git a/src/services/api/graphql/session/errors/op_mode_errors.py b/src/services/api/graphql/session/errors/op_mode_errors.py index 4029fd0a1..18d555f2d 100644 --- a/src/services/api/graphql/session/errors/op_mode_errors.py +++ b/src/services/api/graphql/session/errors/op_mode_errors.py @@ -1,9 +1,8 @@ - -  op_mode_err_msg = {      "UnconfiguredSubsystem": "subsystem is not configured or not running",      "DataUnavailable": "data currently unavailable",      "PermissionDenied": "client does not have permission", +    "InsufficientResources": "insufficient system resources",      "IncorrectValue": "argument value is incorrect",      "UnsupportedOperation": "operation is not supported (yet)",  } @@ -11,6 +10,7 @@ op_mode_err_msg = {  op_mode_err_code = {      "UnconfiguredSubsystem": 2000,      "DataUnavailable": 2001, +    "InsufficientResources": 2002,      "PermissionDenied": 1003,      "IncorrectValue": 1002,      "UnsupportedOperation": 1004, | 
