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