]>
Commit | Line | Data |
---|---|---|
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 | ||
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 | ||
6c7050b5 | 27 | import netifaces |
28 | ||
314ce647 AC |
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 = {} | |
b4027b17 AC |
43 | _make_mirror_name = {} |
44 | IFNAMSIZ_LINUX = 15 # this is the max name size, excluding the null byte. | |
314ce647 AC |
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 | ||
26cea6ad | 56 | def _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 |
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 | ||
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 | ||
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' | |
0475db71 | 114 | --span If specified, mirror all ports (optional) |
314ce647 AC |
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: | |
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 | ||
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 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 | ||
487 | if __name__ == '__main__': | |
488 | main() | |
489 | ||
490 | # Local variables: | |
491 | # mode: python | |
492 | # End: |