How to Set Up OmniRoute's MITM Proxy with TPROXY for Traffic Inspection
OmniRoute's built-in MITM proxy uses Linux TPROXY transparent capture mode to intercept local TCP traffic by marking outbound connections in the netfilter mangle table, routing marked packets through a custom routing table to the loopback interface, and redirecting them to a local listener socket that terminates and re-issues TLS sessions.
OmniRoute is an open-source routing platform that ships with a built-in MITM (Man-in-the-Middle) proxy capable of capturing and inspecting TCP traffic from local applications without altering system-wide proxy settings. This guide explains how to configure the TPROXY interception mechanism using the actual source implementation from the diegosouzapw/OmniRoute repository. By following the exact code paths in src/mitm/tproxy/, you can deploy a transparent proxy that inspects HTTPS traffic for debugging, security auditing, or LLM request routing.
How TPROXY Interception Works in OmniRoute
The transparent interception relies on four sequential packet-flow stages implemented in the Linux networking stack. According to the source code in src/mitm/tproxy/commands.ts, the system manipulates the mangle table and custom routing tables to redirect traffic without modifying application configurations.
-
Mark outbound connections using an
iptablesrule in theOUTPUTchain that applies a firewall mark (fwmark) to packets destined for the target port. -
Add an IP rule that routes packets bearing this fwmark to a custom routing table ID.
-
Create a local-only route (
0.0.0.0/0 → lo) within that custom table, forcing marked packets to re-enter the host's local stack. -
Redirect in PREROUTING using a
TPROXYrule that sends the marked packets to the listener socket running on a specified local port, preserving the original destination address.
The listener socket, implemented in src/mitm/tproxy/transparentSocket.ts, binds with the IP_TRANSPARENT flag enabled, allowing it to receive packets addressed to foreign IP addresses. The src/mitm/tproxy/tlsCapture.ts module then handles TLS termination using a dynamically generated CA, enabling plaintext inspection of HTTPS sessions.
Prerequisites
Before configuring the MITM proxy, ensure your environment meets the following requirements defined in the OmniRoute source:
- Linux kernel ≥ 6.8 with TPROXY support enabled.
- iptables and ip (iproute2) utilities installed and available in
$PATH. - Root privileges or
CAP_NET_ADMINcapability for modifying netfilter rules and routing tables. - Node.js version ≥ 22 && < 23 or ≥ 24 && < 27 (as specified in the repository's
package.json).
Step-by-Step Setup Guide
Install OmniRoute
Clone the repository and install dependencies at the specific release version containing the TPROXY implementation:
git clone https://github.com/diegosouzapw/OmniRoute.git
cd OmniRoute
git checkout release/v3.8.49
npm ci
Generate the Self-Signed CA
The MITM proxy requires a certificate authority to re-sign intercepted TLS traffic. Generate the CA using the built-in test script:
node --import tsx/esm --test src/mitm/cert/generate.ts
This places the generated certificate files under src/mitm/cert/, which the MITM server automatically loads during startup.
Configure TproxyConfig
Create a configuration object matching the TproxyConfig interface defined in src/mitm/tproxy/commands.ts. This example targets HTTPS traffic (port 443):
import type { TproxyConfig } from "./src/mitm/tproxy/commands";
const cfg: TproxyConfig = {
dport: 443, // Destination port to intercept
mark: 0x2333, // Arbitrary fwmark (must be positive)
onPort: 8443, // Local port where the MITM listener binds
routeTable: 233, // Routing table ID for the local route
bypassMark: 0x9999, // Optional: SO_MARK for the proxy's own outbound traffic
};
Apply the TPROXY Rules
The applyTproxy function in src/mitm/tproxy/setup.ts executes the iptables and ip commands generated by buildTproxyApplyCommands. It uses a no-shell execFile implementation and provides crash-safe cleanup:
import { applyTproxy, revertTproxy } from "./src/mitm/tproxy/setup";
// Install firewall and routing rules
await applyTproxy(cfg);
If any command fails during application, applyTproxy automatically invokes revertTproxy to clean up partial state, maintaining the crash-safe invariant documented in the source.
Start the MITM Service
Launch the runtime manager to begin interception. The entry point at src/mitm/manager.runtime.ts dynamically imports the native implementation to avoid build-time module errors:
node --import tsx/esm src/mitm/manager.runtime.ts start
The manager performs the following sequence:
- Loads the generated CA from
src/mitm/cert/. - Initializes the transparent socket with
IP_TRANSPARENTenabled. - Calls
applyTproxyto install interception rules. - Begins handling connections through OmniRoute's internal request pipeline.
Alternatively, use the high-level MitmManager API:
import { MitmManager } from "./src/mitm/manager";
const manager = new MitmManager({
dport: 443,
mark: 0x2333,
onPort: 8443,
routeTable: 233,
bypassMark: 0x9999,
});
await manager.start(); // Applies TPROXY rules and starts listener
Verify the Setup
Confirm that the TPROXY rules are active:
# Check OUTPUT chain marking rule
sudo iptables -t mangle -L OUTPUT -v -n | grep MARK
# Check PREROUTING TPROXY redirection
sudo iptables -t mangle -L PREROUTING -v -n | grep TPROXY
# Verify fwmark routing rule and local route table
sudo ip rule list | grep 0x2333
sudo ip route show table 233
Test interception by making an HTTPS request from the host. The connection should appear in OmniRoute's logs while the iptables counters increment.
Tear Down
Remove the TPROXY rules using the idempotent revert function:
await revertTproxy(cfg);
The MitmManager also calls revertTproxy automatically during manager.stop() or process shutdown. Because the function is idempotent, missing rules (from previous crashes) are ignored safely.
Complete Implementation Example
The following TypeScript program demonstrates a minimal setup that starts the MITM proxy for HTTPS inspection:
// file: start-mitm.ts
import { applyTproxy, revertTproxy } from "./src/mitm/tproxy/setup";
import type { TproxyConfig } from "./src/mitm/tproxy/commands";
import { spawn } from "node:child_process";
async function main() {
const cfg: TproxyConfig = {
dport: 443,
mark: 0x2333,
onPort: 8443,
routeTable: 233,
bypassMark: 0x9999,
};
await applyTproxy(cfg);
const child = spawn("node", [
"--import", "tsx/esm",
"src/mitm/manager.runtime.ts",
"start"
], { stdio: "inherit" });
const shutdown = async () => {
child.kill();
await revertTproxy(cfg);
process.exit(0);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
main().catch((e) => {
console.error("Failed to start MITM:", e);
process.exit(1);
});
Core Source Files
Understanding these key files in diegosouzapw/OmniRoute helps with advanced customization:
-
src/mitm/tproxy/commands.ts– Builds typediptablesandipcommands for applying and reverting TPROXY rules based onTproxyConfig. -
src/mitm/tproxy/setup.ts– Executes command lists safely usingexecFile, guaranteeing crash-safe cleanup via automatic revert on failure. -
src/mitm/manager.runtime.ts– Runtime entry point that imports the native manager implementation while avoiding Turbopack alias issues. -
src/mitm/tproxy/transparentSocket.ts– Low-level socket implementation binding withIP_TRANSPARENTto receive redirected packets. -
src/mitm/tproxy/tlsCapture.ts– Handles TLS termination and re-encryption using the generated self-signed CA. -
src/mitm/cert/generate.ts– Generates the self-signed CA and host certificates required for HTTPS inspection. -
src/mitm/manager.ts– High-level controller orchestrating certificate generation, socket startup, and TPROXY lifecycle management.
Summary
- OmniRoute uses TPROXY transparent proxy mode to intercept local TCP traffic without modifying application proxy settings.
- The interception flow requires four netfilter components: OUTPUT chain marking, ip rule for fwmark, local route table, and PREROUTING TPROXY redirection.
- Configuration uses the
TproxyConfiginterface insrc/mitm/tproxy/commands.tsto define target ports, marks, and routing tables. applyTproxyandrevertTproxyinsrc/mitm/tproxy/setup.tsprovide crash-safe rule management with automatic cleanup on failure.- The
MitmManagerclass offers a high-level API that handles certificate generation, socket initialization, and graceful shutdown. - Node.js versions ≥22 && <23 or ≥24 && <27 and Linux kernel ≥6.8 are required for full functionality.
Frequently Asked Questions
What is the purpose of the bypassMark parameter in TproxyConfig?
The bypassMark parameter assigns a specific SO_MARK value to outbound connections initiated by the MITM proxy itself. This mark prevents the proxy's own traffic from being re-intercepted by the TPROXY rules, avoiding infinite loops where the proxy would try to proxy its own connections. Set this to any value distinct from the main interception mark.
Why does OmniRoute require IP_TRANSPARENT socket capability?
The IP_TRANSPARENT socket option, enabled in src/mitm/tproxy/transparentSocket.ts, allows the MITM listener to bind to and receive packets addressed to foreign IP addresses (the original destinations). Without this capability, the TPROXY redirection would fail because the local socket could not accept packets originally destined for remote HTTPS servers on port 443.
How does OmniRoute handle TLS encryption for intercepted HTTPS traffic?
OmniRoute generates a self-signed Certificate Authority using src/mitm/cert/generate.ts. When the transparent socket captures a connection, src/mitm/tproxy/tlsCapture.ts terminates the TLS session using a certificate signed by this CA, decrypts the traffic for inspection, then establishes a new TLS connection to the original upstream destination. The client must trust the generated CA certificate to avoid security warnings.
What happens if the MITM process crashes while TPROXY rules are active?
The applyTproxy function in src/mitm/tproxy/setup.ts implements a crash-safe invariant: if any iptables or ip command fails during setup, it immediately calls revertTproxy to remove partially applied rules. However, if the process crashes after successful setup, rules may persist. In this case, running revertTproxy manually (or restarting the host) cleans up the residual state, as the revert function is idempotent and ignores missing rules.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →