Read port forwards from per-VM forwards.conf files
A QEMU config file section cannot hold more than one hostfwd entry, so the forwards move out of the ini files into qemu/<vm>/forwards.conf, one "LABEL HOST_PORT GUEST_PORT" rule per line. The new vmforwards.py parses them, skips rules whose host port is taken and builds the -netdev argument, which every launcher now passes instead of relying on a netdev section in the ini or on -nic. The ini files move to qemu/<vm>/config.ini and qemu/<vm>/<os>-<arch>.ini. The *_SSH_PORT / *_RDP_PORT variables and --rdp-port / --ssh-port still replace the host port of the SSH and RDP rules. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
07a2c6db3e
commit
78f6fd424b
29 files changed
+272
-131
No files matched your search
@@ -0,0 +1,96 @@
|
||||
"""Host port forwards for the QEMU user-mode network, read from a .forwards file.
|
||||
|
||||
Each line of the file is "LABEL HOST_PORT GUEST_PORT"; "#" starts a comment.
|
||||
A QEMU config file section cannot hold more than one hostfwd entry, so the
|
||||
launchers build the -netdev argument from these rules instead.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
Forward = namedtuple("Forward", "label host_port guest_port")
|
||||
|
||||
|
||||
def _die(message):
|
||||
print(f"Error: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _port(text, where):
|
||||
if not (text.isascii() and text.isdigit() and 1 <= int(text) <= 65535):
|
||||
_die(f"{where}: not a port number from 1 to 65535: {text}")
|
||||
return int(text)
|
||||
|
||||
|
||||
def port_in_use(port):
|
||||
"""True if a host TCP port cannot be bound, as QEMU does for a hostfwd rule."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
|
||||
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
probe.bind(("", int(port)))
|
||||
except OSError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_forwards(path, host_port_overrides=None):
|
||||
"""Read the rules in a .forwards file.
|
||||
|
||||
host_port_overrides maps a label to a host port that replaces the one in
|
||||
the file, e.g. {"SSH": "2200"}.
|
||||
"""
|
||||
if not path.is_file():
|
||||
_die(f"port forward file was not found: {path}")
|
||||
forwards = []
|
||||
for number, line in enumerate(path.read_text().splitlines(), 1):
|
||||
fields = line.partition("#")[0].split()
|
||||
if not fields:
|
||||
continue
|
||||
where = f"{path}:{number}"
|
||||
if len(fields) != 3:
|
||||
_die(f"{where}: expected LABEL HOST_PORT GUEST_PORT: {line.strip()}")
|
||||
label, host_port, guest_port = fields
|
||||
if any(forward.label == label for forward in forwards):
|
||||
_die(f"{where}: duplicate label: {label}")
|
||||
forwards.append(Forward(label, _port(host_port, where), _port(guest_port, where)))
|
||||
for label, host_port in (host_port_overrides or {}).items():
|
||||
if not any(forward.label == label for forward in forwards):
|
||||
_die(f"{path} has no {label} rule to override the port of")
|
||||
forwards = [forward._replace(host_port=_port(str(host_port), f"{label} port"))
|
||||
if forward.label == label else forward for forward in forwards]
|
||||
seen = set()
|
||||
for forward in forwards:
|
||||
if forward.host_port in seen:
|
||||
_die(f"{path}: host port {forward.host_port} is used by more than one rule")
|
||||
seen.add(forward.host_port)
|
||||
return forwards
|
||||
|
||||
|
||||
def usable_forwards(forwards):
|
||||
"""Drop (with a warning) the rules whose host port is taken.
|
||||
|
||||
QEMU refuses to start when a forwarded host port is taken.
|
||||
"""
|
||||
usable = []
|
||||
for forward in forwards:
|
||||
if port_in_use(forward.host_port):
|
||||
print(f"Warning: host port {forward.host_port} is already in use; "
|
||||
f"{forward.label} will not be forwarded to the guest.", file=sys.stderr)
|
||||
else:
|
||||
usable.append(forward)
|
||||
return usable
|
||||
|
||||
|
||||
def netdev_argument(forwards, netdev_id="net0"):
|
||||
"""The value of -netdev for a user-mode network with the given rules."""
|
||||
return ",".join([f"user,id={netdev_id}", *(
|
||||
f"hostfwd=tcp::{forward.host_port}-:{forward.guest_port}" for forward in forwards)])
|
||||
|
||||
|
||||
def print_forwards(forwards):
|
||||
for forward in forwards:
|
||||
print(f"{forward.label}: localhost:{forward.host_port} -> guest port "
|
||||
f"{forward.guest_port}", flush=True)
|
||||
if forward.guest_port == 22:
|
||||
print(f" ssh -p {forward.host_port} guofang@localhost", flush=True)
|
||||
Reference in new issue
Block a user