From 67b342a2c59035a5282f92e3b3d77f698dc8887c Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Thu, 2 Jun 2022 09:20:37 -0400 Subject: T4446: use a unified neighbor display script --- op-mode-definitions/ipv6-route.xml.in | 30 +++++-- op-mode-definitions/show-arp.xml.in | 4 +- op-mode-definitions/show-ip.xml.in | 24 ++++- op-mode-definitions/show-ipv6.xml.in | 2 +- src/op_mode/show_neigh.py | 162 ++++++++++++++++++---------------- 5 files changed, 133 insertions(+), 89 deletions(-) diff --git a/op-mode-definitions/ipv6-route.xml.in b/op-mode-definitions/ipv6-route.xml.in index 5f20444d4..4f8792f9f 100644 --- a/op-mode-definitions/ipv6-route.xml.in +++ b/op-mode-definitions/ipv6-route.xml.in @@ -7,7 +7,7 @@ - Show IPv6 routing information + Show IPv6 networking information @@ -16,14 +16,32 @@ netstat -gn6 - - + - Show IPv6 Neighbor Discovery (ND) information + Show IPv6 neighbor (NDP) table ${vyos_op_scripts_dir}/show_neigh.py --family inet6 - - + + + + Show IPv6 neighbor table for specified interface + + + + + ${vyos_op_scripts_dir}/show_neigh.py --family inet6 --interface "$5" + + + + Show IPv6 neighbors with specified state + + reachable stale failed permanent + + + ${vyos_op_scripts_dir}/show_neigh.py --family inet6 --state "$5" + + + diff --git a/op-mode-definitions/show-arp.xml.in b/op-mode-definitions/show-arp.xml.in index 12e7d3aa2..58cc6e45e 100644 --- a/op-mode-definitions/show-arp.xml.in +++ b/op-mode-definitions/show-arp.xml.in @@ -6,7 +6,7 @@ Show Address Resolution Protocol (ARP) information - /usr/sbin/arp -e -n + ${vyos_op_scripts_dir}/show_neigh.py --family inet @@ -15,7 +15,7 @@ - /usr/sbin/arp -e -n -i "$4" + ${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$4" diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in index 91564440d..d342ac192 100644 --- a/op-mode-definitions/show-ip.xml.in +++ b/op-mode-definitions/show-ip.xml.in @@ -4,14 +4,34 @@ - Show IPv4 routing information + Show IPv4 networking information - Show IPv4 Neighbor Discovery (ND) information + Show IPv4 neighbor (ARP) table ${vyos_op_scripts_dir}/show_neigh.py --family inet + + + + Show IPv4 neighbor table for specified interface + + + + + ${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$5" + + + + Show IPv4 neighbors with specified state + + reachable stale failed permanent + + + ${vyos_op_scripts_dir}/show_neigh.py --family inet --state "$5" + + diff --git a/op-mode-definitions/show-ipv6.xml.in b/op-mode-definitions/show-ipv6.xml.in index a59c8df0c..66bc2485a 100644 --- a/op-mode-definitions/show-ipv6.xml.in +++ b/op-mode-definitions/show-ipv6.xml.in @@ -4,7 +4,7 @@ - Show IPv6 routing information + Show IPv6 networking information diff --git a/src/op_mode/show_neigh.py b/src/op_mode/show_neigh.py index 94e745493..4d96ee869 100755 --- a/src/op_mode/show_neigh.py +++ b/src/op_mode/show_neigh.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2022 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 @@ -14,83 +14,89 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -#ip -j -f inet neigh list | jq -#[ - #{ - #"dst": "192.168.101.8", - #"dev": "enp0s25", - #"lladdr": "78:d2:94:72:77:7e", - #"state": [ - #"STALE" - #] - #}, - #{ - #"dst": "192.168.101.185", - #"dev": "enp0s25", - #"lladdr": "34:46:ec:76:f8:9b", - #"state": [ - #"STALE" - #] - #}, - #{ - #"dst": "192.168.101.225", - #"dev": "enp0s25", - #"lladdr": "c2:cb:fa:bf:a0:35", - #"state": [ - #"STALE" - #] - #}, - #{ - #"dst": "192.168.101.1", - #"dev": "enp0s25", - #"lladdr": "00:98:2b:f8:3f:11", - #"state": [ - #"REACHABLE" - #] - #}, - #{ - #"dst": "192.168.101.181", - #"dev": "enp0s25", - #"lladdr": "d8:9b:3b:d5:88:22", - #"state": [ - #"STALE" - #] - #} -#] +# Sample output of `ip --json neigh list`: +# +# [ +# { +# "dst": "192.168.1.1", +# "dev": "eth0", # Missing if `dev ...` option is used +# "lladdr": "00:aa:bb:cc:dd:ee", # May be missing for failed entries +# "state": [ +# "REACHABLE" +# ] +# }, +# ] import sys -import argparse -import json -from vyos.util import cmd - -def main(): - #parese args - parser = argparse.ArgumentParser() - parser.add_argument('--family', help='Protocol family', required=True) - args = parser.parse_args() - - neigh_raw_json = cmd(f'ip -j -f {args.family} neigh list') - neigh_raw_json = neigh_raw_json.lower() - neigh_json = json.loads(neigh_raw_json) - - format_neigh = '%-50s %-10s %-20s %s' - print(format_neigh % ("IP Address", "Device", "State", "LLADDR")) - print(format_neigh % ("----------", "------", "-----", "------")) - - if neigh_json is not None: - for neigh_item in neigh_json: - dev = neigh_item['dev'] - dst = neigh_item['dst'] - lladdr = neigh_item['lladdr'] if 'lladdr' in neigh_item else '' - state = neigh_item['state'] - - i = 0 - for state_item in state: - if i == 0: - print(format_neigh % (dst, dev, state_item, lladdr)) - else: - print(format_neigh % ('', '', state_item, '')) - i+=1 - + + +def get_raw_data(family, device=None, state=None): + from json import loads + from vyos.util import cmd + + if device: + device = "dev {0}".format(device) + else: + device = "" + + if state: + state = "nud {0}".format(state) + else: + state = "" + + neigh_cmd = "ip --family {0} --json neighbor list {1} {2}".format(family, device, state) + + data = loads(cmd(neigh_cmd)) + + return data + +def get_formatted_output(family, device=None, state=None): + from tabulate import tabulate + + def entry_to_list(e, intf=None): + dst = e["dst"] + + # State is always a list in the iproute2 output + state = ", ".join(e["state"]) + + # Link layer address is absent from e.g. FAILED entries + if "lladdr" in e: + lladdr = e["lladdr"] + else: + lladdr = None + + # Device field is absent from outputs of `ip neigh list dev ...` + if "dev" in e: + dev = e["dev"] + elif device: + dev = device + else: + raise ValueError("interface is not defined") + + return [dst, dev, lladdr, state] + + neighs = get_raw_data(family, device=device, state=state) + neighs = map(entry_to_list, neighs) + + headers = ["Address", "Interface", "Link layer address", "State"] + return tabulate(neighs, headers) + if __name__ == '__main__': - main() + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("-f", "--family", type=str, default="inet", help="Address family") + parser.add_argument("-i", "--interface", type=str, help="Network interface") + parser.add_argument("-s", "--state", type=str, help="Neighbor table entry state") + + args = parser.parse_args() + + if args.state: + if args.state not in ["reachable", "failed", "stale", "permanent"]: + raise ValueError("Incorrect state {0}! Must be one of: reachable, stale, failed, permanent".format(args.state)) + + try: + print(get_formatted_output(args.family, device=args.interface, state=args.state)) + except ValueError as e: + print(e) + sys.exit(1) -- cgit v1.2.3 From e75ce8b7fc02c984f2d0ddaef3768d9f6431bb80 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 6 Jun 2022 13:29:37 +0300 Subject: T4446: use format strings instead of old-fasionhed format method --- src/op_mode/show_neigh.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/op_mode/show_neigh.py b/src/op_mode/show_neigh.py index 4d96ee869..d874bd544 100755 --- a/src/op_mode/show_neigh.py +++ b/src/op_mode/show_neigh.py @@ -35,16 +35,16 @@ def get_raw_data(family, device=None, state=None): from vyos.util import cmd if device: - device = "dev {0}".format(device) + device = f"dev {device}" else: device = "" if state: - state = "nud {0}".format(state) + state = f"nud {state}" else: state = "" - neigh_cmd = "ip --family {0} --json neighbor list {1} {2}".format(family, device, state) + neigh_cmd = f"ip --family {family} --json neighbor list {device} {state}" data = loads(cmd(neigh_cmd)) @@ -93,7 +93,7 @@ if __name__ == '__main__': if args.state: if args.state not in ["reachable", "failed", "stale", "permanent"]: - raise ValueError("Incorrect state {0}! Must be one of: reachable, stale, failed, permanent".format(args.state)) + raise ValueError(f"""Incorrect state "{args.state}"! Must be one of: reachable, stale, failed, permanent""") try: print(get_formatted_output(args.family, device=args.interface, state=args.state)) -- cgit v1.2.3