diff options
-rw-r--r-- | op-mode-definitions/ipv6-route.xml.in | 30 | ||||
-rw-r--r-- | op-mode-definitions/show-arp.xml.in | 4 | ||||
-rw-r--r-- | op-mode-definitions/show-ip.xml.in | 24 | ||||
-rw-r--r-- | op-mode-definitions/show-ipv6.xml.in | 2 | ||||
-rwxr-xr-x | 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 @@ <children> <node name="ipv6"> <properties> - <help>Show IPv6 routing information</help> + <help>Show IPv6 networking information</help> </properties> <children> <leafNode name="groups"> @@ -16,14 +16,32 @@ </properties> <command>netstat -gn6</command> </leafNode> - - <leafNode name="neighbors"> + <node name="neighbors"> <properties> - <help>Show IPv6 Neighbor Discovery (ND) information</help> + <help>Show IPv6 neighbor (NDP) table</help> </properties> <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6</command> - </leafNode> - + <children> + <tagNode name="interface"> + <properties> + <help>Show IPv6 neighbor table for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -b</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --interface "$5"</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show IPv6 neighbors with specified state</help> + <completionHelp> + <list>reachable stale failed permanent</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet6 --state "$5"</command> + </tagNode> + </children> + </node> </children> </node> </children> 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 @@ <properties> <help>Show Address Resolution Protocol (ARP) information</help> </properties> - <command>/usr/sbin/arp -e -n</command> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command> <children> <tagNode name="interface"> <properties> @@ -15,7 +15,7 @@ <script>${vyos_completion_dir}/list_interfaces.py -b</script> </completionHelp> </properties> - <command>/usr/sbin/arp -e -n -i "$4"</command> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$4"</command> </tagNode> </children> </node> 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 @@ <children> <node name="ip"> <properties> - <help>Show IPv4 routing information</help> + <help>Show IPv4 networking information</help> </properties> <children> <node name="neighbors"> <properties> - <help>Show IPv4 Neighbor Discovery (ND) information</help> + <help>Show IPv4 neighbor (ARP) table</help> </properties> <command>${vyos_op_scripts_dir}/show_neigh.py --family inet</command> + <children> + <tagNode name="interface"> + <properties> + <help>Show IPv4 neighbor table for specified interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces.py -b</script> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --interface "$5"</command> + </tagNode> + <tagNode name="state"> + <properties> + <help>Show IPv4 neighbors with specified state</help> + <completionHelp> + <list>reachable stale failed permanent</list> + </completionHelp> + </properties> + <command>${vyos_op_scripts_dir}/show_neigh.py --family inet --state "$5"</command> + </tagNode> + </children> </node> </children> </node> 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 @@ <children> <node name="ipv6"> <properties> - <help>Show IPv6 routing information</help> + <help>Show IPv6 networking information</help> </properties> <children> <node name="access-list"> diff --git a/src/op_mode/show_neigh.py b/src/op_mode/show_neigh.py index 94e745493..d874bd544 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 <http://www.gnu.org/licenses/>. -#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 = f"dev {device}" + else: + device = "" + + if state: + state = f"nud {state}" + else: + state = "" + + neigh_cmd = f"ip --family {family} --json neighbor list {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(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)) + except ValueError as e: + print(e) + sys.exit(1) |