diff --git a/README.md b/README.md index fe604b7..7ea41b7 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,57 @@ The situation here is that in linux every network namespace automatically gets i As a workaround, the address 169.254.77.65 is hardcoded within httptap to route to 127.0.0.1. +# Subprocesses that daemonize + +In linux, it is possible for a process to create subprocesses that stick around even when the original process exits. This is standard practice for daemons and also for GUI apps launched from the command line. If you run a process that daemonizes under httptap, the daemonized process will still be in httptap's network namespace, but you will need to use `--no-exit` to make sure that httptap keeps proxying and logging traffic even after the immediate subprocess exits. For example, here is visual studio code running within httptap: + +```bash +$ httptap --no-exit -- code --ignore-certificate-errors . +---> OPTIONS https://default.exp-tas.com/vscode/ab +<--- 204 https://default.exp-tas.com/vscode/ab (0 bytes) +---> GET https://default.exp-tas.com/vscode/ab +<--- 304 https://default.exp-tas.com/vscode/ab (0 bytes) +... +``` + +You will have to press ctrl+C to kill httptap once you exit vscode. + +In the above, without `--no-exit`, httptap would exit immediately after launching vscode. However, even though the subprocess launched by httptap has exited, that subprocess created a whole separate process tree before it did so. That process tree is the actual GUI app, and it is still pinned to the httptap network namespace. If httptap exits then the network namespace will continue to exist, and the GUI app will continue to run, but nobody will be reading packets sent to the TUN device that is only available network interface in that network namespace, and as a result the app will have no network connectivity. The workaround for this is the `--no-exit` flag that asks httptap to keep proxying network traffic even after the immediate subprocess exits. + +A more minimal example of this same phenomenon is: +```bash +$ httptap --no-exit -- setsid setsid curl http://httpbin.org/get +---> GET http://httpbin.org/get +<--- 200 http://httpbin.org/get (285 bytes) +``` + +Without the `--no-exit` flag, the above fails: +```bash +$ httptap -- setsid setsid curl http://httpbin.org/get +curl: (6) Could not resolve host: httpbin.org +``` + +A python example that illustrates what syscalls are at play is: +```bash +$ $ httptap --no-exit -- python -c 'import os; import sys; import subprocess +if os.fork(): sys.exit() +os.setsid() +if os.fork(): sys.exit() +subprocess.run(["curl", "http://httpbin.org/get"])' +---> GET http://httpbin.org/get +<--- 200 http://httpbin.org/get (285 bytes) +``` + +Again, without `--no-exit`, the above fails: +```bash +$ httptap -- python -c 'import os; import sys; import subprocess +if os.fork(): sys.exit() +os.setsid() +if os.fork(): sys.exit() +subprocess.run(["curl", "http://httpbin.org/get"])' +curl: (6) Could not resolve host: httpbin.org +``` + # How it works When you run `httptap -- `, httptap runs `` in an isolated network namespace, injecting a certificate authority created on-the-fly in order to decrypt HTTPS traffic. Here is the process in detail: diff --git a/httptap.go b/httptap.go index 346ee03..3cb3a29 100644 --- a/httptap.go +++ b/httptap.go @@ -108,6 +108,7 @@ func Main() error { Head bool `help:"whether to include HTTP headers in terminal output"` Body bool `help:"whether to include HTTP payloads in terminal output"` PrintDNS bool `arg:"--print-dns" help:"whether to print DNS queries and responses"` + NoExit bool `arg:"--no-exit" help:"do not exit when the launched subprocess exits; instead keep proxying forever"` Command []string `arg:"positional"` } args.HTTPPorts = []int{80} @@ -803,6 +804,20 @@ func Main() error { if err != nil { return fmt.Errorf("error running subprocess: %w", err) } + + // If the user requested that we do not exit when the subprocess exits, then stick around. + // + // This is critical for cases where a process daemonizes itself (forks, detaches itself to + // a new process group, then forks again). This is a common pattern for GUI apps launched + // from the command line. + // + // Under these circumstances, the subprocess we launch will return, but there will still be + // other subprocesses running in the network namespace. If the user wants to monitor their + // network activity then they can use "--no-exit" + if args.NoExit { + select {} + } + return nil }