proxytlv

package module
v0.0.0-...-c3e40af Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MPL-2.0 Imports: 13 Imported by: 0

README

proxytlv — PROXY v2 TLVs for caddy-l4

A caddy-l4 handler, layer4.handlers.proxy_tlv, for a Caddy that terminates TLS in front of a TCP service and needs to tell that service what it saw.

It writes one PROXY protocol v2 header ahead of the client's stream:

Header part Value
source / destination the real client, and the address it dialled (taken from an earlier proxy_protocol handler when there is one, else the socket)
PP2_TYPE_AUTHORITY (0x02) the SNI hostname
PP2_TYPE_ALPN (0x01) the negotiated application protocol, if any
PP2_TYPE_SSL (0x20) client-leg TLS, with PP2_SUBTYPE_SSL_VERSION ("TLS 1.3")

That header is the whole contract. The receiver needs to know nothing about Caddy — HAProxy's send-proxy-v2-ssl with proxy-v2-options authority produces the same thing — and the thanks, computer chassis reads it on a ;proxy= tcp listener, where the SNI becomes the hostname a connection is routed by.

Why it exists: caddy-l4's proxy handler can send a PROXY header, but without TLVs, so the hostname never reaches the upstream. When proxy learns to emit them, delete this module.

Use

xcaddy build v2.11.4 \
  --with github.com/mholt/caddy-l4@v0.1.2 \
  --with github.com/loremlabs/thanks-computer/caddy/proxytlv@<commit-or-tag>
{
	layer4 {
		:6697 {
			route {
				# optional: the hop in front of Caddy speaks PROXY too
				proxy_protocol {
					allow 172.16.0.0/12
					timeout 2s
				}
				tls
				proxy_tlv
				proxy {
					upstream chassis.internal:16697
				}
			}
		}
	}
}

Order matters: after tls (there is nothing to report before the handshake), before proxy. Do not also set proxy_protocol on the proxy handler — the upstream would get two headers. proxy_tlv takes no options.

The upstream must only accept this header from the edge's addresses; on the chassis that is the listener's ;proxy=CIDR|CIDR list.

Notes

  • A nested Go module on purpose: xcaddy --with resolves only this directory's go.mod (Caddy, caddy-l4, go-proxyproto), never the chassis's dependency graph. It imports nothing from chassis/. Tags take the nested-module form, caddy/proxytlv/v0.1.0.
  • Built against Caddy v2.11.4 and caddy-l4 v0.1.2, the pair the edge builds. xcaddy refuses a module that wants a newer Caddy than the one it builds, so this go.mod moves only when the edge's Caddy does. golang.org/x/text is raised past Caddy's own pick for an advisory in unicode/norm, which hostname normalization reaches; nothing else is.
  • The handler hands next a cx.Wrap of the connection, never a layer4.Connection built by hand: in caddy-l4 v0.1.x the vars and the replacer live in unexported fields that only Wrap copies.
  • No client-certificate TLVs: the edge does not ask for client certs. PP2_TYPE_SSL's verify field is therefore always non-zero.

Test

go test -race ./...

The root make test runs this too. For a live check, point upstream at nc -l 16697 | xxd and connect with openssl s_client -connect localhost:6697 -servername irc.example.test: the dump starts with the v2 signature (0d0a0d0a000d0a515549540a) and carries the 0x20, 0x02 and 0x01 TLVs.

Documentation

Overview

Package proxytlv is a caddy-l4 handler that tells the upstream what a TLS-terminating edge saw. It writes a PROXY protocol v2 header ahead of the client's stream carrying:

source / destination   the real client, and the address it dialled
PP2_TYPE_AUTHORITY     the SNI hostname
PP2_TYPE_ALPN          the negotiated application protocol
PP2_TYPE_SSL           "the client leg was TLS", + PP2_SUBTYPE_SSL_VERSION

That header IS the contract; the receiving side needs no knowledge of Caddy (HAProxy's `send-proxy-v2-ssl` + `proxy-v2-options authority` produces the same thing). This module exists only because caddy-l4's `proxy` handler writes PROXY headers without TLVs — delete it the day `proxy` can emit them itself.

Place it after `tls` and before `proxy`, and leave `proxy_protocol` off the `proxy` handler (it would add a second header):

route {
	proxy_protocol { allow 172.16.0.0/12 }   # optional: the hop in front of Caddy
	tls
	proxy_tlv
	proxy { upstream chassis.internal:16697 }
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Header(src, dst net.Addr, cs *tls.ConnectionState) ([]byte, error)

Header formats the PROXY v2 header for one connection. cs nil means the client leg was not TLS: addresses only, no TLVs. Exported so the receiving side can test against exactly what the edge writes.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler prepends a PROXY v2 header with TLS TLVs to the client stream. It has no options.

func (*Handler) CaddyModule

func (*Handler) CaddyModule() caddy.ModuleInfo

CaddyModule returns the Caddy module information.

func (*Handler) Handle

func (h *Handler) Handle(cx *layer4.Connection, next layer4.Handler) error

Handle builds the header from what the earlier handlers recorded on the connection and hands the next handler a stream that starts with it.

func (*Handler) Provision

func (h *Handler) Provision(ctx caddy.Context) error

Provision sets up the module.

func (*Handler) UnmarshalCaddyfile

func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

UnmarshalCaddyfile sets up the Handler from Caddyfile tokens. Syntax:

proxy_tlv

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL