|
@@ -5,7 +5,9 @@ package utils
|
5
|
5
|
|
6
|
6
|
import (
|
7
|
7
|
"crypto/tls"
|
|
8
|
+ "encoding/binary"
|
8
|
9
|
"errors"
|
|
10
|
+ "io"
|
9
|
11
|
"net"
|
10
|
12
|
"strings"
|
11
|
13
|
"sync"
|
|
@@ -18,7 +20,7 @@ const (
|
18
|
20
|
// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
19
|
21
|
// "a 108-byte buffer is always enough to store all the line and a trailing zero
|
20
|
22
|
// for string processing."
|
21
|
|
- maxProxyLineLen = 107
|
|
23
|
+ maxProxyLineLenV1 = 107
|
22
|
24
|
)
|
23
|
25
|
|
24
|
26
|
// XXX implement net.Error with a Temporary() method that returns true;
|
|
@@ -56,41 +58,88 @@ type ListenerConfig struct {
|
56
|
58
|
WebSocket bool
|
57
|
59
|
}
|
58
|
60
|
|
59
|
|
-// read a PROXY line one byte at a time, to ensure we don't read anything beyond
|
60
|
|
-// that into a buffer, which would break the TLS handshake
|
61
|
|
-func readRawProxyLine(conn net.Conn, deadline time.Duration) (result string) {
|
|
61
|
+// read a PROXY header (either v1 or v2), ensuring we don't read anything beyond
|
|
62
|
+// the header into a buffer (this would break the TLS handshake)
|
|
63
|
+func readRawProxyLine(conn net.Conn, deadline time.Duration) (result []byte, err error) {
|
62
|
64
|
// normally this is covered by ping timeouts, but we're doing this outside
|
63
|
65
|
// of the normal client goroutine:
|
64
|
66
|
conn.SetDeadline(time.Now().Add(deadline))
|
65
|
67
|
defer conn.SetDeadline(time.Time{})
|
66
|
68
|
|
67
|
|
- var buf [maxProxyLineLen]byte
|
68
|
|
- oneByte := make([]byte, 1)
|
69
|
|
- i := 0
|
70
|
|
- for i < maxProxyLineLen {
|
71
|
|
- n, err := conn.Read(oneByte)
|
|
69
|
+ // read the first 16 bytes of the proxy header
|
|
70
|
+ buf := make([]byte, 16, maxProxyLineLenV1)
|
|
71
|
+ _, err = io.ReadFull(conn, buf)
|
|
72
|
+ if err != nil {
|
|
73
|
+ return
|
|
74
|
+ }
|
|
75
|
+
|
|
76
|
+ switch buf[0] {
|
|
77
|
+ case 'P':
|
|
78
|
+ // PROXY v1: starts with "PROXY"
|
|
79
|
+ return readRawProxyLineV1(conn, buf)
|
|
80
|
+ case '\r':
|
|
81
|
+ // PROXY v2: starts with "\r\n\r\n"
|
|
82
|
+ return readRawProxyLineV2(conn, buf)
|
|
83
|
+ default:
|
|
84
|
+ return nil, ErrBadProxyLine
|
|
85
|
+ }
|
|
86
|
+}
|
|
87
|
+
|
|
88
|
+func readRawProxyLineV1(conn net.Conn, buf []byte) (result []byte, err error) {
|
|
89
|
+ for {
|
|
90
|
+ i := len(buf)
|
|
91
|
+ if i >= maxProxyLineLenV1 {
|
|
92
|
+ return nil, ErrBadProxyLine // did not find \r\n, fail
|
|
93
|
+ }
|
|
94
|
+ // prepare a single byte of free space, then read into it
|
|
95
|
+ buf = buf[0 : i+1]
|
|
96
|
+ _, err = io.ReadFull(conn, buf[i:])
|
72
|
97
|
if err != nil {
|
73
|
|
- return
|
74
|
|
- } else if n == 1 {
|
75
|
|
- buf[i] = oneByte[0]
|
76
|
|
- if buf[i] == '\n' {
|
77
|
|
- candidate := string(buf[0 : i+1])
|
78
|
|
- if strings.HasPrefix(candidate, "PROXY") {
|
79
|
|
- return candidate
|
80
|
|
- } else {
|
81
|
|
- return
|
82
|
|
- }
|
83
|
|
- }
|
84
|
|
- i += 1
|
|
98
|
+ return nil, err
|
|
99
|
+ }
|
|
100
|
+ if buf[i] == '\n' {
|
|
101
|
+ return buf, nil
|
85
|
102
|
}
|
86
|
103
|
}
|
|
104
|
+}
|
|
105
|
+
|
|
106
|
+func readRawProxyLineV2(conn net.Conn, buf []byte) (result []byte, err error) {
|
|
107
|
+ // "The 15th and 16th bytes is the address length in bytes in network endian order."
|
|
108
|
+ addrLen := int(binary.BigEndian.Uint16(buf[14:16]))
|
|
109
|
+ if addrLen == 0 {
|
|
110
|
+ return buf[0:16], nil
|
|
111
|
+ } else if addrLen <= cap(buf)-16 {
|
|
112
|
+ buf = buf[0 : 16+addrLen]
|
|
113
|
+ } else {
|
|
114
|
+ // proxy source is unix domain, we don't really handle this
|
|
115
|
+ buf2 := make([]byte, 16+addrLen)
|
|
116
|
+ copy(buf2[0:16], buf[0:16])
|
|
117
|
+ buf = buf2
|
|
118
|
+ }
|
|
119
|
+ _, err = io.ReadFull(conn, buf[16:16+addrLen])
|
|
120
|
+ if err != nil {
|
|
121
|
+ return
|
|
122
|
+ }
|
|
123
|
+ return buf[0 : 16+addrLen], nil
|
|
124
|
+}
|
87
|
125
|
|
88
|
|
- // no \r\n, fail out
|
89
|
|
- return
|
|
126
|
+// ParseProxyLine parses a PROXY protocol (v1 or v2) line and returns the remote IP.
|
|
127
|
+func ParseProxyLine(line []byte) (ip net.IP, err error) {
|
|
128
|
+ if len(line) == 0 {
|
|
129
|
+ return nil, ErrBadProxyLine
|
|
130
|
+ }
|
|
131
|
+ switch line[0] {
|
|
132
|
+ case 'P':
|
|
133
|
+ return ParseProxyLineV1(string(line))
|
|
134
|
+ case '\r':
|
|
135
|
+ return parseProxyLineV2(line)
|
|
136
|
+ default:
|
|
137
|
+ return nil, ErrBadProxyLine
|
|
138
|
+ }
|
90
|
139
|
}
|
91
|
140
|
|
92
|
|
-// ParseProxyLine parses a PROXY protocol (v1) line and returns the remote IP.
|
93
|
|
-func ParseProxyLine(line string) (ip net.IP, err error) {
|
|
141
|
+// ParseProxyLineV1 parses a PROXY protocol (v1) line and returns the remote IP.
|
|
142
|
+func ParseProxyLineV1(line string) (ip net.IP, err error) {
|
94
|
143
|
params := strings.Fields(line)
|
95
|
144
|
if len(params) != 6 || params[0] != "PROXY" {
|
96
|
145
|
return nil, ErrBadProxyLine
|
|
@@ -102,6 +151,62 @@ func ParseProxyLine(line string) (ip net.IP, err error) {
|
102
|
151
|
return ip.To16(), nil
|
103
|
152
|
}
|
104
|
153
|
|
|
154
|
+func parseProxyLineV2(line []byte) (ip net.IP, err error) {
|
|
155
|
+ if len(line) < 16 {
|
|
156
|
+ return nil, ErrBadProxyLine
|
|
157
|
+ }
|
|
158
|
+ // this doesn't allocate
|
|
159
|
+ if string(line[:12]) != "\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a" {
|
|
160
|
+ return nil, ErrBadProxyLine
|
|
161
|
+ }
|
|
162
|
+ // "The next byte (the 13th one) is the protocol version and command."
|
|
163
|
+ versionCmd := line[12]
|
|
164
|
+ // "The highest four bits contains the version [....] it must always be sent as \x2"
|
|
165
|
+ if (versionCmd >> 4) != 2 {
|
|
166
|
+ return nil, ErrBadProxyLine
|
|
167
|
+ }
|
|
168
|
+ // "The lowest four bits represents the command"
|
|
169
|
+ switch versionCmd & 0x0f {
|
|
170
|
+ case 0:
|
|
171
|
+ return nil, nil // LOCAL command
|
|
172
|
+ case 1:
|
|
173
|
+ // PROXY command, continue below
|
|
174
|
+ default:
|
|
175
|
+ // "Receivers must drop connections presenting unexpected values here"
|
|
176
|
+ return nil, ErrBadProxyLine
|
|
177
|
+ }
|
|
178
|
+
|
|
179
|
+ var addrLen int
|
|
180
|
+ // "The 14th byte contains the transport protocol and address family."
|
|
181
|
+ protoAddr := line[13]
|
|
182
|
+ // "The highest 4 bits contain the address family"
|
|
183
|
+ switch protoAddr >> 4 {
|
|
184
|
+ case 1:
|
|
185
|
+ addrLen = 4 // AF_INET
|
|
186
|
+ case 2:
|
|
187
|
+ addrLen = 16 // AF_INET6
|
|
188
|
+ default:
|
|
189
|
+ return nil, nil // AF_UNSPEC or AF_UNIX, either way there's no IP address
|
|
190
|
+ }
|
|
191
|
+
|
|
192
|
+ // header, source and destination address, two 16-bit port numbers:
|
|
193
|
+ expectedLen := 16 + 2*addrLen + 4
|
|
194
|
+ if len(line) < expectedLen {
|
|
195
|
+ return nil, ErrBadProxyLine
|
|
196
|
+ }
|
|
197
|
+
|
|
198
|
+ // "Starting from the 17th byte, addresses are presented in network byte order.
|
|
199
|
+ // The address order is always the same :
|
|
200
|
+ // - source layer 3 address in network byte order [...]"
|
|
201
|
+ if addrLen == 4 {
|
|
202
|
+ ip = net.IP(line[16 : 16+addrLen]).To16()
|
|
203
|
+ } else {
|
|
204
|
+ ip = make(net.IP, addrLen)
|
|
205
|
+ copy(ip, line[16:16+addrLen])
|
|
206
|
+ }
|
|
207
|
+ return ip, nil
|
|
208
|
+}
|
|
209
|
+
|
105
|
210
|
/// WrappedConn is a net.Conn with some additional data stapled to it;
|
106
|
211
|
// the proxied IP, if one was read via the PROXY protocol, and the listener
|
107
|
212
|
// configuration.
|
|
@@ -161,8 +266,10 @@ func (rl *ReloadableListener) Accept() (conn net.Conn, err error) {
|
161
|
266
|
// but that's OK because this listener *requires* a PROXY line,
|
162
|
267
|
// therefore it must be used with proxies that always send the line
|
163
|
268
|
// and we won't get slowloris'ed waiting for the client response
|
164
|
|
- proxyLine := readRawProxyLine(conn, config.ProxyDeadline)
|
165
|
|
- proxiedIP, err = ParseProxyLine(proxyLine)
|
|
269
|
+ proxyLine, err := readRawProxyLine(conn, config.ProxyDeadline)
|
|
270
|
+ if err == nil {
|
|
271
|
+ proxiedIP, err = ParseProxyLine(proxyLine)
|
|
272
|
+ }
|
166
|
273
|
if err != nil {
|
167
|
274
|
conn.Close()
|
168
|
275
|
return nil, err
|