diff options
Diffstat (limited to 'src/op_mode')
-rwxr-xr-x | src/op_mode/container.py | 13 | ||||
-rwxr-xr-x | src/op_mode/ipsec.py | 28 | ||||
-rwxr-xr-x | src/op_mode/lldp.py | 138 | ||||
-rwxr-xr-x | src/op_mode/lldp_op.py | 127 | ||||
-rwxr-xr-x | src/op_mode/nat.py | 12 | ||||
-rwxr-xr-x | src/op_mode/route.py | 35 | ||||
-rwxr-xr-x | src/op_mode/show_ntp.sh | 31 |
7 files changed, 219 insertions, 165 deletions
diff --git a/src/op_mode/container.py b/src/op_mode/container.py index ecefc556e..d48766a0c 100755 --- a/src/op_mode/container.py +++ b/src/op_mode/container.py @@ -35,6 +35,19 @@ def _get_raw_data(command: str) -> list: data = json.loads(json_data) return data +def add_image(name: str): + from vyos.util import rc_cmd + + rc, output = rc_cmd(f'podman image pull {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) + +def delete_image(name: str): + from vyos.util import rc_cmd + + rc, output = rc_cmd(f'podman image rm --force {name}') + if rc != 0: + raise vyos.opmode.InternalError(output) def show_container(raw: bool): command = 'podman ps --all' diff --git a/src/op_mode/ipsec.py b/src/op_mode/ipsec.py index e0d204a0a..f6417764a 100755 --- a/src/op_mode/ipsec.py +++ b/src/op_mode/ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -173,7 +173,7 @@ def _get_parent_sa_proposal(connection_name: str, data: list) -> dict: for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return {} + continue if 'encr-alg' in sa[connection_name]: encr_alg = sa.get(connection_name, '').get('encr-alg') cipher = encr_alg.split('_')[0] @@ -203,16 +203,17 @@ def _get_parent_sa_state(connection_name: str, data: list) -> str: Returns: Parent SA connection state """ + ike_state = 'down' if not data: - return 'down' + return ike_state for sa in data: # check if parent SA exist - if connection_name not in sa.keys(): - return 'down' - if sa[connection_name]['state'].lower() == 'established': - return 'up' - else: - return 'down' + for connection, connection_conf in sa.items(): + if connection_name != connection: + continue + if connection_conf['state'].lower() == 'established': + ike_state = 'up' + return ike_state def _get_child_sa_state(connection_name: str, tunnel_name: str, @@ -227,19 +228,20 @@ def _get_child_sa_state(connection_name: str, tunnel_name: str, Returns: str: `up` if child SA state is 'installed' otherwise `down` """ + child_sa = 'down' if not data: - return 'down' + return child_sa for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return 'down' + continue child_sas = sa[connection_name]['child-sas'] # Get all child SA states # there can be multiple SAs per tunnel child_sa_states = [ v['state'] for k, v in child_sas.items() if v['name'] == tunnel_name ] - return 'up' if 'INSTALLED' in child_sa_states else 'down' + return 'up' if 'INSTALLED' in child_sa_states else child_sa def _get_child_sa_info(connection_name: str, tunnel_name: str, @@ -257,7 +259,7 @@ def _get_child_sa_info(connection_name: str, tunnel_name: str, for sa in data: # check if parent SA exist if connection_name not in sa.keys(): - return {} + continue child_sas = sa[connection_name]['child-sas'] # Get all child SA data # Skip temp SA name (first key), get only SA values as dict diff --git a/src/op_mode/lldp.py b/src/op_mode/lldp.py new file mode 100755 index 000000000..dc2b1e0b5 --- /dev/null +++ b/src/op_mode/lldp.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jmespath +import json +import sys +import typing + +from tabulate import tabulate + +from vyos.configquery import ConfigTreeQuery +from vyos.util import cmd +from vyos.util import dict_search + +import vyos.opmode +unconf_message = 'LLDP is not configured' +capability_codes = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station + D - Docsis, T - Telephone, O - Other + +""" + +def _verify(func): + """Decorator checks if LLDP config exists""" + from functools import wraps + + @wraps(func) + def _wrapper(*args, **kwargs): + config = ConfigTreeQuery() + if not config.exists(['service', 'lldp']): + raise vyos.opmode.UnconfiguredSubsystem(unconf_message) + return func(*args, **kwargs) + return _wrapper + +def _get_raw_data(interface=None, detail=False): + """ + If interface name is not set - get all interfaces + """ + tmp = 'lldpcli -f json show neighbors' + if detail: + tmp += f' details' + if interface: + tmp += f' ports {interface}' + output = cmd(tmp) + data = json.loads(output) + if not data: + return [] + return data + +def _get_formatted_output(raw_data): + data_entries = [] + for neighbor in dict_search('lldp.interface', raw_data): + for local_if, values in neighbor.items(): + tmp = [] + + # Device field + if 'chassis' in values: + tmp.append(next(iter(values['chassis']))) + else: + tmp.append('') + + # Local Port field + tmp.append(local_if) + + # Protocol field + tmp.append(values['via']) + + # Capabilities + cap = '' + capabilities = jmespath.search('chassis.[*][0][0].capability', values) + if capabilities: + for capability in capabilities: + if capability['enabled']: + if capability['type'] == 'Router': + cap += 'R' + if capability['type'] == 'Bridge': + cap += 'B' + if capability['type'] == 'Wlan': + cap += 'W' + if capability['type'] == 'Station': + cap += 'S' + if capability['type'] == 'Repeater': + cap += 'r' + if capability['type'] == 'Telephone': + cap += 'T' + if capability['type'] == 'Docsis': + cap += 'D' + if capability['type'] == 'Other': + cap += 'O' + tmp.append(cap) + + # Remote software platform + platform = jmespath.search('chassis.[*][0][0].descr', values) + tmp.append(platform[:37]) + + # Remote interface + interface = jmespath.search('port.descr', values) + if not interface: + interface = jmespath.search('port.id.value', values) + if not interface: + interface = 'Unknown' + tmp.append(interface) + + # Add individual neighbor to output list + data_entries.append(tmp) + + headers = ["Device", "Local Port", "Protocol", "Capability", "Platform", "Remote Port"] + output = tabulate(data_entries, headers, numalign="left") + return capability_codes + output + +@_verify +def show_neighbors(raw: bool, interface: typing.Optional[str], detail: typing.Optional[bool]): + lldp_data = _get_raw_data(interface=interface, detail=detail) + if raw: + return lldp_data + else: + return _get_formatted_output(lldp_data) + +if __name__ == "__main__": + try: + res = vyos.opmode.run(sys.modules[__name__]) + if res: + print(res) + except (ValueError, vyos.opmode.Error) as e: + print(e) + sys.exit(1) diff --git a/src/op_mode/lldp_op.py b/src/op_mode/lldp_op.py deleted file mode 100755 index 17f6bf552..000000000 --- a/src/op_mode/lldp_op.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2020 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 jinja2 -import json - -from sys import exit -from tabulate import tabulate - -from vyos.util import cmd -from vyos.config import Config - -parser = argparse.ArgumentParser() -parser.add_argument("-a", "--all", action="store_true", help="Show LLDP neighbors on all interfaces") -parser.add_argument("-d", "--detail", action="store_true", help="Show detailes LLDP neighbor information on all interfaces") -parser.add_argument("-i", "--interface", action="store", help="Show LLDP neighbors on specific interface") - -# Please be careful if you edit the template. -lldp_out = """Capability Codes: R - Router, B - Bridge, W - Wlan r - Repeater, S - Station - D - Docsis, T - Telephone, O - Other - -Device ID Local Proto Cap Platform Port ID ---------- ----- ----- --- -------- ------- -{% for neighbor in neighbors %} -{% for local_if, info in neighbor.items() %} -{{ "%-25s" | format(info.chassis) }} {{ "%-9s" | format(local_if) }} {{ "%-6s" | format(info.proto) }} {{ "%-5s" | format(info.capabilities) }} {{ "%-20s" | format(info.platform[:18]) }} {{ info.remote_if }} -{% endfor %} -{% endfor %} -""" - -def get_neighbors(): - return cmd('/usr/sbin/lldpcli -f json show neighbors') - -def parse_data(data, interface): - output = [] - if not isinstance(data, list): - data = [data] - - for neighbor in data: - for local_if, values in neighbor.items(): - if interface is not None and local_if != interface: - continue - cap = '' - 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] - - for capability in capabilities: - if capability['enabled']: - if capability['type'] == 'Router': - cap += 'R' - if capability['type'] == 'Bridge': - cap += 'B' - if capability['type'] == 'Wlan': - cap += 'W' - if capability['type'] == 'Station': - cap += 'S' - if capability['type'] == 'Repeater': - cap += 'r' - if capability['type'] == 'Telephone': - cap += 'T' - if capability['type'] == 'Docsis': - cap += 'D' - if capability['type'] == 'Other': - cap += 'O' - - remote_if = 'Unknown' - if 'descr' in values.get('port', {}): - remote_if = values.get('port', {}).get('descr') - elif 'id' in values.get('port', {}): - remote_if = values.get('port', {}).get('id').get('value', 'Unknown') - - output.append({local_if: {'chassis': chassis, - 'remote_if': remote_if, - 'proto': values.get('via','Unknown'), - 'platform': c_value.get('descr', 'Unknown'), - 'capabilities': cap}}) - - output = {'neighbors': output} - return output - -if __name__ == '__main__': - args = parser.parse_args() - tmp = { 'neighbors' : [] } - - c = Config() - if not c.exists_effective(['service', 'lldp']): - print('Service LLDP is not configured') - exit(0) - - if args.detail: - print(cmd('/usr/sbin/lldpctl -f plain')) - exit(0) - elif args.all or args.interface: - tmp = json.loads(get_neighbors()) - neighbors = dict() - - if 'interface' in tmp.get('lldp'): - neighbors = tmp['lldp']['interface'] - - else: - parser.print_help() - exit(1) - - tmpl = jinja2.Template(lldp_out, trim_blocks=True) - config_text = tmpl.render(parse_data(neighbors, interface=args.interface)) - print(config_text) - - exit(0) diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py index a46571bd5..cf06de0e9 100755 --- a/src/op_mode/nat.py +++ b/src/op_mode/nat.py @@ -316,14 +316,20 @@ def show_statistics(raw: bool, direction: str, family: str): @_verify -def show_translations(raw: bool, direction: str, family: str, address: typing.Optional[str]): +def show_translations(raw: bool, direction: + str, family: str, + address: typing.Optional[str], + verbose: typing.Optional[bool]): family = 'ipv6' if family == 'inet6' else 'ipv4' - nat_translation = _get_raw_translation(direction, family=family, address=address) + nat_translation = _get_raw_translation(direction, + family=family, + address=address) if raw: return nat_translation else: - return _get_formatted_translation(nat_translation, direction, family, verbose) + return _get_formatted_translation(nat_translation, direction, family, + verbose) if __name__ == '__main__': diff --git a/src/op_mode/route.py b/src/op_mode/route.py index d07a34180..7f0f9cbac 100755 --- a/src/op_mode/route.py +++ b/src/op_mode/route.py @@ -54,16 +54,43 @@ frr_command_template = Template(""" {% endif %} """) -def show_summary(raw: bool): +def show_summary(raw: bool, family: str, table: typing.Optional[int], vrf: typing.Optional[str]): from vyos.util import cmd + if family == 'inet': + family_cmd = 'ip' + elif family == 'inet6': + family_cmd = 'ipv6' + else: + raise ValueError(f"Unsupported address family {family}") + + if (table is not None) and (vrf is not None): + raise ValueError("table and vrf options are mutually exclusive") + + # Replace with Jinja if it ever starts growing + if table: + table_cmd = f"table {table}" + else: + table_cmd = "" + + if vrf: + vrf_cmd = f"vrf {vrf}" + else: + vrf_cmd = "" + if raw: from json import loads - output = cmd(f"vtysh -c 'show ip route summary json'") - return loads(output) + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd} json'").strip() + + # If there are no routes in a table, its "JSON" output is an empty string, + # as of FRR 8.4.1 + if output: + return loads(output) + else: + return {} else: - output = cmd(f"vtysh -c 'show ip route summary'") + output = cmd(f"vtysh -c 'show {family_cmd} route {vrf_cmd} summary {table_cmd}'") return output def show(raw: bool, diff --git a/src/op_mode/show_ntp.sh b/src/op_mode/show_ntp.sh index e9dd6c5c9..85f8eda15 100755 --- a/src/op_mode/show_ntp.sh +++ b/src/op_mode/show_ntp.sh @@ -1,39 +1,34 @@ #!/bin/sh -basic=0 -info=0 +sourcestats=0 +tracking=0 while [[ "$#" -gt 0 ]]; do case $1 in - --info) info=1 ;; - --basic) basic=1 ;; - --server) server=$2; shift ;; + --sourcestats) sourcestats=1 ;; + --tracking) tracking=1 ;; *) echo "Unknown parameter passed: $1" ;; esac shift done -if ! ps -C ntpd &>/dev/null; then +if ! ps -C chronyd &>/dev/null; then echo NTP daemon disabled exit 1 fi -PID=$(pgrep ntpd) -VRF_NAME=$(ip vrf identify ${PID}) +PID=$(pgrep chronyd | head -n1) +VRF_NAME=$(ip vrf identify ) if [ ! -z ${VRF_NAME} ]; then VRF_CMD="sudo ip vrf exec ${VRF_NAME}" fi -if [ $basic -eq 1 ]; then - $VRF_CMD ntpq -n -c peers -elif [ $info -eq 1 ]; then - echo "=== sysingo ===" - $VRF_CMD ntpq -n -c sysinfo - echo - echo "=== kerninfo ===" - $VRF_CMD ntpq -n -c kerninfo -elif [ ! -z $server ]; then - $VRF_CMD /usr/sbin/ntpdate -q $server +if [ $sourcestats -eq 1 ]; then + $VRF_CMD chronyc sourcestats -v +elif [ $tracking -eq 1 ]; then + $VRF_CMD chronyc tracking -v +else + echo "Unknown option" fi |