├── .travis.yml ├── LICENSE ├── listen.go ├── doc.go ├── README.md ├── conn.go └── conn_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | - 1.9 6 | - tip 7 | 8 | script: go test -v . 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Leandro López 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /listen.go: -------------------------------------------------------------------------------- 1 | package viaproxy 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // Listen returns a net.Listener that will wrap Accept so it returns 8 | // net.Conn that know how to work with Proxy Protocol. 9 | func Listen(network, address string) (*Listener, error) { 10 | ln, err := net.Listen(network, address) 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | return &Listener{ln}, nil 16 | } 17 | 18 | // Listener is a wrap on net.Listener that returns wrapped Conn 19 | // objects. 20 | type Listener struct{ ln net.Listener } 21 | 22 | // Close stops listening on the TCP address. Already Accepted 23 | // connections are not closed. 24 | func (l *Listener) Close() error { return l.ln.Close() } 25 | 26 | // Addr returns the listener's network address, a *TCPAddr. The Addr 27 | // returned is shared by all invocations of Addr, so do not modify it. 28 | func (l *Listener) Addr() net.Addr { return l.ln.Addr() } 29 | 30 | // Accept implements the Accept method in the Listener interface; it 31 | // waits for the next call and returns a generic Conn. 32 | func (l *Listener) Accept() (net.Conn, error) { 33 | return l.AcceptFromProxy() 34 | } 35 | 36 | // AcceptFromProxy accepts the next incoming call and returns the new 37 | // connection. 38 | func (l *Listener) AcceptFromProxy() (*Conn, error) { 39 | cn, err := l.ln.Accept() 40 | if err != nil { 41 | return nil, err 42 | } 43 | return Wrap(cn) 44 | } 45 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package viaproxy provides the ability to manage connections that properly understand the 3 | Proxy Protocol defined by Willy Tarreau for HAProxy. 4 | 5 | Regular net.Conn structures will return the "wrong" RemoteAddr when used behind a proxy: 6 | the remote address informed will be the one of the proxy, not the one from the client 7 | initiating the connection to the proxy. This package adds a wrapper for regular net.Conn 8 | that checks for the existence of the proxy protocol line and if present, return a net.Conn 9 | that reports the customer address when calling RemoteAddr. This wrapped connection can be 10 | casted to *viaproxy.Conn to add access to an additional ProxyAddr method that will return 11 | the proxy's address. 12 | 13 | In order to use this extended connection type we can call Wrap on an existing connection: 14 | 15 | ln, err := net.Listen("tcp", *addr) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | for { 21 | cn, err := ln.Accept() 22 | if err != nil { 23 | log.Println("ln.Accept():", err) 24 | continue 25 | } 26 | 27 | pcn, err := viaproxy.Wrap(cn) 28 | if err != nil { 29 | log.Println("Wrap():", err) 30 | continue 31 | } 32 | 33 | log.Printf("remote address is: %v", pcn.RemoteAddr()) 34 | log.Printf("local address is: %v", pcn.LocalAddr()) 35 | log.Printf("proxy address is: %v", pcn.ProxyAddr()) 36 | pcn.Close() 37 | } 38 | 39 | Package viaproxy also provides a Listener struct that already returns viaproxy.Conn 40 | connections when calling AcceptFromProxy: 41 | 42 | ln, err := viaproxy.Listen("tcp", *addr) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | 47 | for { 48 | cn, err := ln.AcceptFromProxy() 49 | if err != nil { 50 | log.Println("ln.Accept():", err) 51 | continue 52 | } 53 | 54 | log.Printf("remote address is: %v", cn.RemoteAddr()) 55 | log.Printf("local address is: %v", cn.LocalAddr()) 56 | log.Printf("proxy address is: %v", cn.ProxyAddr()) 57 | cn.Close() 58 | } 59 | 60 | The Accept method in the Listener struct returns a generic net.Conn 61 | which can safely be casted to a viaproxy.Conn: 62 | 63 | ln, err := viaproxy.Listen("tcp", *addr) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | for { 69 | cn, err := ln.Accept() 70 | if err != nil { 71 | log.Println("ln.Accept():", err) 72 | continue 73 | } 74 | 75 | // The connection should be safe to be converted to a *viaproxy.Conn 76 | // structure. 77 | pcn := conn.(*viaproxy.Conn) 78 | log.Printf("remote address is: %v", pcn.RemoteAddr()) 79 | log.Printf("local address is: %v", pcn.LocalAddr()) 80 | log.Printf("proxy address is: %v", pcn.ProxyAddr()) 81 | pcn.Close() 82 | } 83 | 84 | Using viaproxy.Conn objects whenever a net.Conn is expected should be 85 | safe in all cases. If you encounter an issue please send a bug report 86 | to https://github.com/inkel/viaproxy/issues 87 | */ 88 | package viaproxy 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxy Protocol support for Go net.Conn 2 | 3 | [![GoDoc](https://godoc.org/github.com/inkel/viaproxy?status.svg)](https://godoc.org/github.com/inkel/viaproxy) [![Go Report Card](https://goreportcard.com/badge/github.com/inkel/viaproxy)](https://goreportcard.com/report/github.com/inkel/viaproxy) [![codebeat badge](https://codebeat.co/badges/341b5542-be1e-47ee-86f3-000fb98d3545)](https://codebeat.co/projects/github-com-inkel-viaproxy-master) 4 | 5 | Regular Go `net` doesn't support [Proxy Protocol](http://www.haproxy.com/blog/haproxy/proxy-protocol/) when being load balanced with this option enabled. This makes you loose the original remote address and will report the load balancer's address instead on `net.Conn.RemoteAddr()`. This package adds allows you to create `net.Conn` objects that know how to understand Proxy Protocol. 6 | 7 | You can read more about this in my [Proxy Protocol: what is it and how to use it with Go](https://inkel.github.io/posts/proxy-protocol/) article. 8 | 9 | ## Usage 10 | In your server, you can do the following: 11 | 12 | ```go 13 | ln, err := net.Listen("tcp", *addr) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | for { 19 | cn, err := ln.Accept() 20 | if err != nil { 21 | log.Println("ln.Accept():", err) 22 | continue 23 | } 24 | 25 | pcn, err := viaproxy.Wrap(cn) 26 | if err != nil { 27 | log.Println("Wrap():", err) 28 | continue 29 | } 30 | 31 | log.Printf("remote address is: %v", pcn.RemoteAddr()) 32 | log.Printf("local address is: %v", pcn.LocalAddr()) 33 | log.Printf("proxy address is: %v", pcn.ProxyAddr()) 34 | pcn.Close() 35 | } 36 | ``` 37 | 38 | Given that one can forget about this, you can also do the following: 39 | 40 | ```go 41 | ln, err := viaproxy.Listen("tcp", *addr) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | for { 47 | cn, err := ln.Accept() 48 | if err != nil { 49 | log.Println("ln.Accept():", err) 50 | continue 51 | } 52 | 53 | // The connection should be safe to be converted to a *viaproxy.Conn 54 | // structure. 55 | pcn := conn.(*viaproxy.Conn) 56 | log.Printf("remote address is: %v", pcn.RemoteAddr()) 57 | log.Printf("local address is: %v", pcn.LocalAddr()) 58 | log.Printf("proxy address is: %v", pcn.ProxyAddr()) 59 | pcn.Close() 60 | } 61 | ``` 62 | 63 | In this case, `Accept` returns a generic [`net.Conn`](https://golang.org/pkg/net/#Conn) object. If you want to directly use a `Conn` object (which satisfies the `net.Conn` interface), you can use `AcceptFromProxy` instead: 64 | 65 | ```go 66 | ln, err := viaproxy.Listen("tcp", *addr) 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | for { 72 | cn, err := ln.AcceptFromProxy() 73 | if err != nil { 74 | log.Println("ln.Accept():", err) 75 | continue 76 | } 77 | 78 | // The connection should be safe to be converted to a *viaproxy.Conn 79 | // structure. 80 | log.Printf("remote address is: %v", cn.RemoteAddr()) 81 | log.Printf("local address is: %v", cn.LocalAddr()) 82 | log.Printf("proxy address is: %v", cn.ProxyAddr()) 83 | cn.Close() 84 | } 85 | ``` 86 | 87 | ## Caveats 88 | * Only works with TCP connections. 89 | * Both endpoints of the connection **must** be compatible with proxy protocol. 90 | 91 | ## License 92 | See [LICENSE](LICENSE). 93 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package viaproxy 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "net" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // Wrap takes a net.Conn and returns a pointer to Conn that knows how to 14 | // properly identify the remote address if it comes via a proxy that 15 | // supports the Proxy Protocol. 16 | func Wrap(cn net.Conn) (*Conn, error) { 17 | c := &Conn{cn: cn, r: bufio.NewReader(cn)} 18 | if err := c.init(); err != nil { 19 | return nil, err 20 | } 21 | return c, nil 22 | } 23 | 24 | // Conn is an implementation of net.Conn interface for TCP connections that come 25 | // from a proxy that users the Proxy Protocol to communicate with the upstream 26 | // servers. 27 | type Conn struct { 28 | cn net.Conn 29 | r *bufio.Reader 30 | proxy net.Addr 31 | remote net.Addr 32 | } 33 | 34 | // ProxyAddr returns the proxy remote network address. 35 | func (c *Conn) ProxyAddr() net.Addr { return c.proxy } 36 | 37 | // RemoteAddr returns the remote network address. 38 | func (c *Conn) RemoteAddr() net.Addr { 39 | if c.remote != nil { 40 | return c.remote 41 | } 42 | return c.cn.RemoteAddr() 43 | } 44 | 45 | // LocalAddr returns the local network address. 46 | func (c *Conn) LocalAddr() net.Addr { return c.cn.LocalAddr() } 47 | 48 | // Read reads data from the connection. 49 | func (c *Conn) Read(b []byte) (int, error) { return c.r.Read(b) } 50 | 51 | // Close closes the connection. 52 | func (c *Conn) Close() error { return c.cn.Close() } 53 | 54 | // SetDeadline implements the Conn SetDeadline method. 55 | func (c *Conn) SetDeadline(t time.Time) error { return c.cn.SetDeadline(t) } 56 | 57 | // SetReadDeadline implements the Conn SetReadDeadline method. 58 | func (c *Conn) SetReadDeadline(t time.Time) error { return c.cn.SetReadDeadline(t) } 59 | 60 | // SetWriteDeadline implements the Conn SetWriteDeadline method. 61 | func (c *Conn) SetWriteDeadline(t time.Time) error { return c.cn.SetWriteDeadline(t) } 62 | 63 | // Write implements the Conn Write method. 64 | func (c *Conn) Write(b []byte) (int, error) { return c.cn.Write(b) } 65 | 66 | var unknown = []byte("UNKNOWN\r\n") 67 | 68 | func (c *Conn) init() error { 69 | // PROXY 70 | buf := make([]byte, 6) 71 | n, err := c.r.Read(buf) 72 | if err != nil { 73 | return err 74 | } 75 | if !bytes.Equal(buf, []byte("PROXY ")) { 76 | return errors.Errorf("invalid proxy protocol header prefix: %q", buf[:n]) 77 | } 78 | 79 | buf, err = c.r.Peek(len(unknown)) 80 | if err != nil { 81 | return errors.Wrap(err, "parsing proxy protocol header") 82 | } 83 | if bytes.Equal(buf, unknown) { 84 | _, err = c.r.Discard(len(unknown)) 85 | return err 86 | } 87 | 88 | // TCP4 || TCP6 89 | buf = make([]byte, 5) 90 | // This line cannot return error as the buffer of the *bufio.Reader already contains at least five characters from the call to Peek above. 91 | c.r.Read(buf) 92 | if !bytes.Equal([]byte("TCP4 "), buf) && !bytes.Equal([]byte("TCP6 "), buf) { 93 | return errors.Errorf("unrecognized protocol: %q", buf) 94 | } 95 | 96 | // CLIENT IP 97 | clientIP, err := c.readIP() 98 | if err != nil { 99 | return errors.Wrap(err, "cannot parse client IP") 100 | } 101 | 102 | // PROXY IP 103 | proxyIP, err := c.readIP() 104 | if err != nil { 105 | return errors.Wrap(err, "cannot parse proxy IP") 106 | } 107 | 108 | // CLIENT PORT 109 | clientPort, err := c.readPort(' ') 110 | if err != nil { 111 | return errors.Wrap(err, "cannot parse client port") 112 | } 113 | 114 | // PROXY PORT 115 | proxyPort, err := c.readPort('\r') 116 | if err != nil { 117 | return errors.Wrap(err, "cannot parse proxy port") 118 | } 119 | 120 | // Trailing 121 | b, err := c.r.ReadByte() 122 | if err != nil || b != '\n' { 123 | return errors.Wrap(err, "invalid trailing") 124 | } 125 | 126 | c.remote = &net.TCPAddr{IP: clientIP, Port: clientPort} 127 | c.proxy = &net.TCPAddr{IP: proxyIP, Port: proxyPort} 128 | 129 | return nil 130 | } 131 | 132 | func (c *Conn) readIP() (net.IP, error) { 133 | p, err := c.r.ReadString(' ') 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | ip := net.ParseIP(p[:len(p)-1]) 139 | if ip == nil { 140 | return nil, errors.Errorf("cannot parse IP %q", p) 141 | } 142 | 143 | return ip, nil 144 | } 145 | 146 | func (c *Conn) readPort(delim byte) (int, error) { 147 | p, err := c.r.ReadString(delim) 148 | if err != nil { 149 | return 0, err 150 | } 151 | 152 | port, err := strconv.Atoi(p[:len(p)-1]) 153 | if err != nil { 154 | return 0, err 155 | } 156 | 157 | return port, nil 158 | } 159 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package viaproxy_test 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "testing" 10 | 11 | "github.com/inkel/viaproxy" 12 | ) 13 | 14 | type conn struct { 15 | net.Conn 16 | data io.Reader 17 | } 18 | 19 | func (c *conn) Read(b []byte) (n int, err error) { return c.data.Read(b) } 20 | 21 | func (c *conn) LocalAddr() net.Addr { return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9876} } 22 | func (c *conn) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.ParseIP("10.0.1.2"), Port: 1234} } 23 | 24 | func testConn(data []byte) net.Conn { return &conn{data: bytes.NewReader(data)} } 25 | 26 | type testAddr string 27 | 28 | func (a testAddr) Network() string { return "tcp" } 29 | func (a testAddr) String() string { return string(a) } 30 | 31 | func equalAddr(a, b net.Addr) bool { 32 | return (a == nil && b == nil) || a.Network() == b.Network() && a.String() == b.String() 33 | } 34 | 35 | func TestWrap(t *testing.T) { 36 | cases := []struct { 37 | line, data []byte 38 | remoteIP string 39 | remotePort int 40 | proxy net.Addr 41 | err bool 42 | }{ 43 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 5678 1234\r\nfoo\r\nbar\r\n"), []byte("foo\r\nbar\r\n"), "192.168.1.20", 5678, &net.TCPAddr{IP: net.IPv4(10, 0, 0, 1), Port: 1234}, false}, 44 | {[]byte("PROXY TCP6 fe80::aede:48ff:fe00:1122 ::1 5678 1234\r\nfoo\r\nbar\r\n"), []byte("foo\r\nbar\r\n"), "fe80::aede:48ff:fe00:1122", 5678, &net.TCPAddr{IP: net.ParseIP("::1"), Port: 1234}, false}, 45 | {[]byte("PROXY UNKNOWN\r\nfoo\r\nbar\r\n"), []byte("foo\r\nbar\r\n"), "10.0.1.2", 1234, nil, false}, 46 | 47 | // Invalid proxy protocol lines 48 | {[]byte("GET / HTTP/1.0\r\n"), nil, "", -1, nil, true}, 49 | {[]byte("PROXY TCP5\r\n"), nil, "", -1, nil, true}, 50 | {[]byte("PROXY TCP4 192.168.X.20 10.0.0.1 5678 1234\r\n"), nil, "", -1, nil, true}, 51 | {[]byte("PROXY TCP4 192.168.1.20 10.X.0.1 5678 1234\r\n"), nil, "", -1, nil, true}, 52 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 567X 1234\r\n"), nil, "", -1, nil, true}, 53 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 5678 123X\r\n"), nil, "", -1, nil, true}, 54 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 5678\r\nfoo\r\nbar\r\n"), nil, "", -1, nil, true}, 55 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 5678 1234"), nil, "", -1, nil, true}, 56 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1 5678 1234\r"), nil, "", -1, nil, true}, 57 | {[]byte("PROXY TCP4 192.168.1.20 10.0.0.1"), nil, "", -1, nil, true}, 58 | {[]byte("PROXY TCP4 192.168.1.20"), nil, "", -1, nil, true}, 59 | {[]byte("PROXY TCP 192.168.1.20 10.0.0.1 5678 1234\r\n"), nil, "", -1, nil, true}, 60 | {[]byte("PROXY TCP\r\n192.168.1.20 10.0.0.1 5678 1234\r\n"), nil, "", -1, nil, true}, 61 | {[]byte("PROXY TCP4"), nil, "", -1, nil, true}, 62 | {[]byte(""), nil, "", -1, nil, true}, 63 | } 64 | 65 | for _, c := range cases { 66 | t.Run(string(c.line), func(t *testing.T) { 67 | cn, err := viaproxy.Wrap(testConn(c.line)) 68 | if c.err && err == nil { 69 | t.Fatal("expecting error, got nil") 70 | } 71 | if !c.err && err != nil { 72 | t.Fatalf("unexpected error: %v", err) 73 | } 74 | if cn == nil && c.err && err != nil { 75 | // no need to continue processing 76 | return 77 | } 78 | 79 | var remote net.Addr = &net.TCPAddr{IP: net.ParseIP(c.remoteIP), Port: c.remotePort} 80 | if !equalAddr(remote, cn.RemoteAddr()) { 81 | t.Errorf("expecting RemoteAddr() %v, got %v", remote, cn.RemoteAddr()) 82 | } 83 | 84 | data, err := ioutil.ReadAll(cn) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | if !bytes.Equal(c.data, data) { 90 | t.Errorf("expecting data %q, got %q", c.data, data) 91 | } 92 | 93 | if !equalAddr(cn.ProxyAddr(), c.proxy) { 94 | t.Errorf("expecting ProxyAddr() %v, got %v", c.proxy, cn.ProxyAddr()) 95 | } 96 | }) 97 | } 98 | } 99 | 100 | func ExampleListener_Accept() { 101 | // Listen on TCP port 8080 for connections coming from a proxy that sends 102 | // the Proxy Protocol header. 103 | l, err := viaproxy.Listen("tcp", ":8080") 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | defer l.Close() 108 | 109 | for { 110 | // Wait for a connection. 111 | conn, err := l.Accept() 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | 116 | // The connection should be safe to be converted to a *viaproxy.Conn 117 | // structure. 118 | cn := conn.(*viaproxy.Conn) 119 | log.Printf("remote address is: %v", cn.RemoteAddr()) 120 | log.Printf("local address is: %v", cn.LocalAddr()) 121 | log.Printf("proxy address is: %v", cn.ProxyAddr()) 122 | cn.Close() 123 | } 124 | } 125 | 126 | func ExampleListener_AcceptFromProxy() { 127 | // Listen on TCP port 8080 for connections coming from a proxy that sends 128 | // the Proxy Protocol header. 129 | l, err := viaproxy.Listen("tcp", ":8080") 130 | if err != nil { 131 | log.Fatal(err) 132 | } 133 | defer l.Close() 134 | 135 | for { 136 | // Wait for a connection. 137 | cn, err := l.AcceptFromProxy() 138 | if err != nil { 139 | log.Fatal(err) 140 | } 141 | 142 | log.Printf("remote address is: %v", cn.RemoteAddr()) 143 | log.Printf("local address is: %v", cn.LocalAddr()) 144 | log.Printf("proxy address is: %v", cn.ProxyAddr()) 145 | cn.Close() 146 | } 147 | } 148 | 149 | func ExampleWrap() { 150 | // Listen on TCP port 8080 for connections coming from a proxy that sends 151 | // the Proxy Protocol header. 152 | l, err := net.Listen("tcp", ":8080") 153 | if err != nil { 154 | log.Fatal(err) 155 | } 156 | defer l.Close() 157 | 158 | for { 159 | // Wait for a connection. 160 | cn, err := l.Accept() 161 | if err != nil { 162 | log.Fatal(err) 163 | } 164 | 165 | pcn, err := viaproxy.Wrap(cn) 166 | if err != nil { 167 | log.Fatal(err) 168 | } 169 | 170 | log.Printf("remote address is: %v", pcn.RemoteAddr()) 171 | log.Printf("local address is: %v", pcn.LocalAddr()) 172 | log.Printf("proxy address is: %v", pcn.ProxyAddr()) 173 | pcn.Close() 174 | } 175 | } 176 | --------------------------------------------------------------------------------