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