ba6ee9f65337a3018405b0d2488acc95726c43e7
[cascardo/ovs.git] / utilities / ovs-tcpdump.in
1 #! /usr/bin/env @PYTHON@
2 #
3 # Copyright (c) 2016 Red Hat, Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at:
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import fcntl
18 import netifaces
19 import os
20 import pwd
21 import select
22 import struct
23 import subprocess
24 import sys
25 import time
26
27 try:
28     from ovs.db import idl
29     from ovs import jsonrpc
30     from ovs.poller import Poller
31     from ovs.stream import Stream
32 except Exception:
33     print("ERROR: Please install the correct Open vSwitch python support")
34     print("       libraries (version @VERSION@).")
35     print("       Alternatively, check that your PYTHONPATH is pointing to")
36     print("       the correct location.")
37     sys.exit(1)
38
39 tapdev_fd = None
40 _make_taps = {}
41
42
43 def _doexec(*args, **kwargs):
44     """Executes an application and returns a set of pipes"""
45
46     shell = len(args) == 1
47     proc = subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell,
48                             bufsize=0)
49     return proc
50
51
52 def _install_tap_linux(tap_name):
53     """Uses /dev/net/tun to create a tap device"""
54     global tapdev_fd
55
56     IFF_TAP = 0x0002
57     IFF_NO_PI = 0x1000
58     TUNSETIFF = 0x400454CA  # This is derived by printf() of TUNSETIFF
59     TUNSETOWNER = TUNSETIFF + 2
60
61     tapdev_fd = open('/dev/net/tun', 'rw')
62     ifr = struct.pack('16sH', tap_name, IFF_TAP | IFF_NO_PI)
63     fcntl.ioctl(tapdev_fd, TUNSETIFF, ifr)
64     fcntl.ioctl(tapdev_fd, TUNSETOWNER, os.getegid())
65
66     time.sleep(1)  # required to give the new device settling time
67     pipe = _doexec(
68         *(['ip', 'link', 'set', 'dev', str(tap_name), 'up']))
69     pipe.wait()
70
71 _make_taps['linux'] = _install_tap_linux
72 _make_taps['linux2'] = _install_tap_linux
73
74
75 def username():
76     return pwd.getpwuid(os.getuid())[0]
77
78
79 def usage():
80     print("""\
81 %(prog)s: Open vSwitch tcpdump helper.
82 usage: %(prog)s -i interface [TCPDUMP OPTIONS]
83 where TCPDUMP OPTIONS represents the options normally passed to tcpdump.
84
85 The following options are available:
86    -h, --help                 display this help message
87    -V, --version              display version information
88    --db-sock                  A connection string to reach the Open vSwitch
89                               ovsdb-server.
90                               Default 'unix:@RUNDIR@/db.sock'
91    --dump-cmd                 Command to use for tcpdump (default 'tcpdump')
92    -i, --interface            Open vSwitch interface to mirror and tcpdump
93    --mirror-to                The name for the mirror port to use (optional)
94                               Default 'miINTERFACE'
95 """ % {'prog': sys.argv[0]})
96     sys.exit(0)
97
98
99 class OVSDBException(Exception):
100     pass
101
102
103 class OVSDB(object):
104     @staticmethod
105     def wait_for_db_change(idl):
106         seq = idl.change_seqno
107         stop = time.time() + 10
108         while idl.change_seqno == seq and not idl.run():
109             poller = Poller()
110             idl.wait(poller)
111             poller.block()
112             if time.time() >= stop:
113                 raise Exception('Retry Timeout')
114
115     def __init__(self, db_sock):
116         self._db_sock = db_sock
117         self._txn = None
118         schema = self._get_schema()
119         schema.register_all()
120         self._idl_conn = idl.Idl(db_sock, schema)
121         OVSDB.wait_for_db_change(self._idl_conn)  # Initial Sync with DB
122
123     def _get_schema(self):
124         error, strm = Stream.open_block(Stream.open(self._db_sock))
125         if error:
126             raise Exception("Unable to connect to %s" % self._db_sock)
127         rpc = jsonrpc.Connection(strm)
128         req = jsonrpc.Message.create_request('get_schema', ['Open_vSwitch'])
129         error, resp = rpc.transact_block(req)
130         rpc.close()
131
132         if error or resp.error:
133             raise Exception('Unable to retrieve schema.')
134         return idl.SchemaHelper(None, resp.result)
135
136     def get_table(self, table_name):
137         return self._idl_conn.tables[table_name]
138
139     def _start_txn(self):
140         if self._txn is not None:
141             raise OVSDBException("ERROR: A transaction was started already")
142         self._idl_conn.change_seqno += 1
143         self._txn = idl.Transaction(self._idl_conn)
144         return self._txn
145
146     def _complete_txn(self, try_again_fn):
147         if self._txn is None:
148             raise OVSDBException("ERROR: Not in a transaction")
149         status = self._txn.commit_block()
150         if status is idl.Transaction.TRY_AGAIN:
151             if self._idl_conn._session.rpc.status != 0:
152                 self._idl_conn.force_reconnect()
153                 OVSDB.wait_for_db_change(self._idl_conn)
154             return try_again_fn(self)
155         elif status is idl.Transaction.ERROR:
156             return False
157
158     def _find_row(self, table_name, find):
159         return next(
160             (row for row in self.get_table(table_name).rows.values()
161              if find(row)), None)
162
163     def _find_row_by_name(self, table_name, value):
164         return self._find_row(table_name, lambda row: row.name == value)
165
166     def port_exists(self, port_name):
167         return bool(self._find_row_by_name('Port', port_name))
168
169     def port_bridge(self, port_name):
170         try:
171             row = self._find_row_by_name('Interface', port_name)
172             port = self._find_row('Port', lambda x: row in x.interfaces)
173             br = self._find_row('Bridge', lambda x: port in x.ports)
174             return br.name
175         except Exception:
176             raise OVSDBException('Unable to find port %s bridge' % port_name)
177
178     def interface_exists(self, intf_name):
179         return bool(self._find_row_by_name('Interface', intf_name))
180
181     def mirror_exists(self, mirror_name):
182         return bool(self._find_row_by_name('Mirror', mirror_name))
183
184     def interface_uuid(self, intf_name):
185         row = self._find_row_by_name('Interface', intf_name)
186         if bool(row):
187             return row.uuid
188         raise OVSDBException('No such interface: %s' % intf_name)
189
190     def make_interface(self, intf_name, execute_transaction=True):
191         if self.interface_exists(intf_name):
192             print("INFO: Interface exists.")
193             return self.interface_uuid(intf_name)
194
195         txn = self._start_txn()
196         tmp_row = txn.insert(self.get_table('Interface'))
197         tmp_row.name = intf_name
198
199         def try_again(db_entity):
200             db_entity.make_interface(intf_name)
201
202         if not execute_transaction:
203             return tmp_row
204
205         txn.add_comment("ovs-tcpdump: user=%s,create_intf=%s"
206                         % (username(), intf_name))
207         status = self._complete_txn(try_again)
208         if status is False:
209             raise OVSDBException('Unable to create Interface %s: %s' %
210                                  (intf_name, txn.get_error()))
211         result = txn.get_insert_uuid(tmp_row.uuid)
212         self._txn = None
213         return result
214
215     def destroy_port(self, port_name, bridge_name):
216         if not self.interface_exists(port_name):
217             return
218         txn = self._start_txn()
219         br = self._find_row_by_name('Bridge', bridge_name)
220         ports = [port for port in br.ports if port.name != port_name]
221         br.ports = ports
222
223         def try_again(db_entity):
224             db_entity.destroy_port(port_name)
225
226         txn.add_comment("ovs-tcpdump: user=%s,destroy_port=%s"
227                         % (username(), port_name))
228         status = self._complete_txn(try_again)
229         if status is False:
230             raise OVSDBException('unable to delete Port %s: %s' %
231                                  (port_name, txn.get_error()))
232         self._txn = None
233
234     def destroy_mirror(self, mirror_name, bridge_name):
235         if not self.mirror_exists(mirror_name):
236             return
237         txn = self._start_txn()
238         mirror_row = self._find_row_by_name('Mirror', mirror_name)
239         br = self._find_row_by_name('Bridge', bridge_name)
240         mirrors = [mirror for mirror in br.mirrors
241                    if mirror.uuid != mirror_row.uuid]
242         br.mirrors = mirrors
243
244         def try_again(db_entity):
245             db_entity.destroy_mirror(mirror_name, bridge_name)
246
247         txn.add_comment("ovs-tcpdump: user=%s,destroy_mirror=%s"
248                         % (username(), mirror_name))
249         status = self._complete_txn(try_again)
250         if status is False:
251             raise OVSDBException('Unable to delete Mirror %s: %s' %
252                                  (mirror_name, txn.get_error()))
253         self._txn = None
254
255     def make_port(self, port_name, bridge_name):
256         iface_row = self.make_interface(port_name, False)
257         txn = self._txn
258
259         br = self._find_row_by_name('Bridge', bridge_name)
260         if not br:
261             raise OVSDBException('Bad bridge name %s' % bridge_name)
262
263         port = txn.insert(self.get_table('Port'))
264         port.name = port_name
265
266         br.verify('ports')
267         ports = getattr(br, 'ports', [])
268         ports.append(port)
269         br.ports = ports
270
271         port.verify('interfaces')
272         ifaces = getattr(port, 'interfaces', [])
273         ifaces.append(iface_row)
274         port.interfaces = ifaces
275
276         def try_again(db_entity):
277             db_entity.make_port(port_name, bridge_name)
278
279         txn.add_comment("ovs-tcpdump: user=%s,create_port=%s"
280                         % (username(), port_name))
281         status = self._complete_txn(try_again)
282         if status is False:
283             raise OVSDBException('Unable to create Port %s: %s' %
284                                  (port_name, txn.get_error()))
285         result = txn.get_insert_uuid(port.uuid)
286         self._txn = None
287         return result
288
289     def bridge_mirror(self, intf_name, mirror_intf_name, br_name):
290
291         txn = self._start_txn()
292         mirror = txn.insert(self.get_table('Mirror'))
293         mirror.name = 'm_%s' % intf_name
294
295         mirror.select_all = False
296
297         mirrored_port = self._find_row_by_name('Port', intf_name)
298
299         mirror.verify('select_dst_port')
300         dst_port = getattr(mirror, 'select_dst_port', [])
301         dst_port.append(mirrored_port)
302         mirror.select_dst_port = dst_port
303
304         mirror.verify('select_src_port')
305         src_port = getattr(mirror, 'select_src_port', [])
306         src_port.append(mirrored_port)
307         mirror.select_src_port = src_port
308
309         output_port = self._find_row_by_name('Port', mirror_intf_name)
310
311         mirror.verify('output_port')
312         out_port = getattr(mirror, 'output_port', [])
313         out_port.append(output_port.uuid)
314         mirror.output_port = out_port
315
316         br = self._find_row_by_name('Bridge', br_name)
317         br.verify('mirrors')
318         mirrors = getattr(br, 'mirrors', [])
319         mirrors.append(mirror.uuid)
320         br.mirrors = mirrors
321
322         def try_again(db_entity):
323             db_entity.bridge_mirror(intf_name, mirror_intf_name, br_name)
324
325         txn.add_comment("ovs-tcpdump: user=%s,create_mirror=%s"
326                         % (username(), mirror.name))
327         status = self._complete_txn(try_again)
328         if status is False:
329             raise OVSDBException('Unable to create Mirror %s: %s' %
330                                  (mirror_intf_name, txn.get_error()))
331         result = txn.get_insert_uuid(mirror.uuid)
332         self._txn = None
333         return result
334
335
336 def argv_tuples(lst):
337     cur, nxt = iter(lst), iter(lst)
338     next(nxt, None)
339
340     try:
341         while True:
342             yield next(cur), next(nxt, None)
343     except StopIteration:
344         pass
345
346
347 def main():
348     db_sock = 'unix:@RUNDIR@/db.sock'
349     interface = None
350     tcpdargs = []
351
352     skip_next = False
353     mirror_interface = None
354     dump_cmd = 'tcpdump'
355
356     for cur, nxt in argv_tuples(sys.argv[1:]):
357         if skip_next:
358             skip_next = False
359             continue
360         if cur in ['-h', '--help']:
361             usage()
362         elif cur in ['-V', '--version']:
363             print("ovs-tcpdump (Open vSwitch) @VERSION@")
364             sys.exit(0)
365         elif cur in ['--db-sock']:
366             db_sock = nxt
367             skip_next = True
368             continue
369         elif cur in ['--dump-cmd']:
370             dump_cmd = nxt
371             skip_next = True
372             continue
373         elif cur in ['-i', '--interface']:
374             interface = nxt
375             skip_next = True
376             continue
377         elif cur in ['--mirror-to']:
378             mirror_interface = nxt
379             skip_next = True
380             continue
381         tcpdargs.append(cur)
382
383     if interface is None:
384         print("Error: must at least specify an interface with '-i' option")
385         sys.exit(1)
386
387     if '-l' not in tcpdargs:
388         tcpdargs.insert(0, '-l')
389
390     if '-vv' in tcpdargs:
391         print("TCPDUMP Args: %s" % ' '.join(tcpdargs))
392
393     ovsdb = OVSDB(db_sock)
394     mirror_interface = mirror_interface or "mi%s" % interface
395
396     if sys.platform in _make_taps and \
397        mirror_interface not in netifaces.interfaces():
398         _make_taps[sys.platform](mirror_interface)
399
400     if mirror_interface not in netifaces.interfaces():
401         print("ERROR: Please create an interface called `%s`" %
402               mirror_interface)
403         print("See your OS guide for how to do this.")
404         print("Ex: ip link add %s type veth peer name %s" %
405               (mirror_interface, mirror_interface + "2"))
406         sys.exit(1)
407
408     if not ovsdb.port_exists(interface):
409         print("ERROR: Port %s does not exist." % interface)
410         sys.exit(1)
411     if ovsdb.port_exists(mirror_interface):
412         print("ERROR: Mirror port (%s) exists for port %s." %
413               (mirror_interface, interface))
414         sys.exit(1)
415     try:
416         ovsdb.make_port(mirror_interface, ovsdb.port_bridge(interface))
417         ovsdb.bridge_mirror(interface, mirror_interface,
418                             ovsdb.port_bridge(interface))
419     except OVSDBException as oe:
420         print("ERROR: Unable to properly setup the mirror: %s." % str(oe))
421         try:
422             ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
423         except Exception:
424             pass
425         sys.exit(1)
426
427     pipes = _doexec(*([dump_cmd, '-i', mirror_interface] + tcpdargs))
428     try:
429         while True:
430             print(pipes.stdout.readline())
431             if select.select([sys.stdin], [], [], 0.0)[0]:
432                 data_in = sys.stdin.read()
433                 pipes.stdin.write(data_in)
434     except KeyboardInterrupt:
435         pipes.terminate()
436         ovsdb.destroy_mirror('m%s' % interface, ovsdb.port_bridge(interface))
437         ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
438     except Exception:
439         print("Unable to tear down the ports and mirrors.")
440         print("Please use ovs-vsctl to remove the ports and mirrors created.")
441         print(" ex: ovs-vsctl --db=%s del-port %s" % (db_sock,
442                                                       mirror_interface))
443         sys.exit(1)
444
445     sys.exit(0)
446
447
448 if __name__ == '__main__':
449     main()
450
451 # Local variables:
452 # mode: python
453 # End: