[822] | 1 | # go-proxyproto
|
---|
| 2 |
|
---|
| 3 | [](https://github.com/pires/go-proxyproto/actions)
|
---|
| 4 | [](https://coveralls.io/github/pires/go-proxyproto?branch=master)
|
---|
| 5 | [](https://goreportcard.com/report/github.com/pires/go-proxyproto)
|
---|
| 6 | [](https://pkg.go.dev/github.com/pires/go-proxyproto?tab=doc)
|
---|
| 7 |
|
---|
| 8 |
|
---|
| 9 | A Go library implementation of the [PROXY protocol, versions 1 and 2](https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt),
|
---|
| 10 | which provides, as per specification:
|
---|
| 11 | > (...) a convenient way to safely transport connection
|
---|
| 12 | > information such as a client's address across multiple layers of NAT or TCP
|
---|
| 13 | > proxies. It is designed to require little changes to existing components and
|
---|
| 14 | > to limit the performance impact caused by the processing of the transported
|
---|
| 15 | > information.
|
---|
| 16 |
|
---|
| 17 | This library is to be used in one of or both proxy clients and proxy servers that need to support said protocol.
|
---|
| 18 | Both protocol versions, 1 (text-based) and 2 (binary-based) are supported.
|
---|
| 19 |
|
---|
| 20 | ## Installation
|
---|
| 21 |
|
---|
| 22 | ```shell
|
---|
| 23 | $ go get -u github.com/pires/go-proxyproto
|
---|
| 24 | ```
|
---|
| 25 |
|
---|
| 26 | ## Usage
|
---|
| 27 |
|
---|
| 28 | ### Client
|
---|
| 29 |
|
---|
| 30 | ```go
|
---|
| 31 | package main
|
---|
| 32 |
|
---|
| 33 | import (
|
---|
| 34 | "io"
|
---|
| 35 | "log"
|
---|
| 36 | "net"
|
---|
| 37 |
|
---|
| 38 | proxyproto "github.com/pires/go-proxyproto"
|
---|
| 39 | )
|
---|
| 40 |
|
---|
| 41 | func chkErr(err error) {
|
---|
| 42 | if err != nil {
|
---|
| 43 | log.Fatalf("Error: %s", err.Error())
|
---|
| 44 | }
|
---|
| 45 | }
|
---|
| 46 |
|
---|
| 47 | func main() {
|
---|
| 48 | // Dial some proxy listener e.g. https://github.com/mailgun/proxyproto
|
---|
| 49 | target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:2319")
|
---|
| 50 | chkErr(err)
|
---|
| 51 |
|
---|
| 52 | conn, err := net.DialTCP("tcp", nil, target)
|
---|
| 53 | chkErr(err)
|
---|
| 54 |
|
---|
| 55 | defer conn.Close()
|
---|
| 56 |
|
---|
| 57 | // Create a proxyprotocol header or use HeaderProxyFromAddrs() if you
|
---|
| 58 | // have two conn's
|
---|
| 59 | header := &proxyproto.Header{
|
---|
| 60 | Version: 1,
|
---|
| 61 | Command: proxyproto.PROXY,
|
---|
| 62 | TransportProtocol: proxyproto.TCPv4,
|
---|
| 63 | SourceAddr: &net.TCPAddr{
|
---|
| 64 | IP: net.ParseIP("10.1.1.1"),
|
---|
| 65 | Port: 1000,
|
---|
| 66 | },
|
---|
| 67 | DestinationAddr: &net.TCPAddr{
|
---|
| 68 | IP: net.ParseIP("20.2.2.2"),
|
---|
| 69 | Port: 2000,
|
---|
| 70 | },
|
---|
| 71 | }
|
---|
| 72 | // After the connection was created write the proxy headers first
|
---|
| 73 | _, err = header.WriteTo(conn)
|
---|
| 74 | chkErr(err)
|
---|
| 75 | // Then your data... e.g.:
|
---|
| 76 | _, err = io.WriteString(conn, "HELO")
|
---|
| 77 | chkErr(err)
|
---|
| 78 | }
|
---|
| 79 | ```
|
---|
| 80 |
|
---|
| 81 | ### Server
|
---|
| 82 |
|
---|
| 83 | ```go
|
---|
| 84 | package main
|
---|
| 85 |
|
---|
| 86 | import (
|
---|
| 87 | "log"
|
---|
| 88 | "net"
|
---|
| 89 |
|
---|
| 90 | proxyproto "github.com/pires/go-proxyproto"
|
---|
| 91 | )
|
---|
| 92 |
|
---|
| 93 | func main() {
|
---|
| 94 | // Create a listener
|
---|
| 95 | addr := "localhost:9876"
|
---|
| 96 | list, err := net.Listen("tcp", addr)
|
---|
| 97 | if err != nil {
|
---|
| 98 | log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error())
|
---|
| 99 | }
|
---|
| 100 |
|
---|
| 101 | // Wrap listener in a proxyproto listener
|
---|
| 102 | proxyListener := &proxyproto.Listener{Listener: list}
|
---|
| 103 | defer proxyListener.Close()
|
---|
| 104 |
|
---|
| 105 | // Wait for a connection and accept it
|
---|
| 106 | conn, err := proxyListener.Accept()
|
---|
| 107 | defer conn.Close()
|
---|
| 108 |
|
---|
| 109 | // Print connection details
|
---|
| 110 | if conn.LocalAddr() == nil {
|
---|
| 111 | log.Fatal("couldn't retrieve local address")
|
---|
| 112 | }
|
---|
| 113 | log.Printf("local address: %q", conn.LocalAddr().String())
|
---|
| 114 |
|
---|
| 115 | if conn.RemoteAddr() == nil {
|
---|
| 116 | log.Fatal("couldn't retrieve remote address")
|
---|
| 117 | }
|
---|
| 118 | log.Printf("remote address: %q", conn.RemoteAddr().String())
|
---|
| 119 | }
|
---|
| 120 | ```
|
---|
| 121 |
|
---|
| 122 | ### HTTP Server
|
---|
| 123 | ```go
|
---|
| 124 | package main
|
---|
| 125 |
|
---|
| 126 | import (
|
---|
| 127 | "net"
|
---|
| 128 | "net/http"
|
---|
| 129 | "time"
|
---|
| 130 |
|
---|
| 131 | "github.com/pires/go-proxyproto"
|
---|
| 132 | )
|
---|
| 133 |
|
---|
| 134 | func main() {
|
---|
| 135 | server := http.Server{
|
---|
| 136 | Addr: ":8080",
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | ln, err := net.Listen("tcp", server.Addr)
|
---|
| 140 | if err != nil {
|
---|
| 141 | panic(err)
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | proxyListener := &proxyproto.Listener{
|
---|
| 145 | Listener: ln,
|
---|
| 146 | ReadHeaderTimeout: 10 * time.Second,
|
---|
| 147 | }
|
---|
| 148 | defer proxyListener.Close()
|
---|
| 149 |
|
---|
| 150 | server.Serve(proxyListener)
|
---|
| 151 | }
|
---|
| 152 | ```
|
---|
| 153 |
|
---|
| 154 | ## Special notes
|
---|
| 155 |
|
---|
| 156 | ### AWS
|
---|
| 157 |
|
---|
| 158 | 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.
|
---|
| 159 |
|
---|
| 160 | 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.
|
---|
| 161 |
|
---|
| 162 | Just to be clear, you need this fix only if your server is designed to speak first.
|
---|