diff --git a/Dockerfile b/Dockerfile index dcb475259..8f1434284 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ARG LINUX_VERSION="2.4.26" ARG LIBNVRAM_VERSION="0.0.16" ARG CONSOLE_VERSION="1.0.5" ARG PENGUIN_PLUGINS_VERSION="1.5.15" -ARG VPN_VERSION="1.0.20" +ARG VPN_VERSION="1.0.23" ARG HYPERFS_VERSION="0.0.38" ARG GUESTHOPPER_VERSION="1.0.15" ARG GLOW_VERSION="1.5.1" diff --git a/docs/schema_doc.md b/docs/schema_doc.md index 23e7ccc7e..dd148d236 100644 --- a/docs/schema_doc.md +++ b/docs/schema_doc.md @@ -1154,4 +1154,30 @@ Whether to enable this plugin (default depends on plugin) |__Default__|`null`| +## `network` Network Configuration + +Configuration for networks to attach to guest + +### `network.external` Set up NAT for outgoing connections + +Configuration for NAT for external connections + +#### `network.external.mac` MAC Address for external interface + +||| +|-|-| +|__Type__|string or null| +|__Default__|`'52:54:00:12:34:56'`| + +MAC Address for external network interface + +#### `network.external.pcap` pcap file name + +||| +|-|-| +|__Type__|boolean or null| +|__Default__|`null`| + +Whether to capture traffic over the external net in a pcap file. The file will be called 'ext.pcap' in the output directory. Capture disabled if unset. + diff --git a/pyplugins/actuation/vpn.py b/pyplugins/actuation/vpn.py index cb4c798ad..7a7a1c0fe 100644 --- a/pyplugins/actuation/vpn.py +++ b/pyplugins/actuation/vpn.py @@ -54,7 +54,8 @@ def __init__(self, panda): self.launch_host_vpn(self.get_arg("CID"), self.get_arg("socket_path"), self.get_arg("uds_path"), - self.get_arg_bool("log")) + self.get_arg_bool("log"), + self.get_arg_bool("pcap")) port_maps = self.get_arg("IGLOO_VPN_PORT_MAPS") self.seen_ips = set() # IPs we've seen @@ -124,7 +125,7 @@ def __init__(self, panda): # Whenever NetLog detects a bind, we'll set up bridges plugins.subscribe(plugins.NetBinds, "on_bind", self.on_bind) - def launch_host_vpn(self, CID, socket_path, uds_path, log=False): + def launch_host_vpn(self, CID, socket_path, uds_path, log=False, pcap=False): ''' Launch vhost-device-vsock and VPN on host ''' @@ -157,7 +158,9 @@ def launch_host_vpn(self, CID, socket_path, uds_path, log=False): ] if log: host_vpn_cmd.extend(["-o", self.outdir]) - self.host_vpn = subprocess.Popen(host_vpn_cmd, stdout=subprocess.DEVNULL) + if pcap: + host_vpn_cmd.extend(["-l", join(self.outdir, "vpn.pcap")]) + self.host_vpn = subprocess.Popen(host_vpn_cmd, stdout=subprocess.DEVNULL, stderr=None) running_vpns.append(self.host_vpn) def on_bind(self, sock_type, ipvn, ip, port, procname): @@ -316,6 +319,7 @@ def uninit(self): if hasattr(self, "host_vpn"): self.host_vpn.terminate() + self.host_vpn.wait(timeout=2) # Wait for logged packets to flush self.host_vpn.kill() running_vpns[:] = [x for x in running_vpns if x != self.host_vpn] self.logger.debug("Killed VPN") diff --git a/src/penguin/penguin_config/structure.py b/src/penguin/penguin_config/structure.py index d7f9a9982..baa17f800 100644 --- a/src/penguin/penguin_config/structure.py +++ b/src/penguin/penguin_config/structure.py @@ -763,6 +763,38 @@ class Plugin(BaseModel): version: Annotated[Optional[str], Field(None, title="Plugin version")] +class ExternalNetwork(BaseModel): + """Configuration for NAT for external connections""" + + model_config = ConfigDict(title="Set up NAT for outgoing connections", extra="forbid") + + mac: Optional[str] = Field( + title="MAC Address for external interface", + default="52:54:00:12:34:56", + description="MAC Address for external network interface" + ) + + # Not supported until QEMU 4.0+ + # net: Optional[str] = Field( + # default="10.0.2.0/24", + # description="Net for external interface (e.g., 10.0.2.0/24). Host will accessible via .2" + # ) + + pcap: Optional[bool] = Field( + title="pcap file name", + default=None, + description="Whether to capture traffic over the external net in a pcap file. The file will be called 'ext.pcap' in the output directory. Capture disabled if unset." + ) + + +class Network(BaseModel): + """Configuration for networks to attach to guest""" + + model_config = ConfigDict(title="Network Configuration", extra="forbid") + + external: ExternalNetwork = Field(default_factory=ExternalNetwork) + + class Main(BaseModel): """Configuration file for config-file-based rehosting with IGLOO""" @@ -779,3 +811,4 @@ class Main(BaseModel): lib_inject: LibInject static_files: StaticFiles plugins: Annotated[dict[str, Plugin], Field(title="Plugins")] + network: Optional[Network] = None diff --git a/src/penguin/penguin_run.py b/src/penguin/penguin_run.py index 27744e053..1810f3b5b 100755 --- a/src/penguin/penguin_run.py +++ b/src/penguin/penguin_run.py @@ -381,6 +381,21 @@ def run_config( # Add args from config args += shlex.split(conf["core"].get("extra_qemu_args", "")) + # If we have network args + if network := conf.get("network", None): + if "external" in network: + mac = network["external"]["mac"] + arg_str = f"-netdev user,id=ext -device virtio-net-pci,netdev=ext,mac={mac}" + # Supported in future versions of QEMU + # if net := network["external"].get("net", None): + # arg_str += ",net={net}" + if network["external"].get("pcap"): + pcap_path = os.path.join(out_dir, "ext.pcap") + arg_str += f" -object filter-dump,id=fext,netdev=ext,file={pcap_path}" + args += shlex.split(arg_str) + conf["env"]["IGLOO_EXT_MAC"] = mac + logger.info(f"Starting external network on interface {mac}. Host is available on 10.0.2.2") + # Disable audio (allegedly speeds up emulation by avoiding running another thread) os.environ["QEMU_AUDIO_DRV"] = "none" diff --git a/src/resources/source.d/85_external_net.sh b/src/resources/source.d/85_external_net.sh new file mode 100644 index 000000000..18f02e08b --- /dev/null +++ b/src/resources/source.d/85_external_net.sh @@ -0,0 +1,13 @@ + +#!/igloo/utils/busybox sh + +if [ ! -z "${IGLOO_EXT_MAC}" ]; then + for iface in $(/igloo/utils/busybox ls /sys/class/net); do + if [ "$(/igloo/utils/busybox cat /sys/class/net/$iface/address)" = "${IGLOO_EXT_MAC}" ]; then + break + fi + done + /igloo/utils/busybox ip addr add 10.0.2.15/24 dev $iface + /igloo/utils/busybox ip route add default via 10.0.2.2 + echo "[IGLOO INIT] Found interface $iface with MAC $IGLOO_EXT_MAC and configured with IP 10.0.2.15/24" +fi