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