Skip to main content

TunDevice

TunDevice attaches a Waterwall packet chain to an operating-system TUN interface. It reads layer-3 IP packets from the virtual device and injects them into the chain, and it writes IP packets that reach it from the chain back into the device.

This is a layer-3 adapter, not a connection-oriented stream tunnel. It does not create one line per connection. Runtime packets use the worker packet lines that the chain provides for packet-layer nodes.

Typical Placement

TunDevice can be used at either edge of a packet chain:

TunDevice -> IpManipulator -> RawSocket
TunDevice -> WireGuardDevice -> UdpStatelessSocket
TunDevice -> PacketsToStream -> TcpConnector
TcpListener -> StreamToPackets -> TunDevice

When TunDevice is not the last node, packets read from the OS device are forwarded to the next node with the upstream packet path. When it is the last node, packets read from the OS device are forwarded to the previous node with the downstream packet path.

Payload that reaches TunDevice from either direction is treated as an IP packet and written to the TUN interface.

Basic Example

{
"name": "tun0-adapter",
"type": "TunDevice",
"settings": {
"device-name": "wtun0",
"device-ip": "10.10.0.1/24",
"device-mtu": 1500
},
"next": "next-node"
}

Full-route example with local networks excluded:

{
"name": "tun0-adapter",
"type": "TunDevice",
"settings": {
"device-name": "wtun0",
"device-ip": "10.10.0.1/24",
"device-mtu": 1500,
"route-table": "main",
"route-exclude-cidrs": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
],
"dns": ["1.1.1.1", "8.8.8.8"]
},
"next": "next-node"
}

Required Settings

FieldTypeDescription
device-namestringName of the TUN interface to create or open. Use a unique name that does not collide with another OS interface or another Waterwall node.
device-ipstringDevice address in CIDR form, such as 10.10.0.1/24. Waterwall splits this into the interface IP and subnet mask.

The top-level settings object must exist and must not be empty.

Optional Settings

FieldDefaultDescription
device-mtuglobal Waterwall MTUMTU configured on the TUN device. Must be between 1 and 65535.
route-tableoffControls native route installation. Use off, main, auto, or a Linux routing table name/number.
system-routefalseShortcut for full-device routing. If true and route-table is not set, behaves like route-table: "main".
route-cidrsfull route for the device address familyCIDRs to route through the TUN when system routing is enabled.
route-exclude-cidrsnoneCIDRs subtracted from route-cidrs, useful for LAN, management, and upstream endpoint ranges.
dnsunsetOne or two IPv4 DNS servers to associate with the TUN interface.
loop-protectionenabled when system routing is enabledKeeps Waterwall's own outbound sockets from being routed back into the TUN.
post-up-scriptunsetShell command run after the interface is up, routes are installed, and DNS is configured.
pre-down-scriptunsetShell command run before DNS/routes are cleaned up and the interface is brought down.

How The TUN Address Works

If you configure:

10.10.0.1/24

then 10.10.0.1 is the local address assigned to the operating-system TUN interface. Traffic sent to that exact address is local to the host, similar in spirit to sending traffic to 127.0.0.1; it is not the interesting packet path for Waterwall.

Traffic sent to another address in the routed range, for example:

10.10.0.2

is what normally enters the TUN device. In that case the packet seen by Waterwall can have source 10.10.0.1 and destination 10.10.0.2. Packet nodes after TunDevice, such as IpOverrider or IpManipulator, can then rewrite or transform that packet before it reaches the next adapter.

Route Configuration

Native route installation is disabled by default. Enable it with either:

"route-table": "main"

or:

"system-route": true

Supported route-table values:

ValueBehavior
offDo not install native routes.
mainInstall routes into the platform's main routing table.
autoTreated as main-table routing on platforms that support it.
Linux table name or numberInstall routes into that Linux routing table.

Windows and macOS reject route tables other than main or auto.

When routing is enabled and route-cidrs is omitted, Waterwall installs a full route for the address family of device-ip. A default route such as 0.0.0.0/0 is expanded into split /1 routes so the existing system default route does not need to be directly replaced.

route-exclude-cidrs subtracts networks from the route set:

{
"route-table": "main",
"route-cidrs": ["0.0.0.0/0"],
"route-exclude-cidrs": [
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16"
]
}

Use exclusions for local networks, management addresses, and any upstream server endpoint that must keep using the physical network path.

Loop Protection

When the TUN device becomes a system or full route, the host may route Waterwall's own outbound connector sockets back into the TUN. That creates a self-loop: Waterwall sends traffic, the OS sends it into the TUN, and Waterwall reads it again.

