Networking and TUN

tcpcc needs the hosted Linux stack to see real IP packets. If the outer host terminated TCP first and passed only a byte stream, the outer host would still own congestion control.

Packet path

public packet
  → exact-match DNAT / conntrack
  → routed to TUN
  → hosted Linux public TCP listener
  → hosted TCP termination
  → byte-stream relay
  → separate 127.0.0.1 backend TCP connection

A point-to-point TUN queue is sufficient because the boundary carries Layer-3 IPv4 or IPv6 packets. TAP/Ethernet, ARP, and a software Ethernet bridge are not part of the current design.

Firewall ownership

For each forward, tcpcc installs a narrow DNAT resource:

  • exact public destination address;
  • exact TCP destination port;
  • no UDP interception;
  • no broad redirect of unrelated host traffic;
  • no implicit SNAT/masquerade policy.

Each tcpcc instance owns only the TUN/firewall resources it generated and removes those resources during normal teardown.

Forwarding

The host must allow forwarding for the selected public family:

sysctl net.ipv4.ip_forward
sysctl net.ipv6.conf.all.forwarding

tcpcc checks prerequisites but does not silently change global forwarding sysctls.

One family per process

All public listeners in one process share one TUN/L3 endpoint. They therefore currently use one address family.

A process may expose several IPv4 listeners or several IPv6 listeners, but not a mixture of both.

Why public ports must be unique

DNAT maps the public routes to the same hosted TUN guest address while preserving the public TCP port. Two forwards with the same public port would collapse onto the same hosted endpoint and could not select distinct loopback backends.

Backend networking

The application-side connection is deliberately ordinary IPv4 loopback:

127.0.0.1:<backend-port>

This remains true even for an IPv6 public listener.