ovn-northd.8.xml: Remove outdated flow.
[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             port = self._find_row_by_name('Port', port_name)
172             br = self._find_row('Bridge', lambda x: port in x.ports)
173             return br.name
174         except Exception:
175             raise OVSDBException('Unable to find port %s bridge' % port_name)
176
177     def interface_exists(self, intf_name):
178         return bool(self._find_row_by_name('Interface', intf_name))
179
180     def mirror_exists(self, mirror_name):
181         return bool(self._find_row_by_name('Mirror', mirror_name))
182
183     def interface_uuid(self, intf_name):
184         row = self._find_row_by_name('Interface', intf_name)
185         if bool(row):
186             return row.uuid
187         raise OVSDBException('No such interface: %s' % intf_name)
188
189     def make_interface(self, intf_name, execute_transaction=True):
190         if self.interface_exists(intf_name):
191             print("INFO: Interface exists.")
192             return self.interface_uuid(intf_name)
193
194         txn = self._start_txn()
195         tmp_row = txn.insert(self.get_table('Interface'))
196         tmp_row.name = intf_name
197
198         def try_again(db_entity):
199             db_entity.make_interface(intf_name)
200
201         if not execute_transaction:
202             return tmp_row
203
204         txn.add_comment("ovs-tcpdump: user=%s,create_intf=%s"
205                         % (username(), intf_name))
206         status = self._complete_txn(try_again)
207         if status is False:
208             raise OVSDBException('Unable to create Interface %s: %s' %
209                                  (intf_name, txn.get_error()))
210         result = txn.get_insert_uuid(tmp_row.uuid)
211         self._txn = None
212         return result
213
214     def destroy_port(self, port_name, bridge_name):
215         if not self.interface_exists(port_name):
216             return
217         txn = self._start_txn()
218         br = self._find_row_by_name('Bridge', bridge_name)
219         ports = [port for port in br.ports if port.name != port_name]
220         br.ports = ports
221
222         def try_again(db_entity):
223             db_entity.destroy_port(port_name)
224
225         txn.add_comment("ovs-tcpdump: user=%s,destroy_port=%s"
226                         % (username(), port_name))
227         status = self._complete_txn(try_again)
228         if status is False:
229             raise OVSDBException('unable to delete Port %s: %s' %
230                                  (port_name, txn.get_error()))
231         self._txn = None
232
233     def destroy_mirror(self, mirror_name, bridge_name):
234         if not self.mirror_exists(mirror_name):
235             return
236         txn = self._start_txn()
237         mirror_row = self._find_row_by_name('Mirror', mirror_name)
238         br = self._find_row_by_name('Bridge', bridge_name)
239         mirrors = [mirror for mirror in br.mirrors
240                    if mirror.uuid != mirror_row.uuid]
241         br.mirrors = mirrors
242
243         def try_again(db_entity):
244             db_entity.destroy_mirror(mirror_name, bridge_name)
245
246         txn.add_comment("ovs-tcpdump: user=%s,destroy_mirror=%s"
247                         % (username(), mirror_name))
248         status = self._complete_txn(try_again)
249         if status is False:
250             raise OVSDBException('Unable to delete Mirror %s: %s' %
251                                  (mirror_name, txn.get_error()))
252         self._txn = None
253
254     def make_port(self, port_name, bridge_name):
255         iface_row = self.make_interface(port_name, False)
256         txn = self._txn
257
258         br = self._find_row_by_name('Bridge', bridge_name)
259         if not br:
260             raise OVSDBException('Bad bridge name %s' % bridge_name)
261
262         port = txn.insert(self.get_table('Port'))
263         port.name = port_name
264
265         br.verify('ports')
266         ports = getattr(br, 'ports', [])
267         ports.append(port)
268         br.ports = ports
269
270         port.verify('interfaces')
271         ifaces = getattr(port, 'interfaces', [])
272         ifaces.append(iface_row)
273         port.interfaces = ifaces
274
275         def try_again(db_entity):
276             db_entity.make_port(port_name, bridge_name)
277
278         txn.add_comment("ovs-tcpdump: user=%s,create_port=%s"
279                         % (username(), port_name))
280         status = self._complete_txn(try_again)
281         if status is False:
282             raise OVSDBException('Unable to create Port %s: %s' %
283                                  (port_name, txn.get_error()))
284         result = txn.get_insert_uuid(port.uuid)
285         self._txn = None
286         return result
287
288     def bridge_mirror(self, intf_name, mirror_intf_name, br_name):
289
290         txn = self._start_txn()
291         mirror = txn.insert(self.get_table('Mirror'))
292         mirror.name = 'm_%s' % intf_name
293
294         mirror.select_all = False
295
296         mirrored_port = self._find_row_by_name('Port', intf_name)
297
298         mirror.verify('select_dst_port')
299         dst_port = getattr(mirror, 'select_dst_port', [])
300         dst_port.append(mirrored_port)
301         mirror.select_dst_port = dst_port
302
303         mirror.verify('select_src_port')
304         src_port = getattr(mirror, 'select_src_port', [])
305         src_port.append(mirrored_port)
306         mirror.select_src_port = src_port
307
308         output_port = self._find_row_by_name('Port', mirror_intf_name)
309
310         mirror.verify('output_port')
311         out_port = getattr(mirror, 'output_port', [])
312         out_port.append(output_port.uuid)
313         mirror.output_port = out_port
314
315         br = self._find_row_by_name('Bridge', br_name)
316         br.verify('mirrors')
317         mirrors = getattr(br, 'mirrors', [])
318         mirrors.append(mirror.uuid)
319         br.mirrors = mirrors
320
321         def try_again(db_entity):
322             db_entity.bridge_mirror(intf_name, mirror_intf_name, br_name)
323
324         txn.add_comment("ovs-tcpdump: user=%s,create_mirror=%s"
325                         % (username(), mirror.name))
326         status = self._complete_txn(try_again)
327         if status is False:
328             raise OVSDBException('Unable to create Mirror %s: %s' %
329                                  (mirror_intf_name, txn.get_error()))
330         result = txn.get_insert_uuid(mirror.uuid)
331         self._txn = None
332         return result
333
334
335 def argv_tuples(lst):
336     cur, nxt = iter(lst), iter(lst)
337     next(nxt, None)
338
339     try:
340         while True:
341             yield next(cur), next(nxt, None)
342     except StopIteration:
343         pass
344
345
346 def main():
347     db_sock = 'unix:@RUNDIR@/db.sock'
348     interface = None
349     tcpdargs = []
350
351     skip_next = False
352     mirror_interface = None
353     dump_cmd = 'tcpdump'
354
355     for cur, nxt in argv_tuples(sys.argv[1:]):
356         if skip_next:
357             skip_next = False
358             continue
359         if cur in ['-h', '--help']:
360             usage()
361         elif cur in ['-V', '--version']:
362             print("ovs-tcpdump (Open vSwitch) @VERSION@")
363             sys.exit(0)
364         elif cur in ['--db-sock']:
365             db_sock = nxt
366             skip_next = True
367             continue
368         elif cur in ['--dump-cmd']:
369             dump_cmd = nxt
370             skip_next = True
371             continue
372         elif cur in ['-i', '--interface']:
373             interface = nxt
374             skip_next = True
375             continue
376         elif cur in ['--mirror-to']:
377             mirror_interface = nxt
378             skip_next = True
379             continue
380         tcpdargs.append(cur)
381
382     if interface is None:
383         print("Error: must at least specify an interface with '-i' option")
384         sys.exit(1)
385
386     if '-l' not in tcpdargs:
387         tcpdargs.insert(0, '-l')
388
389     if '-vv' in tcpdargs:
390         print("TCPDUMP Args: %s" % ' '.join(tcpdargs))
391
392     ovsdb = OVSDB(db_sock)
393     mirror_interface = mirror_interface or "mi%s" % interface
394
395     if sys.platform in _make_taps and \
396        mirror_interface not in netifaces.interfaces():
397         _make_taps[sys.platform](mirror_interface)
398
399     if mirror_interface not in netifaces.interfaces():
400         print("ERROR: Please create an interface called `%s`" %
401               mirror_interface)
402         print("See your OS guide for how to do this.")
403         print("Ex: ip link add %s type veth peer name %s" %
404               (mirror_interface, mirror_interface + "2"))
405         sys.exit(1)
406
407     if not ovsdb.port_exists(interface):
408         print("ERROR: Port %s does not exist." % interface)
409         sys.exit(1)
410     if ovsdb.port_exists(mirror_interface):
411         print("ERROR: Mirror port (%s) exists for port %s." %
412               (mirror_interface, interface))
413         sys.exit(1)
414     try:
415         ovsdb.make_port(mirror_interface, ovsdb.port_bridge(interface))
416         ovsdb.bridge_mirror(interface, mirror_interface,
417                             ovsdb.port_bridge(interface))
418     except OVSDBException as oe:
419         print("ERROR: Unable to properly setup the mirror: %s." % str(oe))
420         try:
421             ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
422         except Exception:
423             pass
424         sys.exit(1)
425
426     pipes = _doexec(*([dump_cmd, '-i', mirror_interface] + tcpdargs))
427     try:
428         while pipes.poll() is None:
429             data = pipes.stdout.readline()
430             if len(data) == 0:
431                 raise KeyboardInterrupt
432             print(data)
433             if select.select([sys.stdin], [], [], 0.0)[0]:
434                 data_in = sys.stdin.read()
435                 pipes.stdin.write(data_in)
436         raise KeyboardInterrupt
437     except KeyboardInterrupt:
438         pipes.terminate()
439         ovsdb.destroy_mirror('m%s' % interface, ovsdb.port_bridge(interface))
440         ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
441     except Exception:
442         print("Unable to tear down the ports and mirrors.")
443         print("Please use ovs-vsctl to remove the ports and mirrors created.")
444         print(" ex: ovs-vsctl --db=%s del-port %s" % (db_sock,
445                                                       mirror_interface))
446         sys.exit(1)
447
448     sys.exit(0)
449
450
451 if __name__ == '__main__':
452     main()
453
454 # Local variables:
455 # mode: python
456 # End: