diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | op-mode-definitions/traffic-dump.xml.in | 55 | ||||
-rw-r--r-- | src/op_mode/tcpdump.py | 165 |
3 files changed, 175 insertions, 48 deletions
@@ -61,12 +61,13 @@ op_mode_definitions: $(op_xml_obj) rm -f $(OP_TMPL_DIR)/clear/node.def rm -f $(OP_TMPL_DIR)/delete/node.def - # XXX: ping, traceroute and mtr must be able to recursivly call themselves as the + # XXX: tcpdump, ping, traceroute and mtr must be able to recursivly call themselves as the # options are provided from the scripts themselves ln -s ../node.tag $(OP_TMPL_DIR)/ping/node.tag/node.tag/ ln -s ../node.tag $(OP_TMPL_DIR)/traceroute/node.tag/node.tag/ ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/ ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traceroute/node.tag/node.tag/ + ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traffic/interface/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/traffic-dump.xml.in b/op-mode-definitions/traffic-dump.xml.in index 76e3ddce5..e86e69736 100644 --- a/op-mode-definitions/traffic-dump.xml.in +++ b/op-mode-definitions/traffic-dump.xml.in @@ -8,7 +8,7 @@ </properties> <children> <tagNode name="interface"> - <command>sudo tcpdump -i $4</command> + <command>${vyos_op_scripts_dir}/tcpdump.py $4</command> <properties> <help>Monitor traffic dump from an interface</help> <completionHelp> @@ -16,54 +16,15 @@ </completionHelp> </properties> <children> - <node name="verbose"> - <command>sudo tcpdump -vvv -ne -i $4</command> + <leafNode name="node.tag"> <properties> - <help>Provide more detailed packets for each monitored traffic</help> + <help>Traffic capture options</help> + <completionHelp> + <script>${vyos_op_scripts_dir}/tcpdump.py --get-options-nested "${COMP_WORDS[@]}"</script> + </completionHelp> </properties> - <children> - <tagNode name="filter"> - <command>sudo tcpdump -vvv -ne -i $4 "${@:6}"</command> - <properties> - <help>Monitor traffic matching filter conditions</help> - </properties> - </tagNode> - <tagNode name="save"> - <command>sudo tcpdump -vvv -ne -i $4 -w $6</command> - <properties> - <help>Save traffic dump from an interface to a file</help> - </properties> - <children> - <tagNode name="filter"> - <command>sudo tcpdump -vvv -ne -i $4 -w $6 "${@:8}"</command> - <properties> - <help>Save a dump of traffic matching filter conditions to a file</help> - </properties> - </tagNode> - </children> - </tagNode> - </children> - </node> - <tagNode name="filter"> - <command>sudo tcpdump -n -i $4 "${@:6}"</command> - <properties> - <help>Monitor traffic matching filter conditions</help> - </properties> - </tagNode> - <tagNode name="save"> - <command>sudo tcpdump -n -i $4 -w $6</command> - <properties> - <help>Save traffic dump from an interface to a file</help> - </properties> - <children> - <tagNode name="filter"> - <command>sudo tcpdump -n -i $4 -w $6 "${@:8}"</command> - <properties> - <help>Save a dump of traffic matching filter conditions to a file</help> - </properties> - </tagNode> - </children> - </tagNode> + <command>${vyos_op_scripts_dir}/tcpdump.py "${@:4}"</command> + </leafNode> </children> </tagNode> </children> diff --git a/src/op_mode/tcpdump.py b/src/op_mode/tcpdump.py new file mode 100644 index 000000000..607b59603 --- /dev/null +++ b/src/op_mode/tcpdump.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python3 + +# Copyright (C) 2024 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 sys + +from vyos.utils.process import call + +options = { + 'dump': { + 'cmd': '{command} -A', + 'type': 'noarg', + 'help': 'Print each packet (minus its link level header) in ASCII.' + }, + 'hexdump': { + 'cmd': '{command} -X', + 'type': 'noarg', + 'help': 'Print each packet (minus its link level header) in both hex and ASCII.' + }, + 'filter': { + 'cmd': '{command} \'{value}\'', + 'type': '<pcap-filter>', + 'help': 'Match traffic for capture and display with a pcap-filter expression.' + }, + 'numeric': { + 'cmd': '{command} -nn', + 'type': 'noarg', + 'help': 'Do not attempt to resolve addresses, protocols or services to names.' + }, + 'save': { + 'cmd': '{command} -w {value}', + 'type': '<file>', + 'help': 'Write captured raw packets to <file> rather than parsing or printing them out.' + }, + 'verbose': { + 'cmd': '{command} -vvv -ne', + 'type': 'noarg', + 'help': 'Parse packets with increased detail output, including link-level headers and extended decoding protocol sanity checks.' + }, +} + +tcpdump = 'sudo /usr/bin/tcpdump' + +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 completion_failure(option: str) -> None: + """ + Shows failure message after TAB when option is wrong + :param option: failure option + :type str: + """ + sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option)) + sys.stdout.write('<nocomps>') + sys.exit(1) + + +def expansion_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('<nocomps>') + 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: + expansion_failure(shortname, longnames) + longname = longnames[0] + if options[longname]['type'] == 'noarg': + command = options[longname]['cmd'].format( + command=command, value='') + elif not args: + sys.exit(f'monitor traffic: missing argument for {longname} option') + else: + command = options[longname]['cmd'].format( + command=command, value=args.first()) + return command + + +if __name__ == '__main__': + args = List(sys.argv[1:]) + ifname = args.first() + + # Slightly simplified & tweaked version of the code from mtr.py - it may be + # worthwhile to combine and centralise this in a common module. + if ifname == '--get-options-nested': + args.first() # pop monitor + args.first() # pop traffic + args.first() # pop interface + args.first() # pop <ifname> + usedoptionslist = [] + while args: + option = args.first() # pop option + matched = complete(option) # get option parameters + usedoptionslist.append(option) # list of used options + # Select options + if not args: + # remove from Possible completions used options + for o in usedoptionslist: + if o in matched: + matched.remove(o) + if not matched: + sys.stdout.write('<nocomps>') + else: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + + if len(matched) > 1: + sys.stdout.write(' '.join(matched)) + sys.exit(0) + # If option doesn't have value + if matched: + if options[matched[0]]['type'] == 'noarg': + continue + else: + # Unexpected option + completion_failure(option) + + value = args.first() # pop option's value + if not args: + matched = complete(option) + helplines = options[matched[0]]['type'] + # Run helpfunction to get list of possible values + if 'helpfunction' in options[matched[0]]: + result = options[matched[0]]['helpfunction']() + if result: + helplines = '\n' + ' '.join(result) + sys.stdout.write(helplines) + sys.exit(0) + + command = convert(tcpdump, args) + call(f'{command} -i {ifname}') |