Skip to content

Commit

Permalink
Merge pull request #3601 from talmakion/bugfix/T6456
Browse files Browse the repository at this point in the history
T6456: Convert "monitor traffic" to modern op-mode wrapper
  • Loading branch information
dmbaturin authored Jun 13, 2024
2 parents c4269a9 + e748592 commit e1916a1
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 48 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 8 additions & 47 deletions op-mode-definitions/traffic-dump.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,23 @@
</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>
<script>${vyos_completion_dir}/list_dumpable_interfaces.py</script>
</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>
Expand Down
165 changes: 165 additions & 0 deletions src/op_mode/tcpdump.py
Original file line number Diff line number Diff line change
@@ -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}')

0 comments on commit e1916a1

Please sign in to comment.