summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j217
-rw-r--r--interface-definitions/include/haproxy/logging.xml.i10
-rw-r--r--interface-definitions/load-balancing_reverse-proxy.xml.in3
-rw-r--r--python/vyos/utils/__init__.py1
-rw-r--r--python/vyos/utils/locking.py115
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_reverse-proxy.py41
-rwxr-xr-xsrc/helpers/vyos_net_name143
-rwxr-xr-xsrc/op_mode/nat.py56
-rwxr-xr-xsrc/op_mode/powerctrl.py6
9 files changed, 308 insertions, 84 deletions
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index c18a998b8..5137966c1 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -1,8 +1,6 @@
### Autogenerated by load-balancing_reverse-proxy.py ###
global
- log /dev/log local0
- log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
@@ -11,6 +9,11 @@ global
daemon
{% if global_parameters is vyos_defined %}
+{% if global_parameters.logging is vyos_defined %}
+{% for facility, facility_config in global_parameters.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
{% if global_parameters.max_connections is vyos_defined %}
maxconn {{ global_parameters.max_connections }}
{% endif %}
@@ -67,6 +70,11 @@ frontend {{ front }}
{% if front_config.redirect_http_to_https is vyos_defined %}
http-request redirect scheme https unless { ssl_fc }
{% endif %}
+{% if front_config.logging is vyos_defined %}
+{% for facility, facility_config in front_config.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
mode {{ front_config.mode }}
{% if front_config.tcp_request.inspect_delay is vyos_defined %}
tcp-request inspect-delay {{ front_config.tcp_request.inspect_delay }}
@@ -166,6 +174,11 @@ backend {{ back }}
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
{% endif %}
+{% if back_config.logging is vyos_defined %}
+{% for facility, facility_config in back_config.logging.facility.items() %}
+ log /dev/log {{ facility }} {{ facility_config.level }}
+{% endfor %}
+{% endif %}
mode {{ back_config.mode }}
{% if back_config.http_response_headers is vyos_defined %}
{% for header, header_config in back_config.http_response_headers.items() %}
diff --git a/interface-definitions/include/haproxy/logging.xml.i b/interface-definitions/include/haproxy/logging.xml.i
new file mode 100644
index 000000000..e0af54fa4
--- /dev/null
+++ b/interface-definitions/include/haproxy/logging.xml.i
@@ -0,0 +1,10 @@
+<!-- include start from haproxy/logging.xml.i -->
+<node name="logging">
+ <properties>
+ <help>Logging parameters</help>
+ </properties>
+ <children>
+ #include <include/syslog-facility.xml.i>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_reverse-proxy.xml.in
index 1a432be6d..18274622c 100644
--- a/interface-definitions/load-balancing_reverse-proxy.xml.in
+++ b/interface-definitions/load-balancing_reverse-proxy.xml.in
@@ -36,6 +36,7 @@
</leafNode>
#include <include/generic-description.xml.i>
#include <include/listen-address.xml.i>
+ #include <include/haproxy/logging.xml.i>
#include <include/haproxy/mode.xml.i>
#include <include/port-number.xml.i>
#include <include/haproxy/rule-frontend.xml.i>
@@ -91,6 +92,7 @@
<defaultValue>round-robin</defaultValue>
</leafNode>
#include <include/generic-description.xml.i>
+ #include <include/haproxy/logging.xml.i>
#include <include/haproxy/mode.xml.i>
#include <include/haproxy/http-response-headers.xml.i>
<node name="http-check">
@@ -254,6 +256,7 @@
<help>Global perfomance parameters and limits</help>
</properties>
<children>
+ #include <include/haproxy/logging.xml.i>
<leafNode name="max-connections">
<properties>
<help>Maximum allowed connections</help>
diff --git a/python/vyos/utils/__init__.py b/python/vyos/utils/__init__.py
index 90620071b..3759b2125 100644
--- a/python/vyos/utils/__init__.py
+++ b/python/vyos/utils/__init__.py
@@ -25,6 +25,7 @@ from vyos.utils import file
from vyos.utils import io
from vyos.utils import kernel
from vyos.utils import list
+from vyos.utils import locking
from vyos.utils import misc
from vyos.utils import network
from vyos.utils import permission
diff --git a/python/vyos/utils/locking.py b/python/vyos/utils/locking.py
new file mode 100644
index 000000000..63cb1a816
--- /dev/null
+++ b/python/vyos/utils/locking.py
@@ -0,0 +1,115 @@
+# 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/>.
+
+import fcntl
+import re
+import time
+from pathlib import Path
+
+
+class LockTimeoutError(Exception):
+ """Custom exception raised when lock acquisition times out."""
+
+ pass
+
+
+class InvalidLockNameError(Exception):
+ """Custom exception raised when the lock name is invalid."""
+
+ pass
+
+
+class Lock:
+ """Lock class to acquire and release a lock file"""
+
+ def __init__(self, lock_name: str) -> None:
+ """Lock class constructor
+
+ Args:
+ lock_name (str): Name of the lock file
+
+ Raises:
+ InvalidLockNameError: If the lock name is invalid
+ """
+ # Validate lock name
+ if not re.match(r'^[a-zA-Z0-9_\-]+$', lock_name):
+ raise InvalidLockNameError(f'Invalid lock name: {lock_name}')
+
+ self.__lock_dir = Path('/run/vyos/lock')
+ self.__lock_dir.mkdir(parents=True, exist_ok=True)
+
+ self.__lock_file_path: Path = self.__lock_dir / f'{lock_name}.lock'
+ self.__lock_file = None
+
+ self._is_locked = False
+
+ def __del__(self) -> None:
+ """Ensure the lock file is removed when the object is deleted"""
+ self.release()
+
+ @property
+ def is_locked(self) -> bool:
+ """Check if the lock is acquired
+
+ Returns:
+ bool: True if the lock is acquired, False otherwise
+ """
+ return self._is_locked
+
+ def __unlink_lockfile(self) -> None:
+ """Remove the lock file if it is not currently locked."""
+ try:
+ with self.__lock_file_path.open('w') as f:
+ fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ self.__lock_file_path.unlink(missing_ok=True)
+ except IOError:
+ # If we cannot acquire the lock, it means another process has it, so we do nothing.
+ pass
+
+ def acquire(self, timeout: int = 0) -> None:
+ """Acquire a lock file
+
+ Args:
+ timeout (int, optional): A time to wait for lock. Defaults to 0.
+
+ Raises:
+ LockTimeoutError: If lock could not be acquired within timeout
+ """
+ start_time: float = time.time()
+ while True:
+ try:
+ self.__lock_file = self.__lock_file_path.open('w')
+ fcntl.flock(self.__lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ self._is_locked = True
+ return
+ except IOError:
+ if timeout > 0 and (time.time() - start_time) >= timeout:
+ if self.__lock_file:
+ self.__lock_file.close()
+ raise LockTimeoutError(
+ f'Could not acquire lock within {timeout} seconds'
+ )
+ time.sleep(0.1)
+
+ def release(self) -> None:
+ """Release a lock file"""
+ if self.__lock_file and self._is_locked:
+ try:
+ fcntl.flock(self.__lock_file, fcntl.LOCK_UN)
+ self._is_locked = False
+ finally:
+ self.__lock_file.close()
+ self.__lock_file = None
+ self.__unlink_lockfile()
diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
index aa796f59f..db43a78ec 100755
--- a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
@@ -458,6 +458,47 @@ class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
config = read_file(HAPROXY_CONF)
self.assertIn(f'option smtpchk', config)
+ def test_09_lb_reverse_proxy_logging(self):
+ # Setup base
+ self.base_config()
+ self.cli_commit()
+
+ # Ensure default logging configuration is present
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local0', config)
+ self.assertIn('log /dev/log local1 notice', config)
+
+ # Test global-parameters logging options
+ self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local1', 'level', 'err'])
+ self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local2', 'level', 'warning'])
+ self.cli_commit()
+
+ # Test global logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local1 err', config)
+ self.assertIn('log /dev/log local2 warning', config)
+
+ # Test backend logging options
+ backend_path = base_path + ['backend', 'bk-01']
+ self.cli_set(backend_path + ['logging', 'facility', 'local3', 'level', 'debug'])
+ self.cli_set(backend_path + ['logging', 'facility', 'local4', 'level', 'info'])
+ self.cli_commit()
+
+ # Test backend logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local3 debug', config)
+ self.assertIn('log /dev/log local4 info', config)
+
+ # Test service logging options
+ service_path = base_path + ['service', 'https_front']
+ self.cli_set(service_path + ['logging', 'facility', 'local5', 'level', 'notice'])
+ self.cli_set(service_path + ['logging', 'facility', 'local6', 'level', 'crit'])
+ self.cli_commit()
+
+ # Test service logging parameters are generated in configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('log /dev/log local5 notice', config)
+ self.assertIn('log /dev/log local6 crit', config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name
index 518e204f9..f5de182c6 100755
--- a/src/helpers/vyos_net_name
+++ b/src/helpers/vyos_net_name
@@ -18,42 +18,35 @@ import os
import re
import time
import logging
+import logging.handlers
import tempfile
-import threading
+from pathlib import Path
from sys import argv
from vyos.configtree import ConfigTree
from vyos.defaults import directories
from vyos.utils.process import cmd
from vyos.utils.boot import boot_configuration_complete
+from vyos.utils.locking import Lock
from vyos.migrate import ConfigMigrate
+# Define variables
vyos_udev_dir = directories['vyos_udev_dir']
-vyos_log_dir = '/run/udev/log'
-vyos_log_file = os.path.join(vyos_log_dir, 'vyos-net-name')
-
config_path = '/opt/vyatta/etc/config/config.boot'
-lock = threading.Lock()
-
-try:
- os.mkdir(vyos_log_dir)
-except FileExistsError:
- pass
-
-logging.basicConfig(filename=vyos_log_file, level=logging.DEBUG)
def is_available(intfs: dict, intf_name: str) -> bool:
- """ Check if interface name is already assigned
- """
+ """Check if interface name is already assigned"""
if intf_name in list(intfs.values()):
return False
return True
+
def find_available(intfs: dict, prefix: str) -> str:
- """ Find lowest indexed iterface name that is not assigned
- """
- index_list = [int(x.replace(prefix, '')) for x in list(intfs.values()) if prefix in x]
+ """Find lowest indexed iterface name that is not assigned"""
+ index_list = [
+ int(x.replace(prefix, '')) for x in list(intfs.values()) if prefix in x
+ ]
index_list.sort()
# find 'holes' in list, if any
missing = sorted(set(range(index_list[0], index_list[-1])) - set(index_list))
@@ -62,21 +55,22 @@ def find_available(intfs: dict, prefix: str) -> str:
return f'{prefix}{len(index_list)}'
+
def mod_ifname(ifname: str) -> str:
- """ Check interface with names eX and return ifname on the next format eth{ifindex} - 2
- """
- if re.match("^e[0-9]+$", ifname):
- intf = ifname.split("e")
+ """Check interface with names eX and return ifname on the next format eth{ifindex} - 2"""
+ if re.match('^e[0-9]+$', ifname):
+ intf = ifname.split('e')
if intf[1]:
if int(intf[1]) >= 2:
- return "eth" + str(int(intf[1]) - 2)
+ return 'eth' + str(int(intf[1]) - 2)
else:
- return "eth" + str(intf[1])
+ return 'eth' + str(intf[1])
return ifname
+
def get_biosdevname(ifname: str) -> str:
- """ Use legacy vyatta-biosdevname to query for name
+ """Use legacy vyatta-biosdevname to query for name
This is carried over for compatability only, and will likely be dropped
going forward.
@@ -95,11 +89,12 @@ def get_biosdevname(ifname: str) -> str:
try:
biosname = cmd(f'/sbin/biosdevname --policy all_ethN -i {ifname}')
except Exception as e:
- logging.error(f'biosdevname error: {e}')
+ logger.error(f'biosdevname error: {e}')
biosname = ''
return intf if biosname == '' else biosname
+
def leave_rescan_hint(intf_name: str, hwid: str):
"""Write interface information reported by udev
@@ -112,18 +107,18 @@ def leave_rescan_hint(intf_name: str, hwid: str):
except FileExistsError:
pass
except Exception as e:
- logging.critical(f"Error creating rescan hint directory: {e}")
+ logger.critical(f'Error creating rescan hint directory: {e}')
exit(1)
try:
with open(os.path.join(vyos_udev_dir, intf_name), 'w') as f:
f.write(hwid)
except OSError as e:
- logging.critical(f"OSError {e}")
+ logger.critical(f'OSError {e}')
+
def get_configfile_interfaces() -> dict:
- """Read existing interfaces from config file
- """
+ """Read existing interfaces from config file"""
interfaces: dict = {}
if not os.path.isfile(config_path):
@@ -134,14 +129,14 @@ def get_configfile_interfaces() -> dict:
with open(config_path) as f:
config_file = f.read()
except OSError as e:
- logging.critical(f"OSError {e}")
+ logger.critical(f'OSError {e}')
exit(1)
try:
config = ConfigTree(config_file)
except Exception:
try:
- logging.debug(f"updating component version string syntax")
+ logger.debug('updating component version string syntax')
# this will update the component version string syntax,
# required for updates 1.2 --> 1.3/1.4
with tempfile.NamedTemporaryFile() as fp:
@@ -157,7 +152,8 @@ def get_configfile_interfaces() -> dict:
config = ConfigTree(config_file)
except Exception as e:
- logging.critical(f"ConfigTree error: {e}")
+ logger.critical(f'ConfigTree error: {e}')
+ exit(1)
base = ['interfaces', 'ethernet']
if config.exists(base):
@@ -165,11 +161,13 @@ def get_configfile_interfaces() -> dict:
for intf in eth_intfs:
path = base + [intf, 'hw-id']
if not config.exists(path):
- logging.warning(f"no 'hw-id' entry for {intf}")
+ logger.warning(f"no 'hw-id' entry for {intf}")
continue
hwid = config.return_value(path)
if hwid in list(interfaces):
- logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}")
+ logger.warning(
+ f'multiple entries for {hwid}: {interfaces[hwid]}, {intf}'
+ )
continue
interfaces[hwid] = intf
@@ -179,21 +177,23 @@ def get_configfile_interfaces() -> dict:
for intf in wlan_intfs:
path = base + [intf, 'hw-id']
if not config.exists(path):
- logging.warning(f"no 'hw-id' entry for {intf}")
+ logger.warning(f"no 'hw-id' entry for {intf}")
continue
hwid = config.return_value(path)
if hwid in list(interfaces):
- logging.warning(f"multiple entries for {hwid}: {interfaces[hwid]}, {intf}")
+ logger.warning(
+ f'multiple entries for {hwid}: {interfaces[hwid]}, {intf}'
+ )
continue
interfaces[hwid] = intf
- logging.debug(f"config file entries: {interfaces}")
+ logger.debug(f'config file entries: {interfaces}')
return interfaces
+
def add_assigned_interfaces(intfs: dict):
- """Add interfaces found by previous invocation of udev rule
- """
+ """Add interfaces found by previous invocation of udev rule"""
if not os.path.isdir(vyos_udev_dir):
return
@@ -203,55 +203,74 @@ def add_assigned_interfaces(intfs: dict):
with open(path) as f:
hwid = f.read().rstrip()
except OSError as e:
- logging.error(f"OSError {e}")
+ logger.error(f'OSError {e}')
continue
intfs[hwid] = intf
+
def on_boot_event(intf_name: str, hwid: str, predefined: str = '') -> str:
- """Called on boot by vyos-router: 'coldplug' in vyatta_net_name
- """
- logging.info(f"lookup {intf_name}, {hwid}")
+ """Called on boot by vyos-router: 'coldplug' in vyatta_net_name"""
+ logger.info(f'lookup {intf_name}, {hwid}')
interfaces = get_configfile_interfaces()
- logging.debug(f"config file interfaces are {interfaces}")
+ logger.debug(f'config file interfaces are {interfaces}')
if hwid in list(interfaces):
- logging.info(f"use mapping from config file: '{hwid}' -> '{interfaces[hwid]}'")
+ logger.info(f"use mapping from config file: '{hwid}' -> '{interfaces[hwid]}'")
return interfaces[hwid]
add_assigned_interfaces(interfaces)
- logging.debug(f"adding assigned interfaces: {interfaces}")
+ logger.debug(f'adding assigned interfaces: {interfaces}')
if predefined:
newname = predefined
- logging.info(f"predefined interface name for '{intf_name}' is '{newname}'")
+ logger.info(f"predefined interface name for '{intf_name}' is '{newname}'")
else:
newname = get_biosdevname(intf_name)
- logging.info(f"biosdevname returned '{newname}' for '{intf_name}'")
+ logger.info(f"biosdevname returned '{newname}' for '{intf_name}'")
if not is_available(interfaces, newname):
prefix = re.sub(r'\d+$', '', newname)
newname = find_available(interfaces, prefix)
- logging.info(f"new name for '{intf_name}' is '{newname}'")
+ logger.info(f"new name for '{intf_name}' is '{newname}'")
leave_rescan_hint(newname, hwid)
return newname
+
def hotplug_event():
# Not yet implemented, since interface-rescan will only be run on boot.
pass
-if len(argv) > 3:
- predef_name = argv[3]
-else:
- predef_name = ''
-
-lock.acquire()
-if not boot_configuration_complete():
- res = on_boot_event(argv[1], argv[2], predefined=predef_name)
- logging.debug(f"on boot, returned name is {res}")
- print(res)
-else:
- logging.debug("boot configuration complete")
-lock.release()
+
+if __name__ == '__main__':
+ # Set up logging to syslog
+ syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
+ formatter = logging.Formatter(f'{Path(__file__).name}: %(message)s')
+ syslog_handler.setFormatter(formatter)
+
+ logger = logging.getLogger()
+ logger.addHandler(syslog_handler)
+ logger.setLevel(logging.DEBUG)
+
+ logger.debug(f'Started with arguments: {argv}')
+
+ if len(argv) > 3:
+ predef_name = argv[3]
+ else:
+ predef_name = ''
+
+ lock = Lock('vyos_net_name')
+ # Wait 60 seconds for other running scripts to finish
+ lock.acquire(60)
+
+ if not boot_configuration_complete():
+ res = on_boot_event(argv[1], argv[2], predefined=predef_name)
+ logger.debug(f'on boot, returned name is {res}')
+ print(res)
+ else:
+ logger.debug('boot configuration complete')
+
+ lock.release()
+ logger.debug('Finished')
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 16a545cda..c6cf4770a 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -31,6 +31,7 @@ from vyos.utils.dict import dict_search
ArgDirection = typing.Literal['source', 'destination']
ArgFamily = typing.Literal['inet', 'inet6']
+
def _get_xml_translation(direction, family, address=None):
"""
Get conntrack XML output --src-nat|--dst-nat
@@ -99,22 +100,35 @@ def _get_raw_translation(direction, family, address=None):
def _get_formatted_output_rules(data, direction, family):
- def _get_ports_for_output(my_dict):
- # Get and insert all configured ports or port ranges into output string
- for index, port in enumerate(my_dict['set']):
- if 'range' in str(my_dict['set'][index]):
- output = my_dict['set'][index]['range']
- output = '-'.join(map(str, output))
- else:
- output = str(port)
- if index == 0:
- output = str(output)
- else:
- output = ','.join([output,output])
- # Handle case where configured ports are a negated list
- if my_dict['op'] == '!=':
- output = '!' + output
- return(output)
+
+
+ def _get_ports_for_output(rules):
+ """
+ Return: string of configured ports
+ """
+ ports = []
+ if 'set' in rules:
+ for index, port in enumerate(rules['set']):
+ if 'range' in str(rules['set'][index]):
+ output = rules['set'][index]['range']
+ output = '-'.join(map(str, output))
+ else:
+ output = str(port)
+ ports.append(output)
+ # When NAT rule contains port range or single port
+ # JSON will not contain keyword 'set'
+ elif 'range' in rules:
+ output = rules['range']
+ output = '-'.join(map(str, output))
+ ports.append(output)
+ else:
+ output = rules['right']
+ ports.append(str(output))
+ result = ','.join(ports)
+ # Handle case where ports in NAT rule are negated
+ if rules['op'] == '!=':
+ result = '!' + result
+ return(result)
# Add default values before loop
sport, dport, proto = 'any', 'any', 'any'
@@ -132,7 +146,10 @@ def _get_formatted_output_rules(data, direction, family):
if jmespath.search('rule.expr[*].match.left.meta', rule) else 'any'
for index, match in enumerate(jmespath.search('rule.expr[*].match', rule)):
if 'payload' in match['left']:
- if isinstance(match['right'], dict) and ('prefix' in match['right'] or 'set' in match['right']):
+ # Handle NAT rule containing comma-seperated list of ports
+ if (isinstance(match['right'], dict) and
+ ('prefix' in match['right'] or 'set' in match['right'] or
+ 'range' in match['right'])):
# Merge dict src/dst l3_l4 parameters
my_dict = {**match['left']['payload'], **match['right']}
my_dict['op'] = match['op']
@@ -146,6 +163,7 @@ def _get_formatted_output_rules(data, direction, family):
sport = _get_ports_for_output(my_dict)
elif my_dict['field'] == 'dport':
dport = _get_ports_for_output(my_dict)
+ # Handle NAT rule containing a single port
else:
field = jmespath.search('left.payload.field', match)
if field == 'saddr':
@@ -153,9 +171,9 @@ def _get_formatted_output_rules(data, direction, family):
elif field == 'daddr':
daddr = match.get('right')
elif field == 'sport':
- sport = match.get('right')
+ sport = _get_ports_for_output(match)
elif field == 'dport':
- dport = match.get('right')
+ dport = _get_ports_for_output(match)
else:
saddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
daddr = '::/0' if family == 'inet6' else '0.0.0.0/0'
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index cb4a175dd..fb6b54776 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -117,11 +117,15 @@ def check_unsaved_config():
pass
def execute_shutdown(time, reboot=True, ask=True):
+ from vyos.utils.process import cmd
+
check_unsaved_config()
+ host = cmd("hostname --fqdn")
+
action = "reboot" if reboot else "poweroff"
if not ask:
- if not ask_yes_no(f"Are you sure you want to {action} this system?"):
+ if not ask_yes_no(f"Are you sure you want to {action} this system ({host})?"):
exit(0)
action_cmd = "-r" if reboot else "-P"