[ovs-dev] [RFC] ovs-tcpdump: Add a tcpdump wrapper utility
Aaron Conole
aconole at redhat.com
Wed May 25 15:09:47 UTC 2016
Lance Richardson <lrichard at redhat.com> writes:
> ----- Original Message -----
>> From: "Aaron Conole" <aconole at redhat.com>
>> To: dev at openvswitch.org
>> Sent: Tuesday, May 24, 2016 4:35:29 PM
>> Subject: [ovs-dev] [RFC] ovs-tcpdump: Add a tcpdump wrapper utility
>>
>> Currently, there is some documentation which describes setting up and
>> using port mirrors for bridges. This documentation is helpful to setup
>> a packet capture for specific ports.
>>
>> However, a utility to do such packet capture would be valuable, both
>> as an exercise in documenting the steps an additional time, and as a way
>> of providing an out-of-the-box experience for running a capture.
>>
>> This commit adds a tcpdump-wrapper utility for such purpose. It uses the
>> Open vSwitch python library to add/remove ports and mirrors to/from the
>> Open vSwitch database. It will create a tcpdump instance listening on
>> the mirror port (allowing the user to specify additional arguments), and
>> dump data to the screen (or otherwise).
>>
>> Signed-off-by: Aaron Conole <aconole at redhat.com>
>> ---
>> NEWS | 2 +
>> utilities/automake.mk | 5 +
>> utilities/ovs-tcpdump.8.in | 38 +++++
>> utilities/ovs-tcpdump.in | 398
>> +++++++++++++++++++++++++++++++++++++++++++++
>> 4 files changed, 443 insertions(+)
>> create mode 100644 utilities/ovs-tcpdump.8.in
>> create mode 100755 utilities/ovs-tcpdump.in
>>
>> diff --git a/NEWS b/NEWS
>> index 4e81cad..a32350c 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -54,6 +54,8 @@ Post-v2.5.0
>> * Flow based tunnel match and action can be used for IPv6 address using
>> tun_ipv6_src, tun_ipv6_dst fields.
>> * Added support for IPv6 tunnels to native tunneling.
>> + - A wrapper script, 'ovs-tcpdump', to easily port-mirror an OVS port and
>> + watch with tcpdump
>>
>> v2.5.0 - 26 Feb 2016
>> ---------------------
>> diff --git a/utilities/automake.mk b/utilities/automake.mk
>> index 1cc66b6..f236ec4 100644
>> --- a/utilities/automake.mk
>> +++ b/utilities/automake.mk
>> @@ -12,6 +12,7 @@ bin_SCRIPTS += \
>> utilities/ovs-l3ping \
>> utilities/ovs-parse-backtrace \
>> utilities/ovs-pcap \
>> + utilities/ovs-tcpdump \
>> utilities/ovs-tcpundump \
>> utilities/ovs-test \
>> utilities/ovs-vlan-test
>> @@ -52,6 +53,7 @@ EXTRA_DIST += \
>> utilities/ovs-pipegen.py \
>> utilities/ovs-pki.in \
>> utilities/ovs-save \
>> + utilities/ovs-tcpdump.in \
>> utilities/ovs-tcpundump.in \
>> utilities/ovs-test.in \
>> utilities/ovs-vlan-test.in \
>> @@ -69,6 +71,7 @@ MAN_ROOTS += \
>> utilities/ovs-parse-backtrace.8 \
>> utilities/ovs-pcap.1.in \
>> utilities/ovs-pki.8.in \
>> + utilities/ovs-tcpdump.8.in \
>> utilities/ovs-tcpundump.1.in \
>> utilities/ovs-vlan-bug-workaround.8.in \
>> utilities/ovs-test.8.in \
>> @@ -94,6 +97,8 @@ DISTCLEANFILES += \
>> utilities/ovs-pki.8 \
>> utilities/ovs-sim \
>> utilities/ovs-sim.1 \
>> + utilities/ovs-tcpdump \
>> + utilities/ovs-tcpdump.8 \
>> utilities/ovs-tcpundump \
>> utilities/ovs-tcpundump.1 \
>> utilities/ovs-test \
>> diff --git a/utilities/ovs-tcpdump.8.in b/utilities/ovs-tcpdump.8.in
>> new file mode 100644
>> index 0000000..044e053
>> --- /dev/null
>> +++ b/utilities/ovs-tcpdump.8.in
>> @@ -0,0 +1,38 @@
>> +.TH ovs\-tcpdump 8 "@VERSION@" "Open vSwitch" "Open vSwitch Manual"
>> +.
>> +.SH NAME
>> +ovs\-tcpdump \- Dump traffic from an Open vSwitch port using \fBtcpdump\fR.
>> +.
>> +.SH SYNOPSIS
>> +\fBovs\-tcpdump\fR \fB\-i\fR \fIport\fR \fBtcpdump options...\fR
>> +.
>> +.SH DESCRIPTION
>> +\fBovs\-tcpdump\fR creates switch mirror ports in the \fBovs\-vswitchd\fR
>> +daemon and executes \fBtcpdump\fR to listen against those ports. When the
>> +\fBtcpdump\fR instance exits, it then cleans up the mirror port it created.
>> +.PP
>> +\fBovs\-tcpdump\fR will not allow multiple mirrors for the same port. It has
>> +some logic to parse the current configuration and prevent duplicate mirrors.
>> +.PP
>> +The \fB\-i\fR option may not appear multiple times.
>> +.
>> +.SH "OPTIONS"
>> +.so lib/common.man
>> +.
>> +.IP "\fB\-i\fR"
>> +.IQ "\fB\-\-interface\fR"
>> +The interface for which a mirror port should be created, and packets should
>> +be dumped.
>> +.
>> +.IP "\fB\-\-db\-sock\fR"
>> +The Open vSwitch database socket connection string. The default is
>> +\fIunix:@RUNDIR@/openvswitch/db.sock\fR
>> +.
>> +.SH "SEE ALSO"
>> +.
>> +.BR ovs\-appctl (8),
>> +.BR ovs\-vswitchd (8),
>> +.BR ovs\-pcap (1),
>> +.BR ovs\-tcpundump (1),
>> +.BR tcpdump (8),
>> +.BR wireshark (8).
>> diff --git a/utilities/ovs-tcpdump.in b/utilities/ovs-tcpdump.in
>> new file mode 100755
>> index 0000000..b1cd652
>> --- /dev/null
>> +++ b/utilities/ovs-tcpdump.in
>> @@ -0,0 +1,398 @@
>> +#! /usr/bin/env /usr/bin/python
>> +#
>> +# Copyright (c) 2016 Red Hat, Inc.
>> +#
>> +# Licensed under the Apache License, Version 2.0 (the "License");
>> +# you may not use this file except in compliance with the License.
>> +# You may obtain a copy of the License at:
>> +#
>> +# http://www.apache.org/licenses/LICENSE-2.0
>> +#
>> +# Unless required by applicable law or agreed to in writing, software
>> +# distributed under the License is distributed on an "AS IS" BASIS,
>> +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
>> +# See the License for the specific language governing permissions and
>> +# limitations under the License.
>> +
>> +import subprocess
>> +import sys
>> +import time
>> +import netifaces
>> +import os
>> +import pwd
>> +
>> +try:
>> + from ovs.stream import Stream
>> + from ovs.db import idl
>> + from ovs.poller import Poller
>> + from ovs import jsonrpc
>> +except:
>> + print "ERROR: Please install the correct Open vSwitch python support"
>> + print " libraries (version @VERSION@)."
>> + sys.exit(1)
>> +
>> +
>> +def _doexec(*args, **kwargs):
>> + """Executes an application and returns a set of pipes to be used to
>> + perform io"""
>> + shell = len(args) == 1
>> + proc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell,
>> + bufsize=0)
>> + return proc
>> +
>> +
>> +def username():
>> + return pwd.getpwuid(os.getuid())[0]
>> +
>> +
>> +def usage():
>> + print """\
>> +%(prog)s: Open vSwitch tcpdump helper.
>> +usage: %(prog)s -i interface [TCPDUMP OPTIONS]
>> +where TCPDUMP OPTIONS represents the options normally passed to tcpdump.
>> +
>> +The following options are available:
>> + -h, --help display this help message
>> + -V, --version display version information
>> + -i, --interface Open vSwitch interface to mirror and tcpdump
>> + --mirror-to The name for the mirror port to use (optional)
>> + Default 'mi_INTERFACE'
>> + --db-sock A connection string to reach the Open vSwitch
>> + ovsdb-server.
>> + Default 'unix:@RUNDIR@/openvswitch/db.sock'
>> +""" % {'prog': sys.argv[0]}
>> + sys.exit(0)
>> +
>> +
>> +class OVSDBException(Exception):
>> + pass
>> +
>> +
>> +class OVSDB(object):
>> + @staticmethod
>> + def wait_for_db_change(idl):
>> + seq = idl.change_seqno
>> + stop = time.time() + 10
>> + while idl.change_seqno == seq and not idl.run():
>> + poller = Poller()
>> + idl.wait(poller)
>> + poller.block()
>> + if time.time() >= stop:
>> + raise Exception('Retry Timeout')
>> +
>> + def __init__(self, db_sock):
>> + self._db_sock = db_sock
>> + self._txn = None
>> + schema = self._get_schema()
>> + schema.register_all()
>> + self._idl_conn = idl.Idl(db_sock, schema)
>> + OVSDB.wait_for_db_change(self._idl_conn) # Initial Sync with DB
>> +
>> + def _get_schema(self):
>> + error, strm = Stream.open_block(Stream.open(self._db_sock))
>> + if error:
>> + raise Exception("Unable to connect to %s" % self._db_sock)
>> + rpc = jsonrpc.Connection(strm)
>> + req = jsonrpc.Message.create_request('get_schema', ['Open_vSwitch'])
>> + error, resp = rpc.transact_block(req)
>> + rpc.close()
>> +
>> + if error or resp.error:
>> + raise Exception('Unable to retrieve schema.')
>> + return idl.SchemaHelper(None, resp.result)
>> +
>> + def get_table(self, table_name):
>> + return self._idl_conn.tables[table_name]
>> +
>> + def _start_txn(self):
>> + if self._txn is not None:
>> + raise OVSDBException("ERROR: A transaction was started already")
>> + self._idl_conn.change_seqno += 1
>> + self._txn = idl.Transaction(self._idl_conn)
>> + return self._txn
>> +
>> + def _complete_txn(self, try_again_fn):
>> + if self._txn is None:
>> + raise OVSDBException("ERROR: Not in a transaction")
>> + status = self._txn.commit_block()
>> + if status is idl.Transaction.TRY_AGAIN:
>> + if self._idl_conn._session.rpc.status != 0:
>> + self._idl_conn.force_reconnect()
>> + OVSDB.wait_for_db_change(self._idl_conn)
>> + return try_again_fn(self)
>> + elif status is idl.Transaction.ERROR:
>> + return False
>> +
>> + def _find_row(self, table_name, find):
>> + return next(
>> + (row for row in self.get_table(table_name).rows.values()
>> + if find(row)), None)
>> +
>> + def _find_row_by_name(self, table_name, value):
>> + return self._find_row(table_name, lambda row: row.name == value)
>> +
>> + def port_exists(self, port_name):
>> + return bool(self._find_row_by_name('Port', port_name))
>> +
>> + def port_bridge(self, port_name):
>> + try:
>> + row = self._find_row_by_name('Interface', port_name)
>> + port = self._find_row('Port', lambda x: row in x.interfaces)
>> + br = self._find_row('Bridge', lambda x: port in x.ports)
>> + return br.name
>> + except:
>> + raise OVSDBException('Unable to find port %s bridge' %
>> port_name)
>> +
>> + def interface_exists(self, intf_name):
>> + return bool(self._find_row_by_name('Interface', intf_name))
>> +
>> + def mirror_exists(self, mirror_name):
>> + return bool(self._find_row_by_name('Mirror', mirror_name))
>> +
>> + def interface_uuid(self, intf_name):
>> + row = self._find_row_by_name('Interface', intf_name)
>> + if bool(row):
>> + return row.uuid
>> + raise OVSDBException('No such interface: %s' % intf_name)
>> +
>> + def make_interface(self, intf_name, execute_transaction=True):
>> + if self.interface_exists(intf_name):
>> + print "INFO: Interface exists."
>> + return self.interface_uuid(intf_name)
>> +
>> + txn = self._start_txn()
>> + tmp_row = txn.insert(self.get_table('Interface'))
>> + tmp_row.name = intf_name
>> +
>> + def try_again(db_entity):
>> + db_entity.make_interface(intf_name)
>> +
>> + if not execute_transaction:
>> + return tmp_row
>> +
>> + txn.add_comment("ovs-tcpdump: user=%s,create_intf=%s"
>> + % (username(), intf_name))
>> + status = self._complete_txn(try_again)
>> + if status is False:
>> + raise OVSDBException('Unable to create Interface %s' %
>> intf_name)
>> + result = txn.get_insert_uuid(tmp_row.uuid)
>> + self._txn = None
>> + return result
>> +
>> + def destroy_port(self, port_name, bridge_name):
>> + if not self.interface_exists(port_name):
>> + return
>> + txn = self._start_txn()
>> + br = self._find_row_by_name('Bridge', bridge_name)
>> + ports = [port for port in br.ports if port.name != port_name]
>> + br.ports = ports
>> +
>> + def try_again(db_entity):
>> + db_entity.destroy_port(port_name)
>> +
>> + txn.add_comment("ovs-tcpdump: user=%s,destroy_port=%s"
>> + % (username(), port_name))
>> + status = self._complete_txn(try_again)
>> + if status is False:
>> + raise OVSDBException('unable to delete Port %s' % port_name)
>> + self._txn = None
>> +
>> + def destroy_mirror(self, mirror_name, bridge_name):
>> + if not self.mirror_exists(mirror_name):
>> + return
>> + txn = self._start_txn()
>> + mirror_row = self._find_row_by_name('Mirror', mirror_name)
>> + br = self._find_row_by_name('Bridge', bridge_name)
>> + mirrors = [mirror for mirror in br.mirrors
>> + if mirror.uuid != mirror_row.uuid]
>> + br.mirrors = mirrors
>> +
>> + def try_again(db_entity):
>> + db_entity.destroy_mirror(mirror_name, bridge_name)
>> +
>> + txn.add_comment("ovs-tcpdump: user=%s,destroy_mirror=%s"
>> + % (username(), mirror_name))
>> + status = self._complete_txn(try_again)
>> + if status is False:
>> + print "NO: %s" % txn.get_error()
>> + raise OVSDBException('Unable to delete Mirror %s' % mirror_name)
>> + self._txn = None
>> +
>> + def make_port(self, port_name, bridge_name):
>> + iface_row = self.make_interface(port_name, False)
>> + txn = self._txn
>> +
>> + br = self._find_row_by_name('Bridge', bridge_name)
>> + if not br:
>> + raise OVSDBException('Bad bridge name %s' % bridge_name)
>> +
>> + port = txn.insert(self.get_table('Port'))
>> + port.name = port_name
>> +
>> + br.verify('ports')
>> + ports = getattr(br, 'ports', [])
>> + ports.append(port)
>> + br.ports = ports
>> +
>> + port.verify('interfaces')
>> + ifaces = getattr(port, 'interfaces', [])
>> + ifaces.append(iface_row)
>> + port.interfaces = ifaces
>> +
>> + def try_again(db_entity):
>> + db_entity.make_port(port_name, bridge_name)
>> +
>> + txn.add_comment("ovs-tcpdump: user=%s,create_port=%s"
>> + % (username(), port_name))
>> + status = self._complete_txn(try_again)
>> + if status is False:
>> + raise OVSDBException('Unable to create Port %s: %s' %
>> + (port_name, txn.get_error()))
>> + result = txn.get_insert_uuid(port.uuid)
>> + self._txn = None
>> + return result
>> +
>> + def bridge_mirror(self, intf_name, mirror_intf_name, br_name):
>> +
>> + txn = self._start_txn()
>> + mirror = txn.insert(self.get_table('Mirror'))
>> + mirror.name = 'm_%s' % intf_name
>> +
>> + mirror.select_all = False
>> +
>> + mirrored_port = self._find_row_by_name('Port', intf_name)
>> +
>> + mirror.verify('select_dst_port')
>> + dst_port = getattr(mirror, 'select_dst_port', [])
>> + dst_port.append(mirrored_port)
>> + mirror.select_dst_port = dst_port
>> +
>> + mirror.verify('select_src_port')
>> + src_port = getattr(mirror, 'select_src_port', [])
>> + src_port.append(mirrored_port)
>> + mirror.select_src_port = src_port
>> +
>> + output_port = self._find_row_by_name('Port', mirror_intf_name)
>> +
>> + mirror.verify('output_port')
>> + out_port = getattr(mirror, 'output_port', [])
>> + out_port.append(output_port.uuid)
>> + mirror.output_port = out_port
>> +
>> + br = self._find_row_by_name('Bridge', br_name)
>> + br.verify('mirrors')
>> + mirrors = getattr(br, 'mirrors', [])
>> + mirrors.append(mirror.uuid)
>> + br.mirrors = mirrors
>> +
>> + def try_again(db_entity):
>> + db_entity.bridge_mirror(intf_name, mirror_intf_name, br_name)
>> +
>> + txn.add_comment("ovs-tcpdump: user=%s,create_mirror=%s"
>> + % (username(), mirror.name))
>> + status = self._complete_txn(try_again)
>> + if status is False:
>> + print "NO: %s" % txn.get_error()
>> + raise OVSDBException('Unable to create Mirror %s: %s' %
>> + (mirror_intf_name, txn.get_error()))
>> + result = txn.get_insert_uuid(mirror.uuid)
>> + self._txn = None
>> + return result
>> +
>> +
>> +def argv_tuples(lst):
>> + cur, nxt = iter(lst), iter(lst)
>> + next(nxt, None)
>> +
>> + try:
>> + while True:
>> + yield next(cur), next(nxt, None)
>> + except StopIteration:
>> + pass
>> +
>> +
>> +def main():
>> + db_sock = 'unix:@RUNDIR@/openvswitch/db.sock'
>> + interface = None
>> + tcpdargs = []
>> +
>> + skip_next = False
>> + for cur, nxt in argv_tuples(sys.argv[1:]):
>> + if skip_next:
>> + skip_next = False
>> + continue
>> +
>> + if cur in ['-h', '--help']:
>> + usage()
>> + elif cur in ['-V', '--version']:
>> + print "ovs-tcpdump (Open vSwitch) @VERSION@"
>> + sys.exit(0)
>> + elif cur in ['--mirror-to']:
>> + mirror_interface = nxt
>> + skip_next = True
>> + elif cur in ['--db-sock']:
>> + db_sock = nxt
>> + skip_next = True
>> + continue
>> + elif cur in ['-i']:
>> + interface = nxt
>> + skip_next = True
>> + continue
>> + tcpdargs.append(cur)
>> +
>> + if interface is None:
>> + print "Error: must at least specify an interface with '-i' option"
>> + sys.exit(1)
>> +
>> + if '-l' not in tcpdargs:
>> + tcpdargs.insert(0, '-l')
>> +
>> + print "TCPDUMP Args: %s" % ' '.join(tcpdargs)
>> +
>> + ovsdb = OVSDB(db_sock)
>> + if mirror_interface is None:
>> + mirror_interface = "mi_%s" % interface
>> + if mirror_interface not in netifaces.interfaces():
>> + print "ERROR: Please create a tap interface called `%s`" % \
>> + mirror_interface
>> + print "See your OS guide for how to do this."
>> + print "Ex: ip tuntap add dev %s mode tap" % mirror_interface
>> + sys.exit(1)
>> +
>> + if not ovsdb.port_exists(interface):
>> + print "ERROR: Port %s does not exist." % interface
>> + sys.exit(1)
>> + if ovsdb.port_exists(mirror_interface):
>> + print "ERROR: Mirror port (%s) exists for port %s." % \
>> + (mirror_interface, interface)
>> + sys.exit(1)
>> + try:
>> + ovsdb.make_port(mirror_interface, ovsdb.port_bridge(interface))
>> + ovsdb.bridge_mirror(interface, mirror_interface,
>> + ovsdb.port_bridge(interface))
>
> Hi Aaron,
>
> In the try: block above, is it possible for ovsdb.make_port() to succeed and
> ovsdb.bridge_mirror() to fail? If so, it seems that ovsdb.destroy_port() should
> be called before exiting in order to properly clean things up.
d'oh! Thanks for this catch. And thanks so much for the review, Lance!
> Lance
>
>> + except OVSDBException as oe:
>> + print "ERROR: Unable to properly setup the mirror: %s." % str(oe)
>> + sys.exit(1)
>> +
>> + time.sleep(1)
>> + pipes = _doexec(*(['tcpdump', '-i', mirror_interface] + tcpdargs))
>> + try:
>> + while True:
>> + print pipes.stdout.readline()
>> + except KeyboardInterrupt:
>> + pipes.terminate()
>> + ovsdb.destroy_mirror('m_%s' % interface,
>> ovsdb.port_bridge(interface))
>> + ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
>> + except:
>> + print "Unable to tear down the create ports and mirrors."
>> + print "Please use ovs-vsctl to remove the ports and mirrors
>> created."
>> + sys.exit(1)
>> + sys.exit(0)
>> +
>> +
>> +if __name__ == '__main__':
>> + main()
>> +
>> +# Local variables:
>> +# mode: python
>> +# End:
>> --
>> 2.5.5
>>
>> _______________________________________________
>> dev mailing list
>> dev at openvswitch.org
>> http://openvswitch.org/mailman/listinfo/dev
>>
More information about the dev
mailing list