Add TCP/UDP ingress proxying alongside HTTP#116
Merged
Conversation
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
GyulyVGC
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
nullnet-proxyto forward raw TCP and UDP traffic in addition to its existing Host-header-routed HTTP proxying. Services can now declareprotocol = "tcp"or"udp"and alisten_portin the server stack config; the proxy binds that port directly and forwards traffic to the service — noHostheader involved.What changed
Protocol (
nullnet-grpc-lib)WatchPortMappingsstreaming RPC — proxy subscribes to the live port→service tablePortMappingBundle/PortMappingmessages carryingservice_name,protocol,listen_port,idle_timeout_secsServiceProtocolenum:HTTP(default, existing behaviour),TCP,UDPAgentTcpListenerBindFailed,AgentTcpUpstreamConnectFailed,AgentUdpListenerBindFailed,AgentUdpUpstreamConnectFailedServer (
nullnet-server)protocolandlisten_portfields; validates that TCP/UDP services always declare a port and HTTP services never dolisten_portuniqueness is enforced globally across all stacks on both startup and hot-reload — conflicts are rejected with a structured eventWatchPortMappingsstreams the current port→service table to every connected proxy, then pushes a full snapshot on every TOML changeServiceInfo(bothUnregisteredandRegisteredvariants) carriesprotocolandlisten_portthroughout its lifecycletimeoutto be set (same rule as HTTP)Proxy (
nullnet-proxy)port_mappings.rs— subscribes toWatchPortMappings, diffs the received table against running listeners, and hot-opens or hot-closes raw sockets without a proxy restarttcp_relay.rs— raw TCP listener; each accepted connection resolves an upstream via the existingProxyRPC (on-demand VXLAN setup), then pipes bidirectionallyudp_relay.rs— raw UDP listener; first datagram from a new client address opens a session, subsequent datagrams reuse it; sessions are reaped afteridle_timeout_secsof inactivityConfiguration
Server stack TOML (
services/<stack>.toml)listen_portmust be globally unique per protocol across every stack.timeoutmust be set (even to0) for the service to be proxy-reachable; without it the service is backend-only and the proxy rejectsProxyRPC 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:Known limitation
For TCP/UDP the server tracks session lifetime via the single
ProxyRPC call made at connection open. Unlike HTTP (where every request refreshes the idle timer), a long-lived TCP session ages from connection establishment. AReleaseProxyRPC to signal session close and trigger immediate VXLAN teardown is left as follow-up work.