summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linit-j2.yml19
-rw-r--r--interface-definitions/nat_cgnat.xml.in6
-rw-r--r--op-mode-definitions/monitor-log.xml.in41
-rw-r--r--op-mode-definitions/show-log.xml.in41
-rw-r--r--python/vyos/configquery.py60
-rw-r--r--python/vyos/utils/dict.py7
-rw-r--r--python/vyos/utils/error.py24
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py30
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: