Skip to content

Stream proxy protocol ​

This package implements a small header-based handshake that allows an authenticated client to request forwarding to a (host, port) destination. It supports both TCP-over-TLS and UDP-over-DTLS transports.

Overview ​

The on-wire header is a fixed-size binary blob:

  • Version (8 bytes)
  • HostLength (1 byte)
  • Host (255 bytes, NUL padded)
  • PortLength (1 byte)
  • Port (5 bytes, NUL padded)
  • Flag (1 byte, protocol flags)
  • Checksum (4 bytes, big-endian CRC32)

Total: headerSize = 8 + 1 + 255 + 1 + 5 + 1 + 4 = 275 bytes.

Checksum is crc32.ChecksumIEEE(header[0:headerSize-4]).

Flags ​

The Flag field is a bitmask of protocol flags defined by FlagType:

FlagValuePurpose
FlagCloseImmediately1Health check probe - server closes immediately after validating header

See FlagType and FlagCloseImmediately.

See StreamRequestHeader.

File Structure ​

FilePurpose
header.goStream request header structure and validation.
tcp_client.goTCP client implementation with TLS transport.
tcp_server.goTCP server implementation for handling stream requests.
udp_client.goUDP client implementation with DTLS transport.
udp_server.goUDP server implementation for handling DTLS stream requests.
common.goConnection manager and shared constants.

Constants ​

ConstantValuePurpose
StreamALPN"godoxy-agent-stream/1"TLS ALPN protocol for stream multiplexing.
headerSize275 bytesTotal size of the stream request header.
dialTimeout10sTimeout for establishing destination connections.
readDeadline10sRead timeout for UDP destination sockets.
FlagCloseImmediately1Flag for health check probe - server closes immediately

See common.go.

Public API ​

Types ​

StreamRequestHeader ​

Represents the on-wire protocol header used to negotiate a stream tunnel.

go
type StreamRequestHeader struct {
    Version     [8]byte  // Fixed to "0.1.0" with NUL padding
    HostLength  byte     // Actual host name length (0-255)
    Host        [255]byte // NUL-padded host name
    PortLength  byte     // Actual port string length (0-5)
    Port        [5]byte  // NUL-padded port string
    Flag        FlagType // Protocol flags (e.g., FlagCloseImmediately)
    Checksum    [4]byte  // CRC32 checksum of header without checksum
}

Methods:

  • NewStreamRequestHeader(host, port string) (*StreamRequestHeader, error) - Creates a header for the given host and port. Returns error if host exceeds 255 bytes or port exceeds 5 bytes.
  • NewStreamHealthCheckHeader() *StreamRequestHeader - Creates a header with FlagCloseImmediately set for health check probes.
  • Validate() bool - Validates the version and checksum.
  • GetHostPort() (string, string) - Extracts the host and port from the header.
  • ShouldCloseImmediately() bool - Returns true if FlagCloseImmediately is set.

TCP Functions ​

UDP Functions ​

  • NewUDPClient() - Creates a DTLS client connection and sends the stream header.
  • NewUDPServer() - Creates a DTLS server listening on the given UDP address.

Health Check Probes ​

The protocol supports health check probes using the FlagCloseImmediately flag. When a client sends a header with this flag set, the server validates the header and immediately closes the connection without establishing a destination tunnel.

This is useful for:

  • Connectivity testing between agent and server
  • Verifying TLS/DTLS handshake and mTLS authentication
  • Monitoring stream protocol availability

Usage:

go
header := stream.NewStreamHealthCheckHeader()
// Send header over TLS/DTLS connection
// Server will validate and close immediately

Both TCP and UDP servers silently handle health check probes without logging errors.

See NewStreamHealthCheckHeader() and FlagCloseImmediately.

TCP behavior ​

  1. Client establishes a TLS connection to the stream server.
  2. Client sends exactly one header as a handshake.
  3. After the handshake, both sides proxy raw TCP bytes between client and destination.

Server reads the header using io.ReadFull to avoid dropping bytes.

See NewTCPClient() and (*TCPServer).redirect().

UDP-over-DTLS behavior ​

  1. Client establishes a DTLS connection to the stream server.
  2. Client sends exactly one header as a handshake.
  3. After the handshake, both sides proxy raw UDP datagrams:
    • client -> destination: DTLS payload is written to destination UDPConn
    • destination -> client: destination payload is written back to the DTLS connection

Responses do not include a header.

The UDP server uses a bidirectional forwarding model:

  • One goroutine forwards from client to destination
  • Another goroutine forwards from destination to client

The destination reader uses readDeadline to periodically wake up and check for context cancellation. Timeouts do not terminate the session.

See NewUDPClient() and (*UDPServer).handleDTLSConnection().

Connection Management ​

Both TCPServer and UDPServer create a dedicated destination connection per incoming stream session and close it when the session ends (no destination connection reuse).

Error Handling ​

ErrorDescription
ErrInvalidHeaderHeader validation failed (version or checksum).
ErrCloseImmediatelyHealth check probe - server closed immediately.

Errors from connection creation are propagated to the caller.

See header.go.

Integration ​

This package is used by the agent to provide stream tunneling capabilities. See the parent agent package for integration details with the GoDoxy server.

Certificate Requirements ​

Both TCP and UDP servers require:

  • CA certificate for client verification
  • Server certificate for TLS/DTLS termination

Both clients require:

  • CA certificate for server verification
  • Client certificate for mTLS authentication

ALPN Protocol ​

The StreamALPN constant ("godoxy-agent-stream/1") is used to multiplex stream tunnel traffic and HTTPS API traffic on the same port. Connections negotiating this ALPN are routed to the stream handler.

Released under the MIT License.