]> git.proxmox.com Git - mirror_ovs.git/blame - utilities/ovs-tcpdump.in
ovs-actions: New document describing OVS actions in detail.
[mirror_ovs.git] / utilities / ovs-tcpdump.in
CommitLineData
b49a959b 1#! @PYTHON@
314ce647
AC
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
17import fcntl
6c7050b5 18
314ce647
AC
19import os
20import pwd
b4027b17 21from random import randint
314ce647
AC
22import struct
23import subprocess
24import sys
25import time
26
6c7050b5 27import netifaces
28
314ce647
AC
29try:
30 from ovs.db import idl
31 from ovs import jsonrpc
32 from ovs.poller import Poller
33 from ovs.stream import Stream
34except 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
41tapdev_fd = None
42_make_taps = {}
b4027b17
AC
43_make_mirror_name = {}
44IFNAMSIZ_LINUX = 15 # this is the max name size, excluding the null byte.
314ce647
AC
45
46
47def _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
26cea6ad 56def _install_tap_linux(tap_name, mtu_value=None):
314ce647
AC
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
793bdb6c
TR
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)
314ce647
AC
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
26cea6ad
AC
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
314ce647
AC
77 pipe = _doexec(
78 *(['ip', 'link', 'set', 'dev', str(tap_name), 'up']))
79 pipe.wait()
80
81a86b9a 81
b4027b17
AC
82def _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
314ce647
AC
88_make_taps['linux'] = _install_tap_linux
89_make_taps['linux2'] = _install_tap_linux
b4027b17
AC
90_make_mirror_name['linux'] = _make_linux_mirror_name
91_make_mirror_name['linux2'] = _make_linux_mirror_name
314ce647
AC
92
93
94def username():
95 return pwd.getpwuid(os.getuid())[0]
96
97
98def usage():
99 print("""\
100%(prog)s: Open vSwitch tcpdump helper.
101usage: %(prog)s -i interface [TCPDUMP OPTIONS]
102where TCPDUMP OPTIONS represents the options normally passed to tcpdump.
103
104The 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'
0475db71 114 --span If specified, mirror all ports (optional)
314ce647
AC
115""" % {'prog': sys.argv[0]})
116 sys.exit(0)
117
118
119class OVSDBException(Exception):
120 pass
121
122
123class 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:
31235267 191 port = self._find_row_by_name('Port', port_name)
314ce647
AC
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
26cea6ad
AC
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
314ce647
AC
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
efeb3e44 260 def destroy_mirror(self, intf_name, bridge_name):
261 mirror_name = 'm_%s' % intf_name
314ce647
AC
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
0475db71
MF
316 def bridge_mirror(self, intf_name, mirror_intf_name, br_name,
317 mirror_select_all=False):
314ce647
AC
318
319 txn = self._start_txn()
320 mirror = txn.insert(self.get_table('Mirror'))
321 mirror.name = 'm_%s' % intf_name
322
0475db71 323 mirror.select_all = mirror_select_all
314ce647
AC
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
364def 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
375def main():
376 db_sock = 'unix:@RUNDIR@/db.sock'
377 interface = None
378 tcpdargs = []
379
380 skip_next = False
381 mirror_interface = None
382 dump_cmd = 'tcpdump'
383
384 for cur, nxt in argv_tuples(sys.argv[1:]):
385 if skip_next:
386 skip_next = False
387 continue
388 if cur in ['-h', '--help']:
389 usage()
390 elif cur in ['-V', '--version']:
391 print("ovs-tcpdump (Open vSwitch) @VERSION@")
392 sys.exit(0)
393 elif cur in ['--db-sock']:
394 db_sock = nxt
395 skip_next = True
396 continue
397 elif cur in ['--dump-cmd']:
398 dump_cmd = nxt
399 skip_next = True
400 continue
401 elif cur in ['-i', '--interface']:
402 interface = nxt
403 skip_next = True
404 continue
405 elif cur in ['--mirror-to']:
406 mirror_interface = nxt
407 skip_next = True
408 continue
0475db71
MF
409 elif cur in ['--span']:
410 mirror_select_all = True
411 continue
314ce647
AC
412 tcpdargs.append(cur)
413
414 if interface is None:
415 print("Error: must at least specify an interface with '-i' option")
416 sys.exit(1)
417
418 if '-l' not in tcpdargs:
419 tcpdargs.insert(0, '-l')
420
421 if '-vv' in tcpdargs:
422 print("TCPDUMP Args: %s" % ' '.join(tcpdargs))
423
424 ovsdb = OVSDB(db_sock)
b4027b17
AC
425 if mirror_interface is None:
426 mirror_interface = "mi%s" % interface
427 if sys.platform in _make_mirror_name:
428 mirror_interface = _make_mirror_name[sys.platform](interface)
314ce647
AC
429
430 if sys.platform in _make_taps and \
431 mirror_interface not in netifaces.interfaces():
26cea6ad
AC
432 _make_taps[sys.platform](mirror_interface,
433 ovsdb.interface_mtu(interface))
314ce647
AC
434
435 if mirror_interface not in netifaces.interfaces():
436 print("ERROR: Please create an interface called `%s`" %
437 mirror_interface)
438 print("See your OS guide for how to do this.")
439 print("Ex: ip link add %s type veth peer name %s" %
440 (mirror_interface, mirror_interface + "2"))
441 sys.exit(1)
442
443 if not ovsdb.port_exists(interface):
444 print("ERROR: Port %s does not exist." % interface)
445 sys.exit(1)
446 if ovsdb.port_exists(mirror_interface):
447 print("ERROR: Mirror port (%s) exists for port %s." %
448 (mirror_interface, interface))
449 sys.exit(1)
450 try:
451 ovsdb.make_port(mirror_interface, ovsdb.port_bridge(interface))
452 ovsdb.bridge_mirror(interface, mirror_interface,
0475db71
MF
453 ovsdb.port_bridge(interface),
454 mirror_select_all)
314ce647
AC
455 except OVSDBException as oe:
456 print("ERROR: Unable to properly setup the mirror: %s." % str(oe))
457 try:
458 ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
459 except Exception:
460 pass
461 sys.exit(1)
462
463 pipes = _doexec(*([dump_cmd, '-i', mirror_interface] + tcpdargs))
464 try:
52654c4a 465 while pipes.poll() is None:
793bdb6c 466 data = pipes.stdout.readline().strip(b'\n')
52654c4a
AC
467 if len(data) == 0:
468 raise KeyboardInterrupt
793bdb6c 469 print(data.decode('utf-8'))
52654c4a 470 raise KeyboardInterrupt
314ce647 471 except KeyboardInterrupt:
68b2a37d 472 if pipes.poll() is None:
473 pipes.terminate()
474
efeb3e44 475 ovsdb.destroy_mirror(interface, ovsdb.port_bridge(interface))
314ce647
AC
476 ovsdb.destroy_port(mirror_interface, ovsdb.port_bridge(interface))
477 except Exception:
478 print("Unable to tear down the ports and mirrors.")
479 print("Please use ovs-vsctl to remove the ports and mirrors created.")
480 print(" ex: ovs-vsctl --db=%s del-port %s" % (db_sock,
481 mirror_interface))
482 sys.exit(1)
483
484 sys.exit(0)
485
486
487if __name__ == '__main__':
488 main()
489
490# Local variables:
491# mode: python
492# End: