Skip to content

Add TCP/UDP ingress proxying alongside HTTP#116

Merged
GyulyVGC merged 2 commits into
NullNet-ai:mainfrom
antoncxx:feature/proxy-tcp-udp-support
Jul 2, 2026
Merged

Add TCP/UDP ingress proxying alongside HTTP#116
GyulyVGC merged 2 commits into
NullNet-ai:mainfrom
antoncxx:feature/proxy-tcp-udp-support

Conversation

@antoncxx

@antoncxx antoncxx commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends nullnet-proxy to forward raw TCP and UDP traffic in addition to its existing Host-header-routed HTTP proxying. Services can now declare protocol = "tcp" or "udp" and a listen_port in the server stack config; the proxy binds that port directly and forwards traffic to the service — no Host header involved.

What changed

Protocol (nullnet-grpc-lib)

  • New WatchPortMappings streaming RPC — proxy subscribes to the live port→service table
  • New PortMappingBundle / PortMapping messages carrying service_name, protocol, listen_port, idle_timeout_secs
  • ServiceProtocol enum: HTTP (default, existing behaviour), TCP, UDP
  • New agent event types for bind/connect failures: AgentTcpListenerBindFailed, AgentTcpUpstreamConnectFailed, AgentUdpListenerBindFailed, AgentUdpUpstreamConnectFailed

Server (nullnet-server)

  • Stack TOML parser now accepts protocol and listen_port fields; validates that TCP/UDP services always declare a port and HTTP services never do
  • listen_port uniqueness is enforced globally across all stacks on both startup and hot-reload — conflicts are rejected with a structured event
  • WatchPortMappings streams the current port→service table to every connected proxy, then pushes a full snapshot on every TOML change
  • ServiceInfo (both Unregistered and Registered variants) carries protocol and listen_port throughout its lifecycle
  • Topology UI marks TCP/UDP services in the graph the same way HTTP services are shown; the "entry" badge requires timeout to be set (same rule as HTTP)

Proxy (nullnet-proxy)

  • port_mappings.rs — subscribes to WatchPortMappings, diffs the received table against running listeners, and hot-opens or hot-closes raw sockets without a proxy restart
  • tcp_relay.rs — raw TCP listener; each accepted connection resolves an upstream via the existing Proxy RPC (on-demand VXLAN setup), then pipes bidirectionally
  • udp_relay.rs — raw UDP listener; first datagram from a new client address opens a session, subsequent datagrams reuse it; sessions are reaped after idle_timeout_secs of inactivity

Configuration

Server stack TOML (services/<stack>.toml)

# TCP service — proxy binds port 22 and forwards to ssh.internal
[[services]]
name = "ssh.internal"
timeout = 300        # seconds; 0 = no idle timeout
protocol = "tcp"
listen_port = 22

# UDP service
[[services]]
name = "dns.internal"
timeout = 0
protocol = "udp"
listen_port = 53

listen_port must be globally unique per protocol across every stack. timeout must be set (even to 0) for the service to be proxy-reachable; without it the service is backend-only and the proxy rejects Proxy RPC calls for it.

Client (services.toml)

Non-Docker host services (e.g. sshd) are declared without docker_container. The client checks that the declared port is actively listening before registering the service:

[[services]]
name = "ssh.internal"
port = 22
stack = "my-stack"

Known limitation

For TCP/UDP the server tracks session lifetime via the single Proxy RPC call made at connection open. Unlike HTTP (where every request refreshes the idle timer), a long-lived TCP session ages from connection establishment. A ReleaseProxy RPC to signal session close and trigger immediate VXLAN teardown is left as follow-up work.

antoncxx added 2 commits July 1, 2026 15:43
nullnet-proxy can now forward raw TCP/UDP traffic to services declared
with protocol = "tcp"/"udp" and a listen_port in the server stack
config, in addition to the existing Host-header-routed HTTP proxying on
80/443.

- New WatchPortMappings gRPC streaming RPC pushes the live port→service
  table to the proxy; the proxy hot-opens/closes raw listeners as the
  table changes — no restart needed
- listen_port uniqueness is validated globally across all stacks on
  startup and hot-reload
- tcp_relay: bidirectional pipe per connection, resolved via the
  existing Proxy RPC (on-demand VXLAN)
- udp_relay: per-client-address sessions with configurable idle timeout
- Detailed logging throughout: connection accepted, upstream resolved,
  relay open/close with byte counts, session sweeps, port-mapping
  bundle contents on every push
- Non-Docker host services (e.g. sshd) are supported on the client by
  omitting docker_container; the client checks the port is listening
  before declaring the service
@antoncxx antoncxx marked this pull request as ready for review July 1, 2026 19:50
@GyulyVGC GyulyVGC added the enhancement New feature or request label Jul 2, 2026
@GyulyVGC GyulyVGC merged commit 5fd202e into NullNet-ai:main Jul 2, 2026
4 checks passed
@antoncxx antoncxx deleted the feature/proxy-tcp-udp-support branch July 2, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants