|
18 | 18 | from ..data import get_box, get_empty_box, types |
19 | 19 | from ..utils import files as _files |
20 | 20 | from ..utils import linuxbridge, log, strings |
21 | | -from . import _Provider, get_provider_forwarded_ports, node_add_forwarded_ports, validate_mgmt_ip |
| 21 | +from . import _Provider, get_provider_forwarded_ports, node_add_forwarded_ports, tc_netem_set, validate_mgmt_ip |
22 | 22 |
|
23 | 23 | LIBVIRT_MANAGEMENT_NETWORK_NAME = "vagrant-libvirt" |
24 | 24 | LIBVIRT_MANAGEMENT_BRIDGE_NAME = "libvirt-mgmt" |
@@ -284,8 +284,22 @@ def pre_transform(self, topology: Box) -> None: |
284 | 284 | if not 'public' in l.libvirt: # ... but no 'public' libvirt attr |
285 | 285 | l.libvirt.public = 'bridge' # ... default mode is bridge (MACVTAP) |
286 | 286 |
|
| 287 | + """ |
| 288 | + The libvirt links could be modeled as P2P links (using UDP tunnels) or |
| 289 | + LAN links using a Linux bridge. It's better to use the UDP tunnels, but |
| 290 | + we must us the Linux bridge if: |
| 291 | +
|
| 292 | + * The link type is 'lan' or 'stub' (set/used elsewhere, also includes |
| 293 | + hosts connected to links) |
| 294 | + * The libvirt.provider attribute is set (multi-provider links or external |
| 295 | + connectivity) |
| 296 | + * The system defaults say P2P links should be modeled as bridges |
| 297 | + (used for traffic capture) |
| 298 | + * The link or any of the interfaces has the 'tc' parameter |
| 299 | + """ |
287 | 300 | must_be_lan = l.get('libvirt.provider',None) and 'vlan' not in l.type |
288 | 301 | must_be_lan = must_be_lan or (p2p_bridge and l.get('type','p2p') == 'p2p') |
| 302 | + must_be_lan = must_be_lan or 'tc' in l or [ intf for intf in l.interfaces if 'tc' in intf ] |
289 | 303 | if must_be_lan: |
290 | 304 | l.type = 'lan' |
291 | 305 | if not 'bridge' in l: |
@@ -482,37 +496,76 @@ def validate_node_image(self, node: Box, topology: Box) -> None: |
482 | 496 | f"'vagrant box add <url>' command to add it, or use this recipe to build it:", |
483 | 497 | dp_data.build ]) |
484 | 498 |
|
485 | | - def capture_command(self, node: Box, topology: Box, args: argparse.Namespace) -> typing.Optional[list]: |
486 | | - intf = [ intf for intf in node.interfaces if intf.ifname == args.intf ][0] |
487 | | - if intf.get('libvirt.type',None) == 'tunnel': |
| 499 | + def get_linux_intf( |
| 500 | + self, |
| 501 | + node: Box, |
| 502 | + topology: Box, |
| 503 | + ifname: str, |
| 504 | + op: str, |
| 505 | + hint: str, |
| 506 | + exit_on_error: bool = True) -> typing.Optional[str]: |
| 507 | + |
| 508 | + intf = [ intf for intf in node.interfaces if intf.ifname == ifname ][0] |
| 509 | + if intf.get('libvirt.type',None) == 'tunnel' or 'bridge' not in intf: |
488 | 510 | log.error( |
489 | | - f'Cannot perform packet capture on libvirt point-to-point links', |
| 511 | + f'Cannot perform {op} on libvirt point-to-point links', |
490 | 512 | category=log.FatalError, |
491 | 513 | module='libvirt', |
492 | 514 | skip_header=True, |
493 | | - exit_on_error=True, |
494 | | - hint='capture') |
| 515 | + exit_on_error=exit_on_error, |
| 516 | + hint=hint) |
| 517 | + return None |
495 | 518 |
|
496 | 519 | domiflist = external_commands.run_command( |
497 | 520 | ['virsh','domiflist',f'{topology.name}_{node.name}'], |
498 | 521 | check_result=True, |
499 | 522 | return_stdout=True) |
500 | 523 | if not isinstance(domiflist,str): |
| 524 | + log.error( |
| 525 | + f'Cannot get the list of libvirt interface for node {node.name}', |
| 526 | + category=log.FatalError, |
| 527 | + module='libvirt', |
| 528 | + skip_header=True, |
| 529 | + exit_on_error=exit_on_error) |
501 | 530 | return None |
502 | 531 |
|
503 | 532 | for intf_line in domiflist.split('\n'): |
504 | 533 | intf_data = strings.string_to_list(intf_line) |
505 | 534 | if len(intf_data) != 5: |
506 | 535 | continue |
507 | 536 | if intf_data[2] == intf.bridge: |
508 | | - cmd = strings.string_to_list(topology.defaults.netlab.capture.command) |
509 | | - cmd = strings.eval_format_list(cmd,{'intf': intf_data[0]}) |
510 | | - return ['sudo'] + cmd |
511 | | - |
| 537 | + return intf_data[0] |
| 538 | + |
512 | 539 | log.error( |
513 | 540 | f'Cannot find the interface on node {node.name} attached to libvirt network {intf.bridge}', |
514 | 541 | category=log.FatalError, |
515 | 542 | module='libvirt', |
516 | 543 | skip_header=True, |
517 | | - exit_on_error=True) |
| 544 | + exit_on_error=exit_on_error) |
518 | 545 | return None |
| 546 | + |
| 547 | + def capture_command(self, node: Box, topology: Box, args: argparse.Namespace) -> typing.Optional[list]: |
| 548 | + ifname = self.get_linux_intf(node,topology,args.intf,op='packet capture',hint='capture') |
| 549 | + if not ifname: |
| 550 | + return None |
| 551 | + |
| 552 | + cmd = strings.string_to_list(topology.defaults.netlab.capture.command) |
| 553 | + cmd = strings.eval_format_list(cmd,{'intf': ifname}) |
| 554 | + return ['sudo'] + cmd |
| 555 | + |
| 556 | + def set_tc(self, node: Box, topology: Box, intf: Box) -> None: |
| 557 | + vm_intf = self.get_linux_intf( |
| 558 | + node,topology,ifname=intf.ifname, |
| 559 | + op='traffic control',hint='tc',exit_on_error=False) |
| 560 | + if not vm_intf: |
| 561 | + return |
| 562 | + |
| 563 | + status = tc_netem_set(intf=vm_intf,tc_data=intf.tc) |
| 564 | + if status: |
| 565 | + log.info(text=f'Traffic control on {node.name} {intf.ifname}:{status}') |
| 566 | + else: |
| 567 | + log.error( |
| 568 | + text=f'Failed to deploy tc policy on {node.name} interface {intf.ifname} (Linux interface {vm_intf})', |
| 569 | + module='libvirt', |
| 570 | + skip_header=True, |
| 571 | + category=log.ErrorAbort) |
0 commit comments