This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+125 -122
View File
@@ -1,162 +1,165 @@
# go-proxyproto
[![Actions Status](https://github.com/pires/go-proxyproto/workflows/test/badge.svg)](https://github.com/pires/go-proxyproto/actions)
[![Coverage Status](https://coveralls.io/repos/github/pires/go-proxyproto/badge.svg?branch=master)](https://coveralls.io/github/pires/go-proxyproto?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/pires/go-proxyproto/badge.svg?branch=main)](https://coveralls.io/github/pires/go-proxyproto?branch=main)
[![Go Report Card](https://goreportcard.com/badge/github.com/pires/go-proxyproto)](https://goreportcard.com/report/github.com/pires/go-proxyproto)
[![](https://godoc.org/github.com/pires/go-proxyproto?status.svg)](https://pkg.go.dev/github.com/pires/go-proxyproto?tab=doc)
[![Go Reference](https://pkg.go.dev/badge/github.com/pires/go-proxyproto.svg)](https://pkg.go.dev/github.com/pires/go-proxyproto)
A Go library implementation of the [PROXY protocol, versions 1 and 2](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt),
which provides, as per specification:
> (...) a convenient way to safely transport connection
> information such as a client's address across multiple layers of NAT or TCP
> proxies. It is designed to require little changes to existing components and
> to limit the performance impact caused by the processing of the transported
> information.
A Go library implementation of the
[HAProxy PROXY protocol specification 3.4](https://www.haproxy.org/download/3.4/doc/proxy-protocol.txt).
It covers protocol versions 1 (text) and 2 (binary), carrying the original
client connection information across NAT and proxy layers.
This library is to be used in one of or both proxy clients and proxy servers that need to support said protocol.
Both protocol versions, 1 (text-based) and 2 (binary-based) are supported.
Use it on either side of a PROXY-aware hop: send headers with `Header.WriteTo`
or `Header.FormatUDPDatagram`, and receive them with `Listener`, `NewConn`,
`Read`, or `ParseUDPDatagram`. The stream APIs are for TCP and Unix streams;
UDP uses packet helpers because the spec requires a header in every datagram.
## Installation
```shell
$ go get -u github.com/pires/go-proxyproto
$ go get github.com/pires/go-proxyproto
```
## Examples
The fastest way to get started is the runnable programs under
[`examples/`](examples) and the API examples on
[pkg.go.dev](https://pkg.go.dev/github.com/pires/go-proxyproto#pkg-examples):
| Goal | Where to look |
| ---- | ------------- |
| Minimal client | [`examples/client`](examples/client) |
| Minimal server | [`examples/server`](examples/server) |
| HTTP server | [`examples/httpserver`](examples/httpserver) |
| Server + client over TLS (PROXY header before TLS) | [`examples/tlsserver`](examples/tlsserver), [`examples/tlsclient`](examples/tlsclient) |
| UDP receiver and sender | [`examples/udpserver`](examples/udpserver), [`examples/udpclient`](examples/udpclient) |
| UDP `net.PacketConn` wrapper pattern | [`examples/udppacketconn`](examples/udppacketconn) |
| `Listener`, `NewConn`, `Read`, UDP, and TLS API examples | [Package examples](https://pkg.go.dev/github.com/pires/go-proxyproto#pkg-examples) |
## Usage
### Client
Use the runnable examples above for complete programs. The core API shape is
small.
### Client side
```go
package main
header := proxyproto.HeaderProxyFromAddrs(1, sourceAddr, destinationAddr)
_, err := header.WriteTo(conn) // write the PROXY header before application data
```
import (
"io"
"log"
"net"
See [`examples/client`](examples/client) for a complete TCP client.
proxyproto "github.com/pires/go-proxyproto"
)
### Server side
func chkErr(err error) {
if err != nil {
log.Fatalf("Error: %s", err.Error())
}
}
```go
proxyListener := &proxyproto.Listener{Listener: ln}
conn, err := proxyListener.Accept()
// Connections must open with a PROXY header (the default policy is REQUIRE);
// conn.RemoteAddr() then reports the client address from that header.
```
func main() {
// Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
chkErr(err)
See [`examples/server`](examples/server) for a complete TCP server. For HTTP/1
and HTTP/2, see [`examples/httpserver`](examples/httpserver), which uses
[`helper/http2`](helper/http2) so one server can accept proxied HTTP/1 and HTTP/2
connections.
conn, err := net.DialTCP("tcp", nil, target)
chkErr(err)
> [!WARNING]
> The zero-value configuration requires the PROXY header but still honors it
> from any peer. It is not safe for listeners reachable by untrusted clients
> without a trusted-source policy. See [Security](#security).
defer conn.Close()
## Security
// Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
// have two conn's
header := &proxyproto.Header{
Version: 1,
Command: proxyproto.PROXY,
TransportProtocol: proxyproto.TCPv4,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP("10.1.1.1"),
Port: 1000,
},
DestinationAddr: &net.TCPAddr{
IP: net.ParseIP("20.2.2.2"),
Port: 2000,
},
}
// After the connection was created write the proxy headers first
_, err = header.WriteTo(conn)
chkErr(err)
// Then your data... e.g.:
_, err = io.WriteString(conn, "HELO")
chkErr(err)
The PROXY header replaces what your application sees as the client address, so
whoever is allowed to send one can spoof their origin. The spec (section 2)
says receivers "MUST not try to guess" whether the header is present, and
requires access filtering so only trusted proxies can use the protocol.
Important stream defaults and policies:
- With no policy configured, `Listener` and `NewConn` use
`proxyproto.DefaultPolicy`, which is `REQUIRE`. A connection that does not
open with a PROXY header fails its first I/O with `ErrNoProxyProtocol`, so
header presence is never guessed.
- `REQUIRE` still honors headers from any peer. Use it only when the listener
is reachable exclusively by trusted proxies, such as a private network
segment behind your load balancer.
- Deployments that need historical optional-header behavior can restore it
process-wide with `proxyproto.DefaultPolicy = proxyproto.USE`, per
connection with `WithPolicy(USE)`, or per listener with a policy returning
`USE`.
- For exposed listeners, restrict the senders with `TrustProxyHeaderFrom` or
`TrustProxyHeaderFromRanges`. Trusted peers must send a header; untrusted
peers are dropped by `Accept`. For example:
```go
proxyListener := &proxyproto.Listener{
Listener: ln,
// Connections from the load balancer must open with a PROXY header
// (REQUIRE); connections from any other source are dropped. For CIDR
// ranges, use TrustProxyHeaderFromRanges([]string{"10.0.0.0/24"}).
// For mixed traffic (e.g. optional headers from some sources), spell the
// two policies out with PolicyFromRanges(ranges, matched, unmatched).
ConnPolicy: proxyproto.TrustProxyHeaderFrom(net.ParseIP("10.0.0.10")),
}
```
### Server
For UDP, `ParseUDPDatagram` has no built-in trusted-source policy because it
only sees packet bytes. Check the `net.PacketConn.ReadFrom` sender address
yourself before trusting `header.SourceAddr`.
```go
package main
Related knobs are documented in the
[package docs](https://pkg.go.dev/github.com/pires/go-proxyproto):
`ReadHeaderTimeout` (default 10s), `MaxV2HeaderSize` (default 4KiB),
`V1AcceptIPv4InTCP6` (default off), and `Listener.ValidateHeader`.
import (
"log"
"net"
## UDP
proxyproto "github.com/pires/go-proxyproto"
)
The spec requires the header and proxied payload in the same UDP datagram, and
the receiver must parse the header independently for every datagram. `Listener`
and `Conn` cannot provide those semantics; use `ParseUDPDatagram` and
`Header.FormatUDPDatagram` with your own `net.PacketConn`. Headers with
`UDPv4`/`UDPv6` families carried over stream connections describe the proxied
protocol and remain fully supported.
func main() {
// Create a listener
addr := "localhost:9876"
list, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error())
}
- [`examples/udpserver`](examples/udpserver) and
[`examples/udpclient`](examples/udpclient) show direct per-datagram parsing
and formatting.
- [`examples/udppacketconn`](examples/udppacketconn) sketches a
`net.PacketConn` wrapper with reply routing through the relaying proxy.
- [`ExampleParseUDPDatagram`](https://pkg.go.dev/github.com/pires/go-proxyproto#example-ParseUDPDatagram)
and
[`ExampleHeader_FormatUDPDatagram`](https://pkg.go.dev/github.com/pires/go-proxyproto#example-Header.FormatUDPDatagram)
show the same APIs as package examples.
// Wrap listener in a proxyproto listener
proxyListener := &proxyproto.Listener{Listener: list}
defer proxyListener.Close()
The library deliberately does not export a `net.PacketConn` wrapper. A wrapper
that returns the client address from the header also needs an application-owned
client-to-proxy flow table for replies, including bounds, expiry, and spoofing
policy.
// Wait for a connection and accept it
conn, err := proxyListener.Accept()
defer conn.Close()
### TLS
// Print connection details
if conn.LocalAddr() == nil {
log.Fatal("couldn't retrieve local address")
}
log.Printf("local address: %q", conn.LocalAddr().String())
When combining PROXY protocol with TLS, match the wrapper order to the upstream
order. If the header is sent in cleartext before the handshake, put proxyproto
inside TLS: `tls.NewListener(&proxyproto.Listener{Listener: l}, tlsConfig)`.
If the header is sent inside the TLS session, decrypt first:
`&proxyproto.Listener{Listener: tls.NewListener(l, tlsConfig)}`. In both cases
`conn.RemoteAddr()` reports the client carried by the PROXY header.
if conn.RemoteAddr() == nil {
log.Fatal("couldn't retrieve remote address")
}
log.Printf("remote address: %q", conn.RemoteAddr().String())
}
```
### HTTP Server
```go
package main
import (
"net"
"net/http"
"time"
"github.com/pires/go-proxyproto"
)
func main() {
server := http.Server{
Addr: ":8080",
}
ln, err := net.Listen("tcp", server.Addr)
if err != nil {
panic(err)
}
proxyListener := &proxyproto.Listener{
Listener: ln,
ReadHeaderTimeout: 10 * time.Second,
}
defer proxyListener.Close()
server.Serve(proxyListener)
}
```
Runnable code lives in [`examples/tlsserver`](examples/tlsserver) and
[`examples/tlsclient`](examples/tlsclient). Package examples show both
orderings.
## Special notes
### AWS
AWS Network Load Balancer (NLB) does not push the PPV2 header until the client starts sending the data. This is a problem if your server speaks first. e.g. SMTP, FTP, SSH etc.
By default, NLB target group attribute `proxy_protocol_v2.client_to_server.header_placement` has the value `on_first_ack_with_payload`. You need to contact AWS support to change it to `on_first_ack`, instead.
Just to be clear, you need this fix only if your server is designed to speak first.
AWS Network Load Balancer (NLB) does not send the PROXY v2 header until the
client sends payload: the target group attribute
`proxy_protocol_v2.client_to_server.header_placement` defaults to
`on_first_ack_with_payload`. Server-first protocols such as SMTP, FTP, and SSH
fail in that mode; contact AWS support to change the attribute to
`on_first_ack` so the header arrives before the backend speaks.