# Traffic Plane Configuration Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends. ## Smart TCP Edge Entrypoint Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional route-capable entrypoint name is `edge`, but Gordon does not require that name or assign a built-in public port. When exactly one route-capable `smart_tcp` or `tls_mux` entrypoint exists, app HTTP hosts use it even if it has a custom name. Choose the address that matches your deployment, firewall, and container mapping: ```toml [entrypoints.edge] address = ":443" protocol = "smart_tcp" trusted_cidrs = [] ``` Do not treat `entrypoints.edge.address` as an HTTP port or an HTTPS port. It is one TCP socket that sniffs each new connection and dispatches the original byte stream. Supported entrypoint protocols are `smart_tcp`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tls_mux` can also serve app HTTP hosts through TLS fallback, `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint. ## Smart TCP Dispatch Order For each accepted connection on a `smart_tcp` entrypoint Gordon: 1. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP. 2. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed. 3. Peeks a minimal prefix and replays the bytes to the selected handler/backend. 4. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler. 5. Dispatches TLS ClientHello traffic by SNI: - matching TLS passthrough router -> raw TCP backend without TLS termination; - no passthrough match -> normal Gordon HTTPS fallback. 6. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it. 7. Refuses the connection when no deterministic handler or allowed fallback exists. Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback. ## Standalone and Network Services Standalone services are Gordon-managed containers that L4 routers can target with `service::`. Define them under `[[services]]` when Gordon should own the container lifecycle. ```toml [[services]] name = "rust" image = "registry.example.com:5000/rust:latest" enabled = true [[services.ports]] name = "game" container = 28015 protocol = "udp" publish = "127.0.0.1:38015" [entrypoints.rust] address = "0.0.0.0:28015" protocol = "udp" [[traffic.udp.routers]] name = "rust-game" entrypoint = "rust" service = "service:rust:game" ``` Network services describe manually managed non-HTTP backends that L4 routers can target. ```toml [[network_services]] name = "postgres" [[network_services.ports]] name = "db" container = 5432 protocol = "tcp" ``` Service references use: - `route:` for app HTTP hosts from ACTIVE state (merged with installation external routes) - `external_route:` for configured external HTTP routes - `network_service::` for manually managed TCP, UDP, and TLS passthrough backends - `service::` for Gordon-managed standalone service backends `static:` is reserved and currently unsupported. ## TLS Passthrough on Smart TCP TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. ```toml [entrypoints.edge] address = ":443" protocol = "smart_tcp" [[traffic.tls.routers]] name = "raw-tls" entrypoint = "edge" sni = "raw.example.com" service = "network_service:raw:tls" ``` Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts on the same smart TCP entrypoint are rejected at validation time. App HTTPS hosts that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection. ## Raw TCP Fallback Raw fallback is disabled by default. Unknown non-HTTP/non-TLS bytes reach a TCP backend only when the smart TCP entrypoint explicitly names one TCP router as `raw_fallback` and the source policy allows the peer socket IP. ```toml [entrypoints.edge] address = ":443" protocol = "smart_tcp" raw_fallback = "ssh-fallback" raw_fallback_trusted_cidrs = ["100.64.0.0/10"] [[traffic.tcp.routers]] name = "ssh-fallback" entrypoint = "edge" service = "network_service:ssh:ssh" ``` Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly: ```toml [entrypoints.edge] address = ":443" protocol = "smart_tcp" raw_fallback = "public-raw" allow_public_raw_fallback = true ``` `trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`. ## HTTP Timeouts The HTTP servers behind `smart_tcp` and `tls_mux` entrypoints serve apps and the admin API. They do not use a fixed read or write timeout, so a download or upload that keeps moving data is never cut for taking too long. A connection is cut only after `transfer_idle_timeout` passes with no data in the direction being waited on. ```toml [traffic.http] read_header_timeout = "5s" idle_timeout = "30s" transfer_idle_timeout = "2m" ``` | Option | Env | Default | Description | |--------|-----|---------|-------------| | `traffic.http.read_header_timeout` | `GORDON_TRAFFIC_HTTP_READ_HEADER_TIMEOUT` | `"5s"` | Maximum time to read request headers | | `traffic.http.idle_timeout` | `GORDON_TRAFFIC_HTTP_IDLE_TIMEOUT` | `"30s"` | Maximum keep-alive idle time between requests | | `traffic.http.transfer_idle_timeout` | `GORDON_TRAFFIC_HTTP_TRANSFER_IDLE_TIMEOUT` | `"2m"` | Inactivity timeout for request bodies and responses | Values are Go duration strings; malformed, zero, or negative values are rejected when the configuration loads. Servers pick up changes when the traffic graph is next applied (reload); connections already in flight keep their previous timeout. ### Inactivity timeout Each request has an inactivity timer, armed when the request starts. Progress resets it: a response chunk accepted by the client connection (8 KiB granularity), a flush, or request body bytes read. If `transfer_idle_timeout` passes without progress, Gordon cancels the request context, which aborts the upstream request of a reverse-proxied app, and closes the client connection. When the handler returns, the final response flush and the drain of an unread request body are bounded by the same duration. Consequences: - a response that streams data at least once per `transfer_idle_timeout` can last arbitrarily long, and a client that keeps reading is never cut (it must accept 8 KiB per `transfer_idle_timeout`, about 68 B/s at the 2 minute default); - an upload whose body arrives at least once per `transfer_idle_timeout` can last arbitrarily long; - a backend that stays silent longer than `transfer_idle_timeout` before or between response chunks has its connection cut, so set it above the slowest expected backend response time; - the timer also fires when a handler or backend is silent and no write is pending; - WebSocket and other upgraded connections are not subject to it, and HTTP/2 streams are handled the same way as HTTP/1. `transfer_idle_timeout` does not apply to `[traffic.tcp]` or `[traffic.udp]` routers; those use their own `idle_timeout` (see below). ## TCP Routers Plain TCP entrypoints support one TCP router per entrypoint. TCP routers on a `smart_tcp` entrypoint are valid only when referenced by that entrypoint's `raw_fallback`. ```toml [entrypoints.postgres] address = "0.0.0.0:5432" protocol = "tcp" trusted_cidrs = ["100.64.0.0/10"] [traffic.tcp] dial_timeout = "10s" idle_timeout = "5m" drain_timeout = "30s" max_connections = 1024 [[traffic.tcp.routers]] name = "postgres" entrypoint = "postgres" service = "network_service:postgres:db" ``` If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint. ## UDP Routers UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint: ```toml [entrypoints.game] address = "0.0.0.0:7777" protocol = "udp" [traffic.udp] idle_timeout = "30s" drain_timeout = "30s" max_sessions = 4096 [[traffic.udp.routers]] name = "game" entrypoint = "game" service = "network_service:game:game" ``` UDP sessions are keyed by client address and expire after `idle_timeout`. If `max_sessions` is omitted or set to `0`, Gordon applies the safe runtime default of `4096` active sessions per entrypoint. ## Related - [Server Settings](./server.md) - [App Manifest](./apps.md) - [Standalone Services](./services.md) - [CLI traffic status](../cli/daemon.md#gordon-daemon-traffic)