loop-protection is enabled by default whenever system routing is enabled. Before the TUN routes are installed, TunDevice snapshots the current physical default interface and publishes it as a process-wide egress pin. Outbound adapters such as TcpConnector, UdpConnector, and UdpStatelessSocket can apply that pin unless their own interface setting is configured.

Platform notes:

PlatformLoop-protection behavior
LinuxUses the detected default interface with socket interface binding.
WindowsUses the detected interface index for outbound socket routing.
macOSNo automatic egress pin currently; use explicit connector interface settings or route exclusions.

An explicit adapter interface setting overrides the automatic pin. A source-ip setting alone does not override it. If the chosen source IP belongs to a different physical interface than the detected default route, set the connector interface explicitly.

The c-ares DNS resolver sockets are not pinned by this feature. If the host's physical default route changes after startup, restart Waterwall or configure explicit interfaces/routes.

DNS Configuration

dns accepts one or two IPv4 addresses:

"dns": ["1.1.1.1", "8.8.8.8"]

Invalid IPv4 strings, hostnames, IPv6 addresses, empty entries, and more than two servers are rejected during node creation. null is treated like leaving the field unset.

Platform behavior:

PlatformBehavior
LinuxUses resolvectl dns and sets the ~. routing domain. Requires systemd-resolved and resolvectl.
WindowsUses netsh to configure static IPv4 DNS servers on the Wintun adapter. DNS precedence still follows Windows interface metrics.
macOSThe dns setting is rejected during node creation. Use post-up-script and pre-down-script for resolver setup.

Packet Flow

On startup, TunDevice:

  1. decides whether packets read from the device should go to the next or previous node
  2. creates the TUN interface
  3. assigns device-ip
  4. brings the interface up
  5. installs routes if enabled
  6. configures DNS if requested
  7. runs post-up-script if configured

When the OS device produces a packet:

  1. the packet is received on a worker
  2. the packet is dropped if the device is down or the application is terminating
  3. the packet version is checked
  4. IPv4 packets are forwarded using that worker's packet line
  5. non-IPv4 packets are currently dropped by this receive path

When payload reaches TunDevice from the chain:

  1. the payload is treated as an IP packet
  2. checksum recalculation is performed if the packet line requests it
  3. the packet is written to the TUN device
  4. if the device is down or the write fails, the buffer is reused and a warning is logged

Packet-Line Semantics

TunDevice is a packet adapter. It uses the worker packet line created by the chain, not a normal per-connection line. Packet lines are persistent worker-local runtime lines and must not be destroyed during normal packet flow.

In debug builds, after TunDevice forwards a received packet, it checks that the packet line is still alive. If a downstream packet tunnel destroys that packet line during runtime, Waterwall treats it as a packet-tunnel contract violation.

Connection-style callbacks such as Init, Est, Finish, Pause, and Resume are ignored by this adapter. Payload callbacks are the meaningful runtime path.

Checksum Behavior

Some packet tunnels can mark the line so checksums must be recalculated. When TunDevice writes a packet to the OS device and recalculate_checksum is set, it recalculates the packet checksum and clears the flag.

For fragmented IPv4 packets, Waterwall's checksum helper avoids recalculating transport checksums that cannot be safely recalculated from a single fragment and only fixes what can be fixed from the available packet data.

MTU Notes

If device-mtu is not set, the node uses Waterwall's global MTU value.

Lowering the MTU can be useful when later packet nodes add overhead. Be careful when combining TunDevice with adapters that work directly with a physical interface, such as RawSocket: the OS may reject packets that no longer match the real path MTU expectations. In that kind of topology, keep the global MTU and TUN MTU aligned with the physical path.

Shutdown Behavior

On stop or destroy, TunDevice cleans up in the reverse direction:

  1. runs pre-down-script before bringing the device down
  2. clears the loop-protection egress pin if this instance published it
  3. clears DNS settings
  4. removes installed routes in reverse install order
  5. brings down and destroys the TUN device

If route or DNS cleanup fails, Waterwall logs a warning.

Node Metadata

PropertyValue
LayerLayer 3
Chain positionCan be chain head or chain end
nextOptional, but normally present unless used at the end of a chain
prevAllowed
Required left padding0
Line stateMinimal placeholder state; packet runtime uses worker packet lines

Common Mistakes

  • Pinging the TUN interface address itself and expecting packets to enter Waterwall. Use another address in the routed range.
  • Enabling full routing without excluding the upstream server endpoint or relying on loop protection for a platform/topology it cannot cover.
  • Using dns on macOS. Configure resolver state with scripts instead.
  • Expecting non-IPv4 packets from the TUN receive path to be forwarded.
  • Treating TunDevice as a TCP/UDP connection adapter. It carries raw IP packets through packet lines.
  • Forgetting that Windows TUN management requires administrative privileges.