summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniil Baturin <daniil@vyos.io>2024-06-13 16:51:59 +0200
committerGitHub <noreply@github.com>2024-06-13 16:51:59 +0200
commite1916a16627f34cd25aa8768446a0758de281ba6 (patch)
tree25ecf929a4f5f720501c67d2ec38287ebfc081b6
parentc4269a9ddb0342dee78175f557e331df518d55d3 (diff)
parente74859243042f87c4e3e6fa8e90902641d67b081 (diff)
downloadvyos-1x-e1916a16627f34cd25aa8768446a0758de281ba6.tar.gz
vyos-1x-e1916a16627f34cd25aa8768446a0758de281ba6.zip
Merge pull request #3601 from talmakion/bugfix/T6456
T6456: Convert "monitor traffic" to modern op-mode wrapper
-rw-r--r--Makefile3
-rw-r--r--op-mode-definitions/traffic-dump.xml.in55
-rw-r--r--src/op_mode/tcpdump.py165
3 files changed, 175 insertions, 48 deletions
diff --git a/Makefile b/Makefile
index 3b26273d6..cc382e206 100644
--- a/Makefile
+++ b/Makefile
@@ -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}')