79 lines
3.8 KiB
Python
79 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Check real NIC identities on a bridge isolated in user/net/mount namespaces."""
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from hydra.profiles import Profile
|
|
from hydra.runtime import Runtime
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--namespace-root', type=Path, help=argparse.SUPPRESS)
|
|
args = parser.parse_args()
|
|
if args.namespace_root is None:
|
|
root = Path(tempfile.mkdtemp(prefix='hydra-network-', dir='/tmp'))
|
|
print('Evidence: ' + str(root), flush=True)
|
|
(root / 'parent-namespaces.json').write_text(json.dumps({name: str(Path('/proc/self/ns', name).readlink())
|
|
for name in ('user', 'net', 'mnt')}))
|
|
# All bridge and /etc changes below occur after this explicit namespace
|
|
# boundary. The inherited host bridge configuration is never modified.
|
|
subprocess.run(['unshare', '-Urnm', '--', str(Path(sys.executable).resolve()),
|
|
str(Path(__file__).resolve()), '--namespace-root', str(root)], check=True)
|
|
return
|
|
root = args.namespace_root
|
|
parent_namespaces = json.loads((root / 'parent-namespaces.json').read_text())
|
|
namespaces = {name: str(Path('/proc/self/ns', name).readlink()) for name in ('user', 'net', 'mnt')}
|
|
assert all(namespaces[name] != parent_namespaces[name] for name in namespaces), 'private namespaces required'
|
|
private_etc = root / 'etc'
|
|
(private_etc / 'qemu').mkdir(parents=True)
|
|
(private_etc / 'qemu/bridge.conf').write_text('allow hydrabr0\n')
|
|
subprocess.run(['mount', '--bind', str(private_etc), '/etc'], check=True)
|
|
subprocess.run(['ip', 'link', 'add', 'hydrabr0', 'type', 'bridge'], check=True)
|
|
subprocess.run(['ip', 'link', 'set', 'hydrabr0', 'up'], check=True)
|
|
manager = Runtime(root / 'config', root / 'state', root / 'run')
|
|
result = {'evidence': str(root), 'namespace': 'private user/net/mount',
|
|
'parent_namespaces': parent_namespaces, 'probe_namespaces': namespaces, 'names': {}}
|
|
try:
|
|
for name in ('alpha', 'beta'):
|
|
manager.add(Profile(name, memory=128, cpus=1, disk_size='8M',
|
|
graphics='software', network='bridge'))
|
|
manager.start(name)
|
|
|
|
def actual_mac(name):
|
|
with manager._connect(manager.paths(name)) as qmp:
|
|
network = qmp.command('human-monitor-command', {'command-line': 'info network'})
|
|
mac = manager.profile(name).mac
|
|
assert 'macaddr=' + mac in network, network
|
|
assert int(mac.split(':')[0], 16) & 3 == 2
|
|
return mac, network
|
|
|
|
for name in ('alpha', 'beta'):
|
|
mac, network = actual_mac(name)
|
|
result['names'][name] = {'mac': mac, 'network': network}
|
|
assert result['names']['alpha']['mac'] != result['names']['beta']['mac']
|
|
links = json.loads(subprocess.check_output(['ip', '-j', 'link', 'show', 'master', 'hydrabr0'], text=True))
|
|
assert len(links) == 2, links
|
|
result['bridge_ports'] = [link['ifname'] for link in links]
|
|
manager.stop('alpha', force=True)
|
|
manager.start('alpha')
|
|
assert actual_mac('alpha')[0] == result['names']['alpha']['mac']
|
|
result['restart_preserved_mac'] = True
|
|
result['dhcp_and_guest_network_drivers_pending'] = True
|
|
result['passed'] = True
|
|
(root / 'result.json').write_text(json.dumps(result, indent=2) + '\n')
|
|
print(json.dumps(result, indent=2))
|
|
finally:
|
|
for profile in manager.profiles():
|
|
if manager.status(profile.name)['status'] == 'running':
|
|
manager.stop(profile.name, force=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|