[ovs-dev] [RFC] ovs-tcpdump: Add a tcpdump wrapper utility
Aaron Conole
aconole at redhat.com
Tue May 24 20:35:29 UTC 2016
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))
+ 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
More information about the dev
mailing list