summaryrefslogtreecommitdiff
path: root/src/op_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/op_mode')
-rwxr-xr-xsrc/op_mode/connect_disconnect.py68
-rwxr-xr-xsrc/op_mode/conntrack_sync.py45
-rwxr-xr-xsrc/op_mode/firewall.py355
-rwxr-xr-xsrc/op_mode/force_root-partition-auto-resize.sh60
-rwxr-xr-xsrc/op_mode/format_disk.py76
-rwxr-xr-xsrc/op_mode/generate_ipsec_debug_archive.sh36
-rwxr-xr-xsrc/op_mode/lldp_op.py3
-rwxr-xr-xsrc/op_mode/pki.py85
-rwxr-xr-xsrc/op_mode/policy_route.py189
-rwxr-xr-xsrc/op_mode/powerctrl.py33
-rwxr-xr-xsrc/op_mode/ppp-server-ctrl.py2
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/show_configuration_json.py36
-rwxr-xr-xsrc/op_mode/show_interfaces.py4
-rwxr-xr-xsrc/op_mode/show_ipsec_sa.py3
-rwxr-xr-xsrc/op_mode/show_nat_rules.py22
-rwxr-xr-xsrc/op_mode/show_openvpn_mfa.py64
-rwxr-xr-xsrc/op_mode/show_ram.py64
-rwxr-xr-xsrc/op_mode/show_ram.sh33
-rwxr-xr-xsrc/op_mode/show_uptime.py50
-rwxr-xr-xsrc/op_mode/vpn_ipsec.py2
-rwxr-xr-xsrc/op_mode/zone_policy.py81
22 files changed, 1133 insertions, 180 deletions
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index a773aa28e..ffc574362 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2021 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
@@ -17,21 +17,19 @@
import os
import argparse
-from sys import exit
from psutil import process_iter
-from time import strftime, localtime, time
from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.util import is_wwan_connected
-def check_interface(interface):
+def check_ppp_interface(interface):
if not os.path.isfile(f'/etc/ppp/peers/{interface}'):
- print(f'Interface {interface}: invalid!')
+ print(f'Interface {interface} does not exist!')
exit(1)
def check_ppp_running(interface):
- """
- Check if ppp process is running in the interface in question
- """
+ """ Check if PPP process is running in the interface in question """
for p in process_iter():
if "pppd" in p.name():
if interface in p.cmdline():
@@ -40,32 +38,46 @@ def check_ppp_running(interface):
return False
def connect(interface):
- """
- Connect PPP interface
- """
- check_interface(interface)
+ """ Connect dialer interface """
- # Check if interface is already dialed
- if os.path.isdir(f'/sys/class/net/{interface}'):
- print(f'Interface {interface}: already connected!')
- elif check_ppp_running(interface):
- print(f'Interface {interface}: connection is beeing established!')
+ if interface.startswith('ppp'):
+ check_ppp_interface(interface)
+ # Check if interface is already dialed
+ if os.path.isdir(f'/sys/class/net/{interface}'):
+ print(f'Interface {interface}: already connected!')
+ elif check_ppp_running(interface):
+ print(f'Interface {interface}: connection is beeing established!')
+ else:
+ print(f'Interface {interface}: connecting...')
+ call(f'systemctl restart ppp@{interface}.service')
+ elif interface.startswith('wwan'):
+ if is_wwan_connected(interface):
+ print(f'Interface {interface}: already connected!')
+ else:
+ call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py')
else:
- print(f'Interface {interface}: connecting...')
- call(f'systemctl restart ppp@{interface}.service')
+ print(f'Unknown interface {interface}, can not connect. Aborting!')
def disconnect(interface):
- """
- Disconnect PPP interface
- """
- check_interface(interface)
+ """ Disconnect dialer interface """
- # Check if interface is already down
- if not check_ppp_running(interface):
- print(f'Interface {interface}: connection is already down')
+ if interface.startswith('ppp'):
+ check_ppp_interface(interface)
+
+ # Check if interface is already down
+ if not check_ppp_running(interface):
+ print(f'Interface {interface}: connection is already down')
+ else:
+ print(f'Interface {interface}: disconnecting...')
+ call(f'systemctl stop ppp@{interface}.service')
+ elif interface.startswith('wwan'):
+ if not is_wwan_connected(interface):
+ print(f'Interface {interface}: connection is already down')
+ else:
+ modem = interface.lstrip('wwan')
+ call(f'mmcli --modem {modem} --simple-disconnect', stdout=DEVNULL)
else:
- print(f'Interface {interface}: disconnecting...')
- call(f'systemctl stop ppp@{interface}.service')
+ print(f'Unknown interface {interface}, can not disconnect. Aborting!')
def main():
parser = argparse.ArgumentParser()
diff --git a/src/op_mode/conntrack_sync.py b/src/op_mode/conntrack_sync.py
index 66ecf8439..89f6df4b9 100755
--- a/src/op_mode/conntrack_sync.py
+++ b/src/op_mode/conntrack_sync.py
@@ -20,12 +20,15 @@ import xmltodict
from argparse import ArgumentParser
from vyos.configquery import CliShellApiConfigQuery
+from vyos.configquery import ConfigTreeQuery
+from vyos.util import call
from vyos.util import cmd
from vyos.util import run
from vyos.template import render_to_string
conntrackd_bin = '/usr/sbin/conntrackd'
conntrackd_config = '/run/conntrackd/conntrackd.conf'
+failover_state_file = '/var/run/vyatta-conntrackd-failover-state'
parser = ArgumentParser(description='Conntrack Sync')
group = parser.add_mutually_exclusive_group()
@@ -36,6 +39,8 @@ group.add_argument('--show-internal', help='Show internal (main) tracking cache'
group.add_argument('--show-external', help='Show external (main) tracking cache', action='store_true')
group.add_argument('--show-internal-expect', help='Show internal (expect) tracking cache', action='store_true')
group.add_argument('--show-external-expect', help='Show external (expect) tracking cache', action='store_true')
+group.add_argument('--show-statistics', help='Show connection syncing statistics', action='store_true')
+group.add_argument('--show-status', help='Show conntrack-sync status', action='store_true')
def is_configured():
""" Check if conntrack-sync service is configured """
@@ -131,6 +136,46 @@ if __name__ == '__main__':
out = cmd(f'sudo {conntrackd_bin} -C {conntrackd_config} {opt} -x')
xml_to_stdout(out)
+ elif args.show_statistics:
+ is_configured()
+ config = ConfigTreeQuery()
+ print('\nMain Table Statistics:\n')
+ call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s')
+ print()
+ if config.exists(['service', 'conntrack-sync', 'expect-sync']):
+ print('\nExpect Table Statistics:\n')
+ call(f'sudo {conntrackd_bin} -C {conntrackd_config} -s exp')
+ print()
+
+ elif args.show_status:
+ is_configured()
+ config = ConfigTreeQuery()
+ ct_sync_intf = config.list_nodes(['service', 'conntrack-sync', 'interface'])
+ ct_sync_intf = ', '.join(ct_sync_intf)
+ failover_state = "no transition yet!"
+ expect_sync_protocols = "disabled"
+
+ if config.exists(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp']):
+ failover_mechanism = "vrrp"
+ vrrp_sync_grp = config.value(['service', 'conntrack-sync', 'failover-mechanism', 'vrrp', 'sync-group'])
+
+ if os.path.isfile(failover_state_file):
+ with open(failover_state_file, "r") as f:
+ failover_state = f.readline()
+
+ if config.exists(['service', 'conntrack-sync', 'expect-sync']):
+ expect_sync_protocols = config.values(['service', 'conntrack-sync', 'expect-sync'])
+ if 'all' in expect_sync_protocols:
+ expect_sync_protocols = ["ftp", "sip", "h323", "nfs", "sqlnet"]
+ expect_sync_protocols = ', '.join(expect_sync_protocols)
+
+ show_status = (f'\nsync-interface : {ct_sync_intf}\n'
+ f'failover-mechanism : {failover_mechanism} [sync-group {vrrp_sync_grp}]\n'
+ f'last state transition : {failover_state}'
+ f'ExpectationSync : {expect_sync_protocols}')
+
+ print(show_status)
+
else:
parser.print_help()
exit(1)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
new file mode 100755
index 000000000..cf70890a6
--- /dev/null
+++ b/src/op_mode/firewall.py
@@ -0,0 +1,355 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+import argparse
+import json
+import re
+import tabulate
+
+from vyos.config import Config
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def get_firewall_interfaces(conf, firewall, name=None, ipv6=False):
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ directions = ['in', 'out', 'local']
+
+ def parse_if(ifname, if_conf):
+ if 'firewall' in if_conf:
+ for direction in directions:
+ if direction in if_conf['firewall']:
+ fw_conf = if_conf['firewall'][direction]
+ name_str = f'({ifname},{direction})'
+
+ if 'name' in fw_conf:
+ fw_name = fw_conf['name']
+
+ if not name:
+ firewall['name'][fw_name]['interface'].append(name_str)
+ elif not ipv6 and name == fw_name:
+ firewall['interface'].append(name_str)
+
+ if 'ipv6_name' in fw_conf:
+ fw_name = fw_conf['ipv6_name']
+
+ if not name:
+ firewall['ipv6_name'][fw_name]['interface'].append(name_str)
+ elif ipv6 and name == fw_name:
+ firewall['interface'].append(name_str)
+
+ for iftype in ['vif', 'vif_s', 'vif_c']:
+ if iftype in if_conf:
+ for vifname, vif_conf in if_conf[iftype].items():
+ parse_if(f'{ifname}.{vifname}', vif_conf)
+
+ for iftype, iftype_conf in interfaces.items():
+ for ifname, if_conf in iftype_conf.items():
+ parse_if(ifname, if_conf)
+
+ return firewall
+
+def get_config_firewall(conf, name=None, ipv6=False, interfaces=True):
+ config_path = ['firewall']
+ if name:
+ config_path += ['ipv6-name' if ipv6 else 'name', name]
+
+ firewall = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ if firewall and interfaces:
+ if name:
+ firewall['interface'] = []
+ else:
+ if 'name' in firewall:
+ for fw_name, name_conf in firewall['name'].items():
+ name_conf['interface'] = []
+
+ if 'ipv6_name' in firewall:
+ for fw_name, name_conf in firewall['ipv6_name'].items():
+ name_conf['interface'] = []
+
+ get_firewall_interfaces(conf, firewall, name, ipv6)
+ return firewall
+
+def get_nftables_details(name, ipv6=False):
+ suffix = '6' if ipv6 else ''
+ command = f'sudo nft list chain ip{suffix} filter {name}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ out = {}
+ for line in results.split('\n'):
+ comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ if not comment_search:
+ continue
+
+ rule = {}
+ rule_id = comment_search[1]
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[rule_id] = rule
+ return out
+
+def output_firewall_name(name, name_conf, ipv6=False, single_rule_id=None):
+ ip_str = 'IPv6' if ipv6 else 'IPv4'
+ print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
+
+ if name_conf['interface']:
+ print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ row = [rule_id, rule_conf['action'], rule_conf['protocol'] if 'protocol' in rule_conf else 'all']
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ row.append(rule_details['conditions'])
+ rows.append(row)
+
+ if 'default_action' in name_conf and not single_rule_id:
+ row = ['default', name_conf['default_action'], 'all']
+ if 'default-action' in details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ rows.append(row)
+
+ if rows:
+ header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']
+ print(tabulate.tabulate(rows, header) + '\n')
+
+def output_firewall_name_statistics(name, name_conf, ipv6=False, single_rule_id=None):
+ ip_str = 'IPv6' if ipv6 else 'IPv4'
+ print(f'\n---------------------------------\n{ip_str} Firewall "{name}"\n')
+
+ if name_conf['interface']:
+ print('Active on: {0}\n'.format(" ".join(name_conf['interface'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in name_conf:
+ for rule_id, rule_conf in name_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ source_addr = dict_search_args(rule_conf, 'source', 'address') or '0.0.0.0/0'
+ dest_addr = dict_search_args(rule_conf, 'destination', 'address') or '0.0.0.0/0'
+
+ row = [rule_id]
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ else:
+ row.append('0')
+ row.append('0')
+ row.append(rule_conf['action'])
+ row.append(source_addr)
+ row.append(dest_addr)
+ rows.append(row)
+
+ if 'default_action' in name_conf and not single_rule_id:
+ row = ['default']
+ if 'default-action' in details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ else:
+ row.append('0')
+ row.append('0')
+ row.append(name_conf['default_action'])
+ row.append('0.0.0.0/0') # Source
+ row.append('0.0.0.0/0') # Dest
+ rows.append(row)
+
+ if rows:
+ header = ['Rule', 'Packets', 'Bytes', 'Action', 'Source', 'Destination']
+ print(tabulate.tabulate(rows, header) + '\n')
+
+def show_firewall():
+ print('Rulesets Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ if 'name' in firewall:
+ for name, name_conf in firewall['name'].items():
+ output_firewall_name(name, name_conf, ipv6=False)
+
+ if 'ipv6_name' in firewall:
+ for name, name_conf in firewall['ipv6_name'].items():
+ output_firewall_name(name, name_conf, ipv6=True)
+
+def show_firewall_name(name, ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf, name, ipv6)
+ if firewall:
+ output_firewall_name(name, firewall, ipv6)
+
+def show_firewall_rule(name, rule_id, ipv6=False):
+ print('Rule Information')
+
+ conf = Config()
+ firewall = get_config_firewall(conf, name, ipv6)
+ if firewall:
+ output_firewall_name(name, firewall, ipv6, rule_id)
+
+def show_firewall_group(name=None):
+ conf = Config()
+ firewall = get_config_firewall(conf, interfaces=False)
+
+ if 'group' not in firewall:
+ return
+
+ def find_references(group_type, group_name):
+ out = []
+ for name_type in ['name', 'ipv6_name']:
+ if name_type not in firewall:
+ continue
+ for name, name_conf in firewall[name_type].items():
+ if 'rule' not in name_conf:
+ continue
+ for rule_id, rule_conf in name_conf['rule'].items():
+ source_group = dict_search_args(rule_conf, 'source', 'group', group_type)
+ dest_group = dict_search_args(rule_conf, 'destination', 'group', group_type)
+ if source_group and group_name == source_group:
+ out.append(f'{name}-{rule_id}')
+ elif dest_group and group_name == dest_group:
+ out.append(f'{name}-{rule_id}')
+ return out
+
+ header = ['Name', 'Type', 'References', 'Members']
+ rows = []
+
+ for group_type, group_type_conf in firewall['group'].items():
+ for group_name, group_conf in group_type_conf.items():
+ if name and name != group_name:
+ continue
+
+ references = find_references(group_type, group_name)
+ row = [group_name, group_type, ', '.join(references)]
+ if 'address' in group_conf:
+ row.append(", ".join(group_conf['address']))
+ elif 'network' in group_conf:
+ row.append(", ".join(group_conf['network']))
+ elif 'port' in group_conf:
+ row.append(", ".join(group_conf['port']))
+ rows.append(row)
+
+ if rows:
+ print('Firewall Groups\n')
+ print(tabulate.tabulate(rows, header))
+
+def show_summary():
+ print('Ruleset Summary')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ header = ['Ruleset Name', 'Description', 'References']
+ v4_out = []
+ v6_out = []
+
+ if 'name' in firewall:
+ for name, name_conf in firewall['name'].items():
+ description = name_conf.get('description', '')
+ interfaces = ", ".join(name_conf['interface'])
+ v4_out.append([name, description, interfaces])
+
+ if 'ipv6_name' in firewall:
+ for name, name_conf in firewall['ipv6_name'].items():
+ description = name_conf.get('description', '')
+ interfaces = ", ".join(name_conf['interface'])
+ v6_out.append([name, description, interfaces])
+
+ if v6_out:
+ print('\nIPv6 name:\n')
+ print(tabulate.tabulate(v6_out, header) + '\n')
+
+ if v4_out:
+ print('\nIPv4 name:\n')
+ print(tabulate.tabulate(v4_out, header) + '\n')
+
+ show_firewall_group()
+
+def show_statistics():
+ print('Rulesets Statistics')
+
+ conf = Config()
+ firewall = get_config_firewall(conf)
+
+ if not firewall:
+ return
+
+ if 'name' in firewall:
+ for name, name_conf in firewall['name'].items():
+ output_firewall_name_statistics(name, name_conf, ipv6=False)
+
+ if 'ipv6_name' in firewall:
+ for name, name_conf in firewall['ipv6_name'].items():
+ output_firewall_name_statistics(name, name_conf, ipv6=True)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Action', required=False)
+ parser.add_argument('--name', help='Firewall name', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--rule', help='Firewall Rule ID', required=False)
+ parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ if not args.rule:
+ show_firewall_name(args.name, args.ipv6)
+ else:
+ show_firewall_rule(args.name, args.rule, args.ipv6)
+ elif args.action == 'show_all':
+ show_firewall()
+ elif args.action == 'show_group':
+ show_firewall_group(args.name)
+ elif args.action == 'show_statistics':
+ show_statistics()
+ elif args.action == 'show_summary':
+ show_summary()
diff --git a/src/op_mode/force_root-partition-auto-resize.sh b/src/op_mode/force_root-partition-auto-resize.sh
new file mode 100755
index 000000000..b39e87560
--- /dev/null
+++ b/src/op_mode/force_root-partition-auto-resize.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+#
+# Copyright (C) 2021 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 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/>.
+
+# ROOT_PART_DEV – root partition device path
+# ROOT_PART_NAME – root partition device name
+# ROOT_DEV_NAME – disk device name
+# ROOT_DEV – disk device path
+# ROOT_PART_NUM – number of root partition on disk
+# ROOT_DEV_SIZE – disk total size in 512 bytes sectors
+# ROOT_PART_SIZE – root partition total size in 512 bytes sectors
+# ROOT_PART_START – number of 512 bytes sector where root partition starts
+# AVAILABLE_EXTENSION_SIZE – calculation available disk space after root partition in 512 bytes sectors
+ROOT_PART_DEV=$(findmnt /usr/lib/live/mount/persistence -o source -n)
+ROOT_PART_NAME=$(echo "$ROOT_PART_DEV" | cut -d "/" -f 3)
+ROOT_DEV_NAME=$(echo /sys/block/*/"${ROOT_PART_NAME}" | cut -d "/" -f 4)
+ROOT_DEV="/dev/${ROOT_DEV_NAME}"
+ROOT_PART_NUM=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/partition")
+ROOT_DEV_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/size")
+ROOT_PART_SIZE=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/size")
+ROOT_PART_START=$(cat "/sys/block/${ROOT_DEV_NAME}/${ROOT_PART_NAME}/start")
+AVAILABLE_EXTENSION_SIZE=$((ROOT_DEV_SIZE - ROOT_PART_START - ROOT_PART_SIZE - 8))
+
+#
+# Check if device have space for root partition growing up.
+#
+if [ $AVAILABLE_EXTENSION_SIZE -lt 1 ]; then
+ echo "There is no available space for root partition extension"
+ exit 0;
+fi
+
+#
+# Resize the partition and grow the filesystem.
+#
+# "print" and "Fix" directives were added to fix GPT table if it corrupted after virtual drive extension.
+# If GPT table is corrupted we'll get Fix/Ignore dialogue after "print" command.
+# "Fix" will be the answer for this dialogue.
+# If GPT table is fine and no auto-fix dialogue appeared the directive "Fix" simply will print parted utility help info.
+parted -m ${ROOT_DEV} ---pretend-input-tty > /dev/null 2>&1 <<EOF
+print
+Fix
+resizepart
+${ROOT_PART_NUM}
+Yes
+100%
+EOF
+partprobe > /dev/null 2>&1
+resize2fs ${ROOT_PART_DEV} > /dev/null 2>&1
diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py
index df4486bce..b3ba44e87 100755
--- a/src/op_mode/format_disk.py
+++ b/src/op_mode/format_disk.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2019-2021 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
@@ -17,11 +17,10 @@
import argparse
import os
import re
-import sys
+
from datetime import datetime
-from time import sleep
-from vyos.util import is_admin, ask_yes_no
+from vyos.util import ask_yes_no
from vyos.util import call
from vyos.util import cmd
from vyos.util import DEVNULL
@@ -38,16 +37,17 @@ def list_disks():
def is_busy(disk: str):
"""Check if given disk device is busy by re-reading it's partition table"""
- return call(f'sudo blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0
+ return call(f'blockdev --rereadpt /dev/{disk}', stderr=DEVNULL) != 0
def backup_partitions(disk: str):
"""Save sfdisk partitions output to a backup file"""
- device_path = '/dev/' + disk
- backup_ts = datetime.now().strftime('%Y-%m-%d-%H:%M')
- backup_file = '/var/tmp/backup_{}.{}'.format(disk, backup_ts)
- cmd(f'sudo /sbin/sfdisk -d {device_path} > {backup_file}')
+ device_path = f'/dev/{disk}'
+ backup_ts = datetime.now().strftime('%Y%m%d-%H%M')
+ backup_file = f'/var/tmp/backup_{disk}.{backup_ts}'
+ call(f'sfdisk -d {device_path} > {backup_file}')
+ print(f'Partition table backup saved to {backup_file}')
def list_partitions(disk: str):
@@ -65,11 +65,11 @@ def list_partitions(disk: str):
def delete_partition(disk: str, partition_idx: int):
- cmd(f'sudo /sbin/parted /dev/{disk} rm {partition_idx}')
+ cmd(f'parted /dev/{disk} rm {partition_idx}')
def format_disk_like(target: str, proto: str):
- cmd(f'sudo /sbin/sfdisk -d /dev/{proto} | sudo /sbin/sfdisk --force /dev/{target}')
+ cmd(f'sfdisk -d /dev/{proto} | sfdisk --force /dev/{target}')
if __name__ == '__main__':
@@ -79,10 +79,6 @@ if __name__ == '__main__':
group.add_argument('-p', '--proto', type=str, required=True, help='Prototype device to use as reference')
args = parser.parse_args()
- if not is_admin():
- print('Must be admin or root to format disk')
- sys.exit(1)
-
target_disk = args.target
eligible_target_disks = list_disks()
@@ -90,54 +86,48 @@ if __name__ == '__main__':
eligible_proto_disks = eligible_target_disks.copy()
eligible_proto_disks.remove(target_disk)
- fmt = {
- 'target_disk': target_disk,
- 'proto_disk': proto_disk,
- }
-
if proto_disk == target_disk:
print('The two disk drives must be different.')
- sys.exit(1)
+ exit(1)
- if not os.path.exists('/dev/' + proto_disk):
- print('Device /dev/{proto_disk} does not exist'.format_map(fmt))
- sys.exit(1)
+ if not os.path.exists(f'/dev/{proto_disk}'):
+ print(f'Device /dev/{proto_disk} does not exist')
+ exit(1)
if not os.path.exists('/dev/' + target_disk):
- print('Device /dev/{target_disk} does not exist'.format_map(fmt))
- sys.exit(1)
+ print(f'Device /dev/{target_disk} does not exist')
+ exit(1)
if target_disk not in eligible_target_disks:
- print('Device {target_disk} can not be formatted'.format_map(fmt))
- sys.exit(1)
+ print(f'Device {target_disk} can not be formatted')
+ exit(1)
if proto_disk not in eligible_proto_disks:
- print('Device {proto_disk} can not be used as a prototype for {target_disk}'.format_map(fmt))
- sys.exit(1)
+ print(f'Device {proto_disk} can not be used as a prototype for {target_disk}')
+ exit(1)
if is_busy(target_disk):
- print("Disk device {target_disk} is busy. Can't format it now".format_map(fmt))
- sys.exit(1)
+ print(f'Disk device {target_disk} is busy, unable to format')
+ exit(1)
- print('This will re-format disk {target_disk} so that it has the same disk\n'
- 'partion sizes and offsets as {proto_disk}. This will not copy\n'
- 'data from {proto_disk} to {target_disk}. But this will erase all\n'
- 'data on {target_disk}.\n'.format_map(fmt))
+ print(f'\nThis will re-format disk {target_disk} so that it has the same disk'
+ f'\npartion sizes and offsets as {proto_disk}. This will not copy'
+ f'\ndata from {proto_disk} to {target_disk}. But this will erase all'
+ f'\ndata on {target_disk}.\n')
- if not ask_yes_no("Do you wish to proceed?"):
- print('OK. Disk drive {target_disk} will not be re-formated'.format_map(fmt))
- sys.exit(0)
+ if not ask_yes_no('Do you wish to proceed?'):
+ print(f'Disk drive {target_disk} will not be re-formated')
+ exit(0)
- print('OK. Re-formating disk drive {target_disk}...'.format_map(fmt))
+ print(f'Re-formating disk drive {target_disk}...')
print('Making backup copy of partitions...')
backup_partitions(target_disk)
- sleep(1)
print('Deleting old partitions...')
for p in list_partitions(target_disk):
delete_partition(disk=target_disk, partition_idx=p)
- print('Creating new partitions on {target_disk} based on {proto_disk}...'.format_map(fmt))
+ print(f'Creating new partitions on {target_disk} based on {proto_disk}...')
format_disk_like(target=target_disk, proto=proto_disk)
- print('Done.')
+ print('Done!')
diff --git a/src/op_mode/generate_ipsec_debug_archive.sh b/src/op_mode/generate_ipsec_debug_archive.sh
new file mode 100755
index 000000000..53d0a6eaa
--- /dev/null
+++ b/src/op_mode/generate_ipsec_debug_archive.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+# Collecting IPSec Debug Information
+
+DATE=`date +%d-%m-%Y`
+
+a_CMD=(
+ "sudo ipsec status"
+ "sudo swanctl -L"
+ "sudo swanctl -l"
+ "sudo swanctl -P"
+ "sudo ip x sa show"
+ "sudo ip x policy show"
+ "sudo ip tunnel show"
+ "sudo ip address"
+ "sudo ip rule show"
+ "sudo ip route"
+ "sudo ip route show table 220"
+ )
+
+
+echo "DEBUG: ${DATE} on host \"$(hostname)\"" > /tmp/ipsec-status-${DATE}.txt
+date >> /tmp/ipsec-status-${DATE}.txt
+
+# Execute all DEBUG commands and save it to file
+for cmd in "${a_CMD[@]}"; do
+ echo -e "\n### ${cmd} ###" >> /tmp/ipsec-status-${DATE}.txt
+ ${cmd} >> /tmp/ipsec-status-${DATE}.txt 2>/dev/null
+done
+
+# Collect charon logs, build .tgz archive
+sudo journalctl /usr/lib/ipsec/charon > /tmp/journalctl-charon-${DATE}.txt && \
+sudo tar -zcvf /tmp/ipsec-debug-${DATE}.tgz /tmp/journalctl-charon-${DATE}.txt /tmp/ipsec-status-${DATE}.txt >& /dev/null
+sudo rm -f /tmp/journalctl-charon-${DATE}.txt /tmp/ipsec-status-${DATE}.txt
+
+echo "Debug file is generated and located in /tmp/ipsec-debug-${DATE}.tgz"
diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py
index 731e71891..b9ebc991a 100755
--- a/src/op_mode/lldp_op.py
+++ b/src/op_mode/lldp_op.py
@@ -55,6 +55,9 @@ def parse_data(data, interface):
if interface is not None and local_if != interface:
continue
for chassis, c_value in values.get('chassis', {}).items():
+ # bail out early if no capabilities found
+ if 'capability' not in c_value:
+ continue
capabilities = c_value['capability']
if isinstance(capabilities, dict):
capabilities = [capabilities]
diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py
index e1428c581..bc7813052 100755
--- a/src/op_mode/pki.py
+++ b/src/op_mode/pki.py
@@ -35,6 +35,7 @@ from vyos.pki import verify_certificate
from vyos.xml import defaults
from vyos.util import ask_input, ask_yes_no
from vyos.util import cmd
+from vyos.util import install_into_config
CERT_REQ_END = '-----END CERTIFICATE REQUEST-----'
auth_dir = '/config/auth'
@@ -142,48 +143,50 @@ def get_revoked_by_serial_numbers(serial_numbers=[]):
return certs_out
def install_certificate(name, cert='', private_key=None, key_type=None, key_passphrase=None, is_ca=False):
- # Show conf commands for installing certificate
+ # Show/install conf commands for certificate
prefix = 'ca' if is_ca else 'certificate'
- print('Configure mode commands to install:')
- base = f"set pki {prefix} {name}"
+ base = f"pki {prefix} {name}"
+ config_paths = []
if cert:
cert_pem = "".join(encode_certificate(cert).strip().split("\n")[1:-1])
- print(f"{base} certificate '{cert_pem}'")
+ config_paths.append(f"{base} certificate '{cert_pem}'")
if private_key:
key_pem = "".join(encode_private_key(private_key, passphrase=key_passphrase).strip().split("\n")[1:-1])
- print(f"{base} private key '{key_pem}'")
+ config_paths.append(f"{base} private key '{key_pem}'")
if key_passphrase:
- print(f"{base} private password-protected")
+ config_paths.append(f"{base} private password-protected")
+
+ install_into_config(conf, config_paths)
def install_crl(ca_name, crl):
- # Show conf commands for installing crl
- print("Configure mode commands to install CRL:")
+ # Show/install conf commands for crl
crl_pem = "".join(encode_certificate(crl).strip().split("\n")[1:-1])
- print(f"set pki ca {ca_name} crl '{crl_pem}'")
+ install_into_config(conf, [f"pki ca {ca_name} crl '{crl_pem}'"])
def install_dh_parameters(name, params):
- # Show conf commands for installing dh params
- print("Configure mode commands to install DH parameters:")
+ # Show/install conf commands for dh params
dh_pem = "".join(encode_dh_parameters(params).strip().split("\n")[1:-1])
- print(f"set pki dh {name} parameters '{dh_pem}'")
+ install_into_config(conf, [f"pki dh {name} parameters '{dh_pem}'"])
def install_ssh_key(name, public_key, private_key, passphrase=None):
- # Show conf commands for installing ssh key
+ # Show/install conf commands for ssh key
key_openssh = encode_public_key(public_key, encoding='OpenSSH', key_format='OpenSSH')
username = os.getlogin()
type_key_split = key_openssh.split(" ")
- base = f"set system login user {username} authentication public-keys {name}"
- print("Configure mode commands to install SSH key:")
- print(f"{base} key '{type_key_split[1]}'")
- print(f"{base} type '{type_key_split[0]}'", end="\n\n")
+ base = f"system login user {username} authentication public-keys {name}"
+ install_into_config(conf, [
+ f"{base} key '{type_key_split[1]}'",
+ f"{base} type '{type_key_split[0]}'"
+ ])
print(encode_private_key(private_key, encoding='PEM', key_format='OpenSSH', passphrase=passphrase))
def install_keypair(name, key_type, private_key=None, public_key=None, passphrase=None):
- # Show conf commands for installing key-pair
- print("Configure mode commands to install key pair:")
+ # Show/install conf commands for key-pair
+
+ config_paths = []
if public_key:
install_public_key = ask_yes_no('Do you want to install the public key?', default=True)
@@ -191,7 +194,7 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
if install_public_key:
install_public_pem = "".join(public_key_pem.strip().split("\n")[1:-1])
- print(f"set pki key-pair {name} public key '{install_public_pem}'")
+ config_paths.append(f"pki key-pair {name} public key '{install_public_pem}'")
else:
print("Public key:")
print(public_key_pem)
@@ -202,13 +205,15 @@ def install_keypair(name, key_type, private_key=None, public_key=None, passphras
if install_private_key:
install_private_pem = "".join(private_key_pem.strip().split("\n")[1:-1])
- print(f"set pki key-pair {name} private key '{install_private_pem}'")
+ config_paths.append(f"pki key-pair {name} private key '{install_private_pem}'")
if passphrase:
- print(f"set pki key-pair {name} private password-protected")
+ config_paths.append(f"pki key-pair {name} private password-protected")
else:
print("Private key:")
print(private_key_pem)
+ install_into_config(conf, config_paths)
+
def install_wireguard_key(interface, private_key, public_key):
# Show conf commands for installing wireguard key pairs
from vyos.ifconfig import Section
@@ -217,20 +222,10 @@ def install_wireguard_key(interface, private_key, public_key):
exit(1)
# Check if we are running in a config session - if yes, we can directly write to the CLI
- cli_string = f"interfaces wireguard {interface} private-key '{private_key}'"
- if Config().in_session():
- cmd(f"/opt/vyatta/sbin/my_set {cli_string}")
-
- print('"generate" CLI command executed from config session.\nGenerated private-key was imported to CLI!',end='\n\n')
- print(f'Use the following command to verify: show interfaces wireguard {interface}')
- else:
- print('"generate" CLI command executed from operational level.\n'
- 'Generated private-key is not stored to CLI, use configure mode commands to install key:', end='\n\n')
- print(f"set {cli_string}", end="\n\n")
+ install_into_config(conf, [f"interfaces wireguard {interface} private-key '{private_key}'"])
print(f"Corresponding public-key to use on peer system is: '{public_key}'")
-
def install_wireguard_psk(interface, peer, psk):
from vyos.ifconfig import Section
if Section.section(interface) != 'wireguard':
@@ -238,17 +233,7 @@ def install_wireguard_psk(interface, peer, psk):
exit(1)
# Check if we are running in a config session - if yes, we can directly write to the CLI
- cli_string = f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"
- if Config().in_session():
- cmd(f"/opt/vyatta/sbin/my_set {cli_string}")
-
- print('"generate" CLI command executed from config session.\nGenerated preshared-key was imported to CLI!',end='\n\n')
- print(f'Use the following command to verify: show interfaces wireguard {interface}')
- else:
- print('"generate" CLI command executed from operational level.\n'
- 'Generated preshared-key is not stored to CLI, use configure mode commands to install key:', end='\n\n')
- print(f"set {cli_string}", end="\n\n")
-
+ install_into_config(conf, [f"interfaces wireguard {interface} peer {peer} preshared-key '{psk}'"])
def ask_passphrase():
passphrase = None
@@ -858,8 +843,18 @@ if __name__ == '__main__':
elif args.action == 'show':
if args.ca:
- show_certificate_authority(None if args.ca == 'all' else args.ca)
+ ca_name = None if args.ca == 'all' else args.ca
+ if ca_name:
+ if not conf.exists(['pki', 'ca', ca_name]):
+ print(f'CA "{ca_name}" does not exist!')
+ exit(1)
+ show_certificate_authority(ca_name)
elif args.certificate:
+ cert_name = None if args.certificate == 'all' else args.certificate
+ if cert_name:
+ if not conf.exists(['pki', 'certificate', cert_name]):
+ print(f'Certificate "{cert_name}" does not exist!')
+ exit(1)
show_certificate(None if args.certificate == 'all' else args.certificate)
elif args.crl:
show_crl(None if args.crl == 'all' else args.crl)
diff --git a/src/op_mode/policy_route.py b/src/op_mode/policy_route.py
new file mode 100755
index 000000000..e0b4ac514
--- /dev/null
+++ b/src/op_mode/policy_route.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+import argparse
+import re
+import tabulate
+
+from vyos.config import Config
+from vyos.util import cmd
+from vyos.util import dict_search_args
+
+def get_policy_interfaces(conf, policy, name=None, ipv6=False):
+ interfaces = conf.get_config_dict(['interfaces'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ routes = ['route', 'ipv6_route']
+
+ def parse_if(ifname, if_conf):
+ if 'policy' in if_conf:
+ for route in routes:
+ if route in if_conf['policy']:
+ route_name = if_conf['policy'][route]
+ name_str = f'({ifname},{route})'
+
+ if not name:
+ policy[route][route_name]['interface'].append(name_str)
+ elif not ipv6 and name == route_name:
+ policy['interface'].append(name_str)
+
+ for iftype in ['vif', 'vif_s', 'vif_c']:
+ if iftype in if_conf:
+ for vifname, vif_conf in if_conf[iftype].items():
+ parse_if(f'{ifname}.{vifname}', vif_conf)
+
+ for iftype, iftype_conf in interfaces.items():
+ for ifname, if_conf in iftype_conf.items():
+ parse_if(ifname, if_conf)
+
+def get_config_policy(conf, name=None, ipv6=False, interfaces=True):
+ config_path = ['policy']
+ if name:
+ config_path += ['ipv6-route' if ipv6 else 'route', name]
+
+ policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ if policy and interfaces:
+ if name:
+ policy['interface'] = []
+ else:
+ if 'route' in policy:
+ for route_name, route_conf in policy['route'].items():
+ route_conf['interface'] = []
+
+ if 'ipv6_route' in policy:
+ for route_name, route_conf in policy['ipv6_route'].items():
+ route_conf['interface'] = []
+
+ get_policy_interfaces(conf, policy, name, ipv6)
+
+ return policy
+
+def get_nftables_details(name, ipv6=False):
+ suffix = '6' if ipv6 else ''
+ command = f'sudo nft list chain ip{suffix} mangle VYOS_PBR{suffix}_{name}'
+ try:
+ results = cmd(command)
+ except:
+ return {}
+
+ out = {}
+ for line in results.split('\n'):
+ comment_search = re.search(rf'{name}[\- ](\d+|default-action)', line)
+ if not comment_search:
+ continue
+
+ rule = {}
+ rule_id = comment_search[1]
+ counter_search = re.search(r'counter packets (\d+) bytes (\d+)', line)
+ if counter_search:
+ rule['packets'] = counter_search[1]
+ rule['bytes'] = counter_search[2]
+
+ rule['conditions'] = re.sub(r'(\b(counter packets \d+ bytes \d+|drop|reject|return|log)\b|comment "[\w\-]+")', '', line).strip()
+ out[rule_id] = rule
+ return out
+
+def output_policy_route(name, route_conf, ipv6=False, single_rule_id=None):
+ ip_str = 'IPv6' if ipv6 else 'IPv4'
+ print(f'\n---------------------------------\n{ip_str} Policy Route "{name}"\n')
+
+ if route_conf['interface']:
+ print('Active on: {0}\n'.format(" ".join(route_conf['interface'])))
+
+ details = get_nftables_details(name, ipv6)
+ rows = []
+
+ if 'rule' in route_conf:
+ for rule_id, rule_conf in route_conf['rule'].items():
+ if single_rule_id and rule_id != single_rule_id:
+ continue
+
+ if 'disable' in rule_conf:
+ continue
+
+ action = rule_conf['action'] if 'action' in rule_conf else 'set'
+ protocol = rule_conf['protocol'] if 'protocol' in rule_conf else 'all'
+
+ row = [rule_id, action, protocol]
+ if rule_id in details:
+ rule_details = details[rule_id]
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ row.append(rule_details['conditions'])
+ rows.append(row)
+
+ if 'default_action' in route_conf and not single_rule_id:
+ row = ['default', route_conf['default_action'], 'all']
+ if 'default-action' in details:
+ rule_details = details['default-action']
+ row.append(rule_details.get('packets', 0))
+ row.append(rule_details.get('bytes', 0))
+ rows.append(row)
+
+ if rows:
+ header = ['Rule', 'Action', 'Protocol', 'Packets', 'Bytes', 'Conditions']
+ print(tabulate.tabulate(rows, header) + '\n')
+
+def show_policy(ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ policy = get_config_policy(conf)
+
+ if not policy:
+ return
+
+ if not ipv6 and 'route' in policy:
+ for route, route_conf in policy['route'].items():
+ output_policy_route(route, route_conf, ipv6=False)
+
+ if ipv6 and 'ipv6_route' in policy:
+ for route, route_conf in policy['ipv6_route'].items():
+ output_policy_route(route, route_conf, ipv6=True)
+
+def show_policy_name(name, ipv6=False):
+ print('Ruleset Information')
+
+ conf = Config()
+ policy = get_config_policy(conf, name, ipv6)
+ if policy:
+ output_policy_route(name, policy, ipv6)
+
+def show_policy_rule(name, rule_id, ipv6=False):
+ print('Rule Information')
+
+ conf = Config()
+ policy = get_config_policy(conf, name, ipv6)
+ if policy:
+ output_policy_route(name, policy, ipv6, rule_id)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Action', required=False)
+ parser.add_argument('--name', help='Policy name', required=False, action='store', nargs='?', default='')
+ parser.add_argument('--rule', help='Policy Rule ID', required=False)
+ parser.add_argument('--ipv6', help='IPv6 toggle', action='store_true')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ if not args.rule:
+ show_policy_name(args.name, args.ipv6)
+ else:
+ show_policy_rule(args.name, args.rule, args.ipv6)
+ elif args.action == 'show_all':
+ show_policy(args.ipv6)
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index f8b5a3dda..679b03c0b 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -92,37 +92,40 @@ def cancel_shutdown():
try:
run('/sbin/shutdown -c --no-wall')
except OSError as e:
- exit("Could not cancel a reboot or poweroff: %s" % e)
+ exit(f'Could not cancel a reboot or poweroff: {e}')
- message = 'Scheduled {} has been cancelled {}'.format(output['MODE'], timenow)
+ mode = output['MODE']
+ message = f'Scheduled {mode} has been cancelled {timenow}'
run(f'wall {message} > /dev/null 2>&1')
else:
print("Reboot or poweroff is not scheduled")
def execute_shutdown(time, reboot=True, ask=True):
+ action = "reboot" if reboot else "poweroff"
if not ask:
- action = "reboot" if reboot else "poweroff"
- if not ask_yes_no("Are you sure you want to %s this system?" % action):
+ if not ask_yes_no(f"Are you sure you want to {action} this system?"):
exit(0)
-
- action = "-r" if reboot else "-P"
+ action_cmd = "-r" if reboot else "-P"
if len(time) == 0:
# T870 legacy reboot job support
chk_vyatta_based_reboots()
###
- out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT)
+ out = cmd(f'/sbin/shutdown {action_cmd} now', stderr=STDOUT)
print(out.split(",", 1)[0])
return
elif len(time) == 1:
# Assume the argument is just time
ts = parse_time(time[0])
if ts:
- cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT)
+ cmd(f'/sbin/shutdown {action_cmd} {time[0]}', stderr=STDOUT)
+ # Inform all other logged in users about the reboot/shutdown
+ wall_msg = f'System {action} is scheduled {time[0]}'
+ cmd(f'/usr/bin/wall "{wall_msg}"')
else:
- exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
+ exit(f'Invalid time "{time[0]}". The valid format is HH:MM')
elif len(time) == 2:
# Assume it's date and time
ts = parse_time(time[0])
@@ -131,14 +134,18 @@ def execute_shutdown(time, reboot=True, ask=True):
t = datetime.combine(ds, ts)
td = t - datetime.now()
t2 = 1 + int(td.total_seconds())//60 # Get total minutes
- cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT)
+
+ cmd(f'/sbin/shutdown {action_cmd} {t2}', stderr=STDOUT)
+ # Inform all other logged in users about the reboot/shutdown
+ wall_msg = f'System {action} is scheduled {time[1]} {time[0]}'
+ cmd(f'/usr/bin/wall "{wall_msg}"')
else:
if not ts:
- exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
+ exit(f'Invalid time "{time[0]}". The valid format is HH:MM')
else:
- exit("Invalid time \"{0}\". A valid format is YYYY-MM-DD [HH:MM]".format(time[1]))
+ exit(f'Invalid date "{time[1]}". A valid format is YYYY-MM-DD [HH:MM]')
else:
- exit("Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM")
+ exit('Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM')
check_shutdown()
diff --git a/src/op_mode/ppp-server-ctrl.py b/src/op_mode/ppp-server-ctrl.py
index 670cdf879..e93963fdd 100755
--- a/src/op_mode/ppp-server-ctrl.py
+++ b/src/op_mode/ppp-server-ctrl.py
@@ -60,7 +60,7 @@ def main():
output, err = popen(cmd_dict['cmd_base'].format(cmd_dict['vpn_types'][args.proto]) + args.action + ses_pattern, stderr=DEVNULL, decode='utf-8')
if not err:
try:
- print(output)
+ print(f' {output}')
except:
sys.exit(0)
else:
diff --git a/src/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 109c8dd7b..e5014452f 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -138,7 +138,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['bfdd', 'bgpd', 'ldpd', 'ospfd', 'ospf6d', 'isisd', 'ripd', 'ripngd', 'staticd', 'zebra'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/show_configuration_json.py b/src/op_mode/show_configuration_json.py
new file mode 100755
index 000000000..fdece533b
--- /dev/null
+++ b/src/op_mode/show_configuration_json.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+import argparse
+import json
+
+from vyos.configquery import ConfigTreeQuery
+
+
+config = ConfigTreeQuery()
+c = config.get_config_dict()
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-p", "--pretty", action="store_true", help="Show pretty configuration in JSON format")
+
+
+if __name__ == '__main__':
+ args = parser.parse_args()
+
+ if args.pretty:
+ print(json.dumps(c, indent=4))
+ else:
+ print(json.dumps(c))
diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py
index 3d50eb938..eac068274 100755
--- a/src/op_mode/show_interfaces.py
+++ b/src/op_mode/show_interfaces.py
@@ -94,10 +94,8 @@ def split_text(text, used=0):
used: number of characted already used in the screen
"""
no_tty = call('tty -s')
- if no_tty:
- return text.split()
- returned = cmd('stty size')
+ returned = cmd('stty size') if not no_tty else ''
if len(returned) == 2:
rows, columns = [int(_) for _ in returned]
else:
diff --git a/src/op_mode/show_ipsec_sa.py b/src/op_mode/show_ipsec_sa.py
index c964caaeb..e72f0f965 100755
--- a/src/op_mode/show_ipsec_sa.py
+++ b/src/op_mode/show_ipsec_sa.py
@@ -46,7 +46,6 @@ def format_output(conns, sas):
if parent_sa["state"] == b"ESTABLISHED" and installed_sas:
state = "up"
- uptime = vyos.util.seconds_to_human(parent_sa["established"].decode())
remote_host = parent_sa["remote-host"].decode()
remote_id = parent_sa["remote-id"].decode()
@@ -75,6 +74,8 @@ def format_output(conns, sas):
# Remove B from <1K values
pkts_str = re.sub(r'B', r'', pkts_str)
+ uptime = vyos.util.seconds_to_human(isa['install-time'].decode())
+
enc = isa["encr-alg"].decode()
if "encr-keysize" in isa:
key_size = isa["encr-keysize"].decode()
diff --git a/src/op_mode/show_nat_rules.py b/src/op_mode/show_nat_rules.py
index d68def26a..98adb31dd 100755
--- a/src/op_mode/show_nat_rules.py
+++ b/src/op_mode/show_nat_rules.py
@@ -32,7 +32,7 @@ args = parser.parse_args()
if args.source or args.destination:
tmp = cmd('sudo nft -j list table ip nat')
tmp = json.loads(tmp)
-
+
format_nat_rule = '{0: <10} {1: <50} {2: <50} {3: <10}'
print(format_nat_rule.format("Rule", "Source" if args.source else "Destination", "Translation", "Outbound Interface" if args.source else "Inbound Interface"))
print(format_nat_rule.format("----", "------" if args.source else "-----------", "-----------", "------------------" if args.source else "-----------------"))
@@ -40,7 +40,7 @@ if args.source or args.destination:
data_json = jmespath.search('nftables[?rule].rule[?chain]', tmp)
for idx in range(0, len(data_json)):
data = data_json[idx]
-
+
# The following key values must exist
# When the rule JSON does not have some keys, this is not a rule we can work with
continue_rule = False
@@ -50,9 +50,9 @@ if args.source or args.destination:
continue
if continue_rule:
continue
-
+
comment = data['comment']
-
+
# Check the annotation to see if the annotation format is created by VYOS
continue_rule = True
for comment_prefix in ['SRC-NAT-', 'DST-NAT-']:
@@ -60,7 +60,7 @@ if args.source or args.destination:
continue_rule = False
if continue_rule:
continue
-
+
rule = int(''.join(list(filter(str.isdigit, comment))))
chain = data['chain']
if not ((args.source and chain == 'POSTROUTING') or (not args.source and chain == 'PREROUTING')):
@@ -88,7 +88,7 @@ if args.source or args.destination:
else:
port_range = srcdest_json['set'][0]['range']
srcdest += 'port ' + str(port_range[0]) + '-' + str(port_range[1]) + ' '
-
+
tran_addr_json = dict_search('snat' if args.source else 'dnat', data['expr'][i])
if tran_addr_json:
if isinstance(tran_addr_json['addr'],str):
@@ -98,10 +98,10 @@ if args.source or args.destination:
len_tmp = dict_search('snat.addr.prefix.len' if args.source else 'dnat.addr.prefix.len', data['expr'][3])
if addr_tmp and len_tmp:
tran_addr += addr_tmp + '/' + str(len_tmp) + ' '
-
+
if isinstance(tran_addr_json['port'],int):
- tran_addr += 'port ' + tran_addr_json['port']
-
+ tran_addr += 'port ' + str(tran_addr_json['port'])
+
else:
if 'masquerade' in data['expr'][i]:
tran_addr = 'masquerade'
@@ -112,10 +112,10 @@ if args.source or args.destination:
srcdests.append(srcdest)
srcdest = ''
print(format_nat_rule.format(rule, srcdests[0], tran_addr, interface))
-
+
for i in range(1, len(srcdests)):
print(format_nat_rule.format(' ', srcdests[i], ' ', ' '))
-
+
exit(0)
else:
parser.print_help()
diff --git a/src/op_mode/show_openvpn_mfa.py b/src/op_mode/show_openvpn_mfa.py
new file mode 100755
index 000000000..1ab54600c
--- /dev/null
+++ b/src/op_mode/show_openvpn_mfa.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 2021 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 re
+import socket
+import urllib.parse
+import argparse
+
+from vyos.util import popen
+
+otp_file = '/config/auth/openvpn/{interface}-otp-secrets'
+
+def get_mfa_secret(interface, client):
+ try:
+ with open(otp_file.format(interface=interface), "r") as f:
+ users = f.readlines()
+ for user in users:
+ if re.search('^' + client + ' ', user):
+ return user.split(':')[3]
+ except:
+ pass
+
+def get_mfa_uri(client, secret):
+ hostname = socket.gethostname()
+ fqdn = socket.getfqdn()
+ uri = 'otpauth://totp/{hostname}:{client}@{fqdn}?secret={secret}'
+
+ return urllib.parse.quote(uri.format(hostname=hostname, client=client, fqdn=fqdn, secret=secret), safe='/:@?=')
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(add_help=False, description='Show two-factor authentication information')
+ parser.add_argument('--intf', action="store", type=str, default='', help='only show the specified interface')
+ parser.add_argument('--user', action="store", type=str, default='', help='only show the specified users')
+ parser.add_argument('--action', action="store", type=str, default='show', help='action to perform')
+
+ args = parser.parse_args()
+ secret = get_mfa_secret(args.intf, args.user)
+
+ if args.action == "secret" and secret:
+ print(secret)
+
+ if args.action == "uri" and secret:
+ uri = get_mfa_uri(args.user, secret)
+ print(uri)
+
+ if args.action == "qrcode" and secret:
+ uri = get_mfa_uri(args.user, secret)
+ qrcode,err = popen('qrencode -t ansiutf8', input=uri)
+ print(qrcode)
+
diff --git a/src/op_mode/show_ram.py b/src/op_mode/show_ram.py
new file mode 100755
index 000000000..5818ec132
--- /dev/null
+++ b/src/op_mode/show_ram.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+#
+
+def get_system_memory():
+ from re import search as re_search
+
+ def find_value(keyword, mem_data):
+ regex = keyword + ':\s+(\d+)'
+ res = re_search(regex, mem_data).group(1)
+ return int(res)
+
+ with open("/proc/meminfo", "r") as f:
+ mem_data = f.read()
+
+ total = find_value('MemTotal', mem_data)
+ available = find_value('MemAvailable', mem_data)
+ buffers = find_value('Buffers', mem_data)
+ cached = find_value('Cached', mem_data)
+
+ used = total - available
+
+ res = {
+ "total": total,
+ "free": available,
+ "used": used,
+ "buffers": buffers,
+ "cached": cached
+ }
+
+ return res
+
+def get_system_memory_human():
+ from vyos.util import bytes_to_human
+
+ mem = get_system_memory()
+
+ for key in mem:
+ # The Linux kernel exposes memory values in kilobytes,
+ # so we need to normalize them
+ mem[key] = bytes_to_human(mem[key], initial_exponent=10)
+
+ return mem
+
+if __name__ == '__main__':
+ mem = get_system_memory_human()
+
+ print("Total: {}".format(mem["total"]))
+ print("Free: {}".format(mem["free"]))
+ print("Used: {}".format(mem["used"]))
+
diff --git a/src/op_mode/show_ram.sh b/src/op_mode/show_ram.sh
deleted file mode 100755
index b013e16f8..000000000
--- a/src/op_mode/show_ram.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-#
-# Module: vyos-show-ram.sh
-# Displays memory usage information in minimalistic format
-#
-# Copyright (C) 2019 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 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/>.
-
-MB_DIVISOR=1024
-
-TOTAL=$(cat /proc/meminfo | grep -E "^MemTotal:" | awk -F ' ' '{print $2}')
-FREE=$(cat /proc/meminfo | grep -E "^MemFree:" | awk -F ' ' '{print $2}')
-BUFFERS=$(cat /proc/meminfo | grep -E "^Buffers:" | awk -F ' ' '{print $2}')
-CACHED=$(cat /proc/meminfo | grep -E "^Cached:" | awk -F ' ' '{print $2}')
-
-DISPLAY_FREE=$(( ($FREE + $BUFFERS + $CACHED) / $MB_DIVISOR ))
-DISPLAY_TOTAL=$(( $TOTAL / $MB_DIVISOR ))
-DISPLAY_USED=$(( $DISPLAY_TOTAL - $DISPLAY_FREE ))
-
-echo "Total: $DISPLAY_TOTAL"
-echo "Free: $DISPLAY_FREE"
-echo "Used: $DISPLAY_USED"
diff --git a/src/op_mode/show_uptime.py b/src/op_mode/show_uptime.py
new file mode 100755
index 000000000..c3dea52e6
--- /dev/null
+++ b/src/op_mode/show_uptime.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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 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/>.
+
+def get_uptime_seconds():
+ from re import search
+ from vyos.util import read_file
+
+ data = read_file("/proc/uptime")
+ seconds = search("([0-9\.]+)\s", data).group(1)
+
+ return int(float(seconds))
+
+def get_load_averages():
+ from re import search
+ from vyos.util import cmd
+
+ data = cmd("uptime")
+ matches = search(r"load average:\s*(?P<one>[0-9\.]+)\s*,\s*(?P<five>[0-9\.]+)\s*,\s*(?P<fifteen>[0-9\.]+)\s*", data)
+
+ res = {}
+ res[1] = float(matches["one"])
+ res[5] = float(matches["five"])
+ res[15] = float(matches["fifteen"])
+
+ return res
+
+if __name__ == '__main__':
+ from vyos.util import seconds_to_human
+
+ print("Uptime: {}\n".format(seconds_to_human(get_uptime_seconds())))
+
+ avgs = get_load_averages()
+
+ print("Load averages:")
+ print("1 minute: {:.02f}%".format(avgs[1]*100))
+ print("5 minutes: {:.02f}%".format(avgs[5]*100))
+ print("15 minutes: {:.02f}%".format(avgs[15]*100))
diff --git a/src/op_mode/vpn_ipsec.py b/src/op_mode/vpn_ipsec.py
index 06e227ccf..40854fa8f 100755
--- a/src/op_mode/vpn_ipsec.py
+++ b/src/op_mode/vpn_ipsec.py
@@ -48,7 +48,7 @@ def reset_peer(peer, tunnel):
result = True
for conn in conns:
try:
- call(f'sudo /usr/sbin/ipsec down {conn}', timeout = 10)
+ call(f'sudo /usr/sbin/ipsec down {conn}{{*}}', timeout = 10)
call(f'sudo /usr/sbin/ipsec up {conn}', timeout = 10)
except TimeoutExpired as e:
print(f'Timed out while resetting {conn}')
diff --git a/src/op_mode/zone_policy.py b/src/op_mode/zone_policy.py
new file mode 100755
index 000000000..7b43018c2
--- /dev/null
+++ b/src/op_mode/zone_policy.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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/>.
+
+import argparse
+import tabulate
+
+from vyos.config import Config
+from vyos.util import dict_search_args
+
+def get_config_zone(conf, name=None):
+ config_path = ['zone-policy']
+ if name:
+ config_path += ['zone', name]
+
+ zone_policy = conf.get_config_dict(config_path, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+ return zone_policy
+
+def output_zone_name(zone, zone_conf):
+ print(f'\n---------------------------------\nZone: "{zone}"\n')
+
+ interfaces = ', '.join(zone_conf['interface']) if 'interface' in zone_conf else ''
+ if 'local_zone' in zone_conf:
+ interfaces = 'LOCAL'
+
+ print(f'Interfaces: {interfaces}\n')
+
+ header = ['From Zone', 'Firewall']
+ rows = []
+
+ if 'from' in zone_conf:
+ for from_name, from_conf in zone_conf['from'].items():
+ row = [from_name]
+ v4_name = dict_search_args(from_conf, 'firewall', 'name')
+ v6_name = dict_search_args(from_conf, 'firewall', 'ipv6_name')
+
+ if v4_name:
+ rows.append(row + [v4_name])
+
+ if v6_name:
+ rows.append(row + [f'{v6_name} [IPv6]'])
+
+ if rows:
+ print('From Zones:\n')
+ print(tabulate.tabulate(rows, header))
+
+def show_zone_policy(zone):
+ conf = Config()
+ zone_policy = get_config_zone(conf, zone)
+
+ if not zone_policy:
+ return
+
+ if 'zone' in zone_policy:
+ for zone, zone_conf in zone_policy['zone'].items():
+ output_zone_name(zone, zone_conf)
+ elif zone:
+ output_zone_name(zone, zone_policy)
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--action', help='Action', required=False)
+ parser.add_argument('--name', help='Zone name', required=False, action='store', nargs='?', default='')
+
+ args = parser.parse_args()
+
+ if args.action == 'show':
+ show_zone_policy(args.name)