From 55d2ee80dcddfbaae8600f6389088f1d718965a8 Mon Sep 17 00:00:00 2001 From: 3roin Date: Thu, 5 May 2022 12:28:28 +0000 Subject: op-mode: T4416: Rewrite 'traceroute' op-command and expand available options using python --- Makefile | 3 +- op-mode-definitions/traceroute.xml.in | 220 ++-------------------------------- src/op_mode/traceroute.py | 207 ++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 213 deletions(-) create mode 100755 src/op_mode/traceroute.py diff --git a/Makefile b/Makefile index 3e838540c..2333eebed 100644 --- a/Makefile +++ b/Makefile @@ -60,9 +60,10 @@ op_mode_definitions: $(op_xml_obj) rm -f $(OP_TMPL_DIR)/show/node.def rm -f $(OP_TMPL_DIR)/show/system/node.def - # XXX: ping must be able to recursivly call itself as the + # XXX: ping and traceroute must be able to recursivly call itself as the # options are provided from the script itself ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/traceroute/node.tag/node.tag/ # XXX: test if there are empty node.def files - this is not allowed as these # could mask help strings or mandatory priority statements diff --git a/op-mode-definitions/traceroute.xml.in b/op-mode-definitions/traceroute.xml.in index e3217235c..aba0f45e3 100644 --- a/op-mode-definitions/traceroute.xml.in +++ b/op-mode-definitions/traceroute.xml.in @@ -2,226 +2,22 @@ - Track network path to node - - <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - - - /usr/bin/traceroute "$2" - - - - Track network path to node + Trace network path to node <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> + ${vyos_op_scripts_dir}/traceroute.py ${@:2} - + - Explicitly use IPv4 when tracing the path + Traceroute options - <hostname> <x.x.x.x> + - /usr/bin/traceroute -4 "$3" - - - - Route tracing and port detection using TCP - - sudo /usr/bin/tcptraceroute "$3" - - - - TCP port to connect to for path tracing - - 0-65535 - - - sudo /usr/bin/tcptraceroute "$3" $6 - - - - - - - - Explicitly use IPv6 when tracing the path - - <hostname> <h:h:h:h:h:h:h:h> - - - /usr/bin/traceroute -6 "$3" - - - - Use TCP/IPv6 packets to perform a traceroute - - sudo /usr/bin/tcptraceroute6 "$3" - - - - TCP port to connect to for path tracing - - 0-65535 - - - sudo /usr/bin/tcptraceroute6 "$3" $6 - - - - - - - - Track network path to specified node via given VRF - - vrf name - - - - - - - Track network path to specified node via given VRF - - <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - - - sudo ip vrf exec "$3" /usr/bin/traceroute "$4" - - - - Explicitly use IPv4 when tracing the path via given VRF - - <hostname> <x.x.x.x> - - - sudo ip vrf exec "$3" /usr/bin/traceroute -4 "$5" - - - - Route tracing and port detection using TCP - - sudo ip vrf exec "$3" /usr/bin/tcptraceroute "$5" - - - - TCP port to connect to for path tracing - - 0-65535 - - - sudo ip vrf exec "$3" /usr/bin/tcptraceroute "$5" $8 - - - - - - - - Explicitly use IPv6 when tracing the path via given VRF - - <hostname> <h:h:h:h:h:h:h:h> - - - sudo ip vrf exec "$3" /usr/bin/traceroute -6 "$5" - - - - Use TCP/IPv6 packets to perform a traceroute - - sudo ip vrf exec "$3" /usr/bin/tcptraceroute6 "$5" - - - - TCP port to connect to for path tracing - - 0-65535 - - - sudo ip vrf exec "$3" /usr/bin/tcptraceroute6 "$5" $8 - - - - - - - - - - - - - - Monitor path to destination in realtime - - <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - - - /usr/bin/mtr "$3" - - - - - - IPv4 fully qualified domain name (FQDN) - - <fqdn> - - - /usr/bin/mtr -4 "$4" - - - - IPv6 fully qualified domain name (FQDN) - - <fqdn> - - - /usr/bin/mtr -6 "$4" - - - - Monitor path to destination in realtime via given VRF - - vrf name - - - - - - IPv4 fully qualified domain name (FQDN) - - <fqdn> - - - sudo ip vrf exec "$4" /usr/bin/mtr -4 "$6" - - - - IPv6 fully qualified domain name (FQDN) - - <fqdn> - - - sudo ip vrf exec "$4" /usr/bin/mtr -6 "$6" - - - - Track network path to specified node via given VRF - - <hostname> <x.x.x.x> <h:h:h:h:h:h:h:h> - - - sudo ip vrf exec "$4" /usr/bin/mtr "$5" - - - - - + ${vyos_op_scripts_dir}/traceroute.py ${@:2} + - + diff --git a/src/op_mode/traceroute.py b/src/op_mode/traceroute.py new file mode 100755 index 000000000..4299d6e5f --- /dev/null +++ b/src/op_mode/traceroute.py @@ -0,0 +1,207 @@ +#! /usr/bin/env python3 + +# 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 +# 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 . + +import os +import sys +import socket +import ipaddress + +options = { + 'backward-hops': { + 'traceroute': '{command} --back', + 'type': 'noarg', + 'help': 'Display number of backward hops when they different from the forwarded path' + }, + 'bypass': { + 'traceroute': '{command} -r', + 'type': 'noarg', + 'help': 'Bypass the normal routing tables and send directly to a host on an attached network' + }, + 'do-not-fragment': { + 'traceroute': '{command} -F', + 'type': 'noarg', + 'help': 'Do not fragment probe packets.' + }, + 'first-ttl': { + 'traceroute': '{command} -f {value}', + 'type': '', + 'help': 'Specifies with what TTL to start. Defaults to 1.' + }, + 'icmp': { + 'traceroute': '{command} -I', + 'type': 'noarg', + 'help': 'Use ICMP ECHO for tracerouting' + }, + 'interface': { + 'traceroute': '{command} -i {value}', + 'type': '', + 'help': 'Source interface' + }, + 'lookup-as': { + 'traceroute': '{command} -A', + 'type': 'noarg', + 'help': 'Perform AS path lookups' + }, + 'mark': { + 'traceroute': '{command} --fwmark={value}', + 'type': '', + 'help': 'Set the firewall mark for outgoing packets' + }, + 'no-resolve': { + 'traceroute': '{command} -n', + 'type': 'noarg', + 'help': 'Do not resolve hostnames' + }, + 'port': { + 'traceroute': '{command} -p {value}', + 'type': '', + 'help': 'Destination port' + }, + 'source-address': { + 'traceroute': '{command} -s {value}', + 'type': ' ', + 'help': 'Specify source IP v4/v6 address' + }, + 'tcp': { + 'traceroute': '{command} -T', + 'type': 'noarg', + 'help': 'Use TCP SYN for tracerouting (default port is 80)' + }, + 'tos': { + 'traceroute': '{commad} -t {value}', + 'type': '', + 'help': 'Mark packets with specified TOS' + }, + 'ttl': { + 'traceroute': '{command} -m {value}', + 'type': '', + 'help': 'Maximum number of hops' + }, + 'udp': { + 'traceroute': '{command} -U', + 'type': 'noarg', + 'help': 'Use UDP to particular port for tracerouting (default port is 53)' + }, + 'vrf': { + 'traceroute': 'sudo ip vrf exec {value} {command}', + 'type': '', + 'help': 'Use specified VRF table', + 'dflt': 'default'} +} + +traceroute = { + 4: '/bin/traceroute -4', + 6: '/bin/traceroute -6', +} + + +class List (list): + def first (self): + return self.pop(0) if self else '' + + def last(self): + return self.pop() if self else '' + + def prepend(self,value): + self.insert(0,value) + + +def expension_failure(option, completions): + reason = 'Ambiguous' if completions else 'Invalid' + sys.stderr.write('\n\n {} command: {} [{}]\n\n'.format(reason,' '.join(sys.argv), option)) + if completions: + sys.stderr.write(' Possible completions:\n ') + sys.stderr.write('\n '.join(completions)) + sys.stderr.write('\n') + sys.stdout.write('') + sys.exit(1) + + +def complete(prefix): + return [o for o in options if o.startswith(prefix)] + + +def convert(command, args): + while args: + shortname = args.first() + longnames = complete(shortname) + if len(longnames) != 1: + expension_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['traceroute'].format( + command=command, value='') + elif not args: + sys.exit(f'traceroute: missing argument for {longname} option') + else: + command = options[longname]['traceroute'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + host = args.first() + + if not host: + sys.exit("traceroute: Missing host") + + if host == '--get-options': + args.first() # pop traceroute + args.first() # pop IP + while args: + option = args.first() + + matched = complete(option) + if not args: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1 : + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if options[matched[0]]['type'] == 'noarg': + continue + + value = args.first() + if not args: + matched = complete(option) + sys.stdout.write(options[matched[0]]['type']) + sys.exit(0) + + for name,option in options.items(): + if 'dflt' in option and name not in args: + args.append(name) + args.append(option['dflt']) + + try: + ip = socket.gethostbyname(host) + except UnicodeError: + sys.exit(f'tracroute: Unknown host: {host}') + except socket.gaierror: + ip = host + + try: + version = ipaddress.ip_address(ip).version + except ValueError: + sys.exit(f'traceroute: Unknown host: {host}') + + command = convert(traceroute[version],args) + + # print(f'{command} {host}') + os.system(f'{command} {host}') + -- cgit v1.2.3