"""The interface the engine writes decrypted traffic to. TLS is terminated inside the engine, which is what lets a protected service occupy no port beyond the one it already answered on — and it means the plaintext is never a packet on any interface, so there would be nothing for a capture tool to read. The engine writes it out itself (`proxysrc/src/capture.rs`), framed as the TCP stream it was before encryption, onto the device this module puts there. All that is needed here is the device: a `dummy` interface, which is a sink that carries whatever is sent to it and goes nowhere. One for the whole instance, so every TLS service's plaintext arrives on the same interface and a single capture covers the lot. It exists only while some TLS service is running. Nothing is lost by removing it — the engine reopens its socket per process — and an interface that is there when nothing is decrypting is an interface somebody points a capture at and watches stay empty. """ import subprocess #: What an operator types into a capture tool. Fixed, and the same name the engine uses. DEVICE = "firegex0" def _run(*args: str) -> subprocess.CompletedProcess: return subprocess.run(args, capture_output=True, text=True) def present() -> bool: return _run("ip", "link", "show", DEVICE).returncode == 0 def ensure() -> bool: """Put the device there, if it is not already. Returns whether it is now. A failure is not an error anybody hears about: a capture aid that could stop a service from starting would be worse than no capture aid. The engine finds no interface, writes nothing, and the traffic is filtered and forwarded exactly as it would have been. """ if present(): return True if _run("ip", "link", "add", DEVICE, "type", "dummy").returncode != 0: return False return _run("ip", "link", "set", DEVICE, "up").returncode == 0 def release() -> None: """Take it away again.""" _run("ip", "link", "del", DEVICE)