diff options
-rw-r--r-- | .github/workflows/linit-j2.yml | 19 | ||||
-rw-r--r-- | interface-definitions/nat_cgnat.xml.in | 6 | ||||
-rw-r--r-- | op-mode-definitions/monitor-log.xml.in | 41 | ||||
-rw-r--r-- | op-mode-definitions/show-log.xml.in | 41 | ||||
-rw-r--r-- | python/vyos/configquery.py | 60 | ||||
-rw-r--r-- | python/vyos/utils/dict.py | 7 | ||||
-rw-r--r-- | python/vyos/utils/error.py | 24 | ||||
-rwxr-xr-x | src/conf_mode/nat_cgnat.py | 30 |
8 files changed, 207 insertions, 21 deletions
diff --git a/.github/workflows/linit-j2.yml b/.github/workflows/linit-j2.yml deleted file mode 100644 index f3dc497d2..000000000 --- a/.github/workflows/linit-j2.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: J2 Lint - -on: - pull_request: - branches: - - current - - crux - - equuleus - - sagitta - -permissions: - pull-requests: write - contents: read - -jobs: - j2lint: - uses: vyos/.github/.github/workflows/lint-j2.yml@feature/T6349-reusable-workflows - secrets: inherit diff --git a/interface-definitions/nat_cgnat.xml.in b/interface-definitions/nat_cgnat.xml.in index fce5e655d..71f4d67b0 100644 --- a/interface-definitions/nat_cgnat.xml.in +++ b/interface-definitions/nat_cgnat.xml.in @@ -8,6 +8,12 @@ <priority>221</priority> </properties> <children> + <leafNode name="log-allocation"> + <properties> + <help>Log IP address and port allocation</help> + <valueless/> + </properties> + </leafNode> <node name="pool"> <properties> <help>External and internal pool parameters</help> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index 559952e25..a2d5d924a 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -359,6 +359,47 @@ </properties> <command>journalctl --no-hostname --boot --follow --unit keepalived.service</command> </leafNode> + <node name="wireless"> + <properties> + <help>Monitor last lines of Wireless interface log</help> + </properties> + <children> + <node name="wpa-supplicant"> + <properties> + <help>Monitor last lines of WPA supplicant</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific wireless interface supplicant</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --follow --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command> + </tagNode> + </children> + </node> + <node name="hostapd"> + <properties> + <help>Monitor last lines of host access point daemon</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --follow --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Monitor last lines of specific host access point interface</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --follow --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command> + </tagNode> + </children> + </node> + </children> + </node> </children> </node> </children> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index c3aa324ba..7ae3b890b 100644 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -762,6 +762,47 @@ </properties> <command>journalctl --no-hostname --boot --unit keepalived.service</command> </leafNode> + <node name="wireless"> + <properties> + <help>Show log for Wireless interface</help> + </properties> + <children> + <node name="wpa-supplicant"> + <properties> + <help>Show log for WPA supplicant</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "wpa_supplicant@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show log for specific wireless interface supplicant</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "station" ]]; then journalctl --no-hostname --boot --unit "wpa_supplicant@$6.service"; else echo "Wireless interface $6 not configured as station!"; fi</command> + </tagNode> + </children> + </node> + <node name="hostapd"> + <properties> + <help>Show log for host access point daemon</help> + </properties> + <command>if cli-shell-api existsActive interfaces wireless; then journalctl --no-hostname --boot --unit "hostapd@*.service"; else echo "No wireless interface configured!"; fi</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show log for specific host access point daemon interface</help> + <completionHelp> + <path>interfaces wireless</path> + </completionHelp> + </properties> + <command>if [[ $(cli-shell-api returnActiveValue interfaces wireless $6 type) == "access-point" ]]; then journalctl --no-hostname --boot --unit "hostapd@$6.service"; else echo "Wireless interface $6 not configured as access-point!"; fi</command> + </tagNode> + </children> + </node> + </children> + </node> <leafNode name="webproxy"> <properties> <help>Show log for Webproxy</help> diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py index 71ad5b4f0..5d6ca9be9 100644 --- a/python/vyos/configquery.py +++ b/python/vyos/configquery.py @@ -1,4 +1,4 @@ -# Copyright 2021-2023 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2021-2024 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 @@ -19,6 +19,8 @@ settings from op mode, and execution of arbitrary op mode commands. ''' import os +import json +import subprocess from vyos.utils.process import STDOUT from vyos.utils.process import popen @@ -27,6 +29,14 @@ from vyos.utils.boot import boot_configuration_complete from vyos.config import Config from vyos.configsource import ConfigSourceSession, ConfigSourceString from vyos.defaults import directories +from vyos.configtree import ConfigTree +from vyos.utils.dict import embed_dict +from vyos.utils.dict import get_sub_dict +from vyos.utils.dict import mangle_dict_keys +from vyos.utils.error import cli_shell_api_err +from vyos.xml_ref import multi_to_list +from vyos.xml_ref import is_tag +from vyos.base import Warning config_file = os.path.join(directories['config'], 'config.boot') @@ -133,4 +143,50 @@ def query_context(config_query_class=CliShellApiConfigQuery, run = op_run_class() return query, run - +def verify_mangling(key_mangling): + if not (isinstance(key_mangling, tuple) and + len(key_mangling) == 2 and + isinstance(key_mangling[0], str) and + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + +def op_mode_run(cmd): + """ low-level to avoid overhead """ + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + out = p.stdout.read() + p.wait() + return p.returncode, out.decode() + +def op_mode_config_dict(path=None, key_mangling=None, + no_tag_node_value_mangle=False, + no_multi_convert=False, get_first_key=False): + + if path is None: + path = [] + command = ['/bin/cli-shell-api', '--show-active-only', 'showConfig'] + + rc, out = op_mode_run(command + path) + if rc == cli_shell_api_err.VYOS_EMPTY_CONFIG: + out = '' + if rc == cli_shell_api_err.VYOS_INVALID_PATH: + Warning(out) + return {} + + ct = ConfigTree(out) + d = json.loads(ct.to_json()) + # cli-shell-api output includes last path component if tag node + if is_tag(path): + config_dict = embed_dict(path[:-1], d) + else: + config_dict = embed_dict(path, d) + + if not no_multi_convert: + config_dict = multi_to_list([], config_dict) + + if key_mangling is not None: + verify_mangling(key_mangling) + config_dict = mangle_dict_keys(config_dict, + key_mangling[0], key_mangling[1], + no_tag_node_value_mangle=no_tag_node_value_mangle) + + return get_sub_dict(config_dict, path, get_first_key=get_first_key) diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py index d36b6fcfb..062ab9c81 100644 --- a/python/vyos/utils/dict.py +++ b/python/vyos/utils/dict.py @@ -307,6 +307,13 @@ def dict_to_paths(d: dict) -> list: for r in func(d, []): yield r +def embed_dict(p: list[str], d: dict) -> dict: + path = p.copy() + ret = d + while path: + ret = {path.pop(): ret} + return ret + def check_mutually_exclusive_options(d, keys, required=False): """ Checks if a dict has at most one or only one of mutually exclusive keys. diff --git a/python/vyos/utils/error.py b/python/vyos/utils/error.py new file mode 100644 index 000000000..8d4709bff --- /dev/null +++ b/python/vyos/utils/error.py @@ -0,0 +1,24 @@ +# Copyright 2024 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/>. + +from enum import IntEnum + +class cli_shell_api_err(IntEnum): + """ vyatta-cfg/src/vyos-errors.h """ + VYOS_SUCCESS = 0 + VYOS_GENERAL_FAILURE = 1 + VYOS_INVALID_PATH = 2 + VYOS_EMPTY_CONFIG = 3 + VYOS_CONFIG_PARSE_ERROR = 4 diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py index d429f6e21..cb336a35c 100755 --- a/src/conf_mode/nat_cgnat.py +++ b/src/conf_mode/nat_cgnat.py @@ -16,9 +16,11 @@ import ipaddress import jmespath +import logging import os from sys import exit +from logging.handlers import SysLogHandler from vyos.config import Config from vyos.template import render @@ -32,6 +34,18 @@ airbag.enable() nftables_cgnat_config = '/run/nftables-cgnat.nft' +# Logging +logger = logging.getLogger('cgnat') +logger.setLevel(logging.DEBUG) + +syslog_handler = SysLogHandler(address="/dev/log") +syslog_handler.setLevel(logging.INFO) + +formatter = logging.Formatter('%(name)s: %(message)s') +syslog_handler.setFormatter(formatter) + +logger.addHandler(syslog_handler) + class IPOperations: def __init__(self, ip_prefix: str): @@ -356,6 +370,22 @@ def apply(config): return None cmd(f'nft --file {nftables_cgnat_config}') + # Logging allocations + if 'log_allocation' in config: + allocations = config['proto_map_elements'] + allocations = allocations.split(',') + for allocation in allocations: + try: + # Split based on the delimiters used in the nft data format + internal_host, rest = allocation.split(' : ') + external_host, port_range = rest.split(' . ') + # Log the parsed data + logger.info( + f"Internal host: {internal_host.lstrip()}, external host: {external_host}, Port range: {port_range}") + except ValueError as e: + # Log error message + logger.error(f"Error processing line '{allocation}': {e}") + if __name__ == '__main__': try: |