Merge branch 'debian/experimental' into debian/sid
Pirate Praveen
10 months ago
118 | 118 | } |
119 | 119 | ``` |
120 | 120 | |
121 | ### HTTP Server | |
122 | ```go | |
123 | package main | |
124 | ||
125 | import ( | |
126 | "net" | |
127 | "net/http" | |
128 | "time" | |
129 | ||
130 | "github.com/pires/go-proxyproto" | |
131 | ) | |
132 | ||
133 | func main() { | |
134 | server := http.Server{ | |
135 | Addr: ":8080", | |
136 | } | |
137 | ||
138 | ln, err := net.Listen("tcp", server.Addr) | |
139 | if err != nil { | |
140 | panic(err) | |
141 | } | |
142 | ||
143 | proxyListener := &proxyproto.Listener{ | |
144 | Listener: ln, | |
145 | ReadHeaderTimeout: 10 * time.Second, | |
146 | } | |
147 | defer proxyListener.Close() | |
148 | ||
149 | server.Serve(proxyListener) | |
150 | } | |
151 | ``` | |
152 | ||
121 | 153 | ## Special notes |
122 | 154 | |
123 | 155 | ### AWS |
0 | golang-github-pires-go-proxyproto (0.6.0-1) experimental; urgency=medium | |
1 | ||
2 | * Team upload. | |
3 | * New upstream release 0.6.0 | |
4 | ||
5 | -- Pirate Praveen <praveen@debian.org> Thu, 11 Nov 2021 20:47:11 +0530 | |
6 | ||
0 | 7 | golang-github-pires-go-proxyproto (0.4.2-3) unstable; urgency=medium |
1 | 8 | |
2 | 9 | * Team upload |
0 | From: "Iestyn C. Elfick" <dev@chimera.dev> | |
1 | Date: Wed, 3 Mar 2021 01:45:02 +0000 | |
2 | Subject: add tests proving issue #69 | |
3 | ||
4 | (cherry picked from commit ac2f466ac556f4a0e21f33252fb63965c0853a58) | |
5 | --- | |
6 | header.go | 3 +- | |
7 | header_test.go | 11 ++-- | |
8 | v1_test.go | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- | |
9 | 3 files changed, 167 insertions(+), 7 deletions(-) | |
10 | ||
11 | diff --git a/header.go b/header.go | |
12 | index db877e8..fd67e17 100644 | |
13 | --- a/header.go | |
14 | +++ b/header.go | |
15 | @@ -16,7 +16,8 @@ var ( | |
16 | SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'} | |
17 | SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'} | |
18 | ||
19 | - ErrLineMustEndWithCrlf = errors.New("proxyproto: header is invalid, must end with \\r\\n") | |
20 | + ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less") | |
21 | + ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n") | |
22 | ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command") | |
23 | ErrCantReadAddressFamilyAndProtocol = errors.New("proxyproto: can't read address family or protocol") | |
24 | ErrCantReadLength = errors.New("proxyproto: can't read length") | |
25 | diff --git a/header_test.go b/header_test.go | |
26 | index 5385369..9b8662a 100644 | |
27 | --- a/header_test.go | |
28 | +++ b/header_test.go | |
29 | @@ -13,11 +13,12 @@ import ( | |
30 | // Stuff to be used in both versions tests. | |
31 | ||
32 | const ( | |
33 | - NO_PROTOCOL = "There is no spoon" | |
34 | - IP4_ADDR = "127.0.0.1" | |
35 | - IP6_ADDR = "::1" | |
36 | - PORT = 65533 | |
37 | - INVALID_PORT = 99999 | |
38 | + NO_PROTOCOL = "There is no spoon" | |
39 | + IP4_ADDR = "127.0.0.1" | |
40 | + IP6_ADDR = "::1" | |
41 | + IP6_LONG_ADDR = "1234:5678:9abc:def0:cafe:babe:dead:2bad" | |
42 | + PORT = 65533 | |
43 | + INVALID_PORT = 99999 | |
44 | ) | |
45 | ||
46 | var ( | |
47 | diff --git a/v1_test.go b/v1_test.go | |
48 | index 8d6dab1..1ee218f 100644 | |
49 | --- a/v1_test.go | |
50 | +++ b/v1_test.go | |
51 | @@ -3,19 +3,25 @@ package proxyproto | |
52 | import ( | |
53 | "bufio" | |
54 | "bytes" | |
55 | + "io" | |
56 | + "net" | |
57 | "strconv" | |
58 | "strings" | |
59 | "testing" | |
60 | + "time" | |
61 | ) | |
62 | ||
63 | var ( | |
64 | IPv4AddressesAndPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) | |
65 | IPv4AddressesAndInvalidPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(INVALID_PORT), strconv.Itoa(INVALID_PORT)}, separator) | |
66 | IPv6AddressesAndPorts = strings.Join([]string{IP6_ADDR, IP6_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) | |
67 | + IPv6LongAddressesAndPorts = strings.Join([]string{IP6_LONG_ADDR, IP6_LONG_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) | |
68 | ||
69 | fixtureTCP4V1 = "PROXY TCP4 " + IPv4AddressesAndPorts + crlf + "GET /" | |
70 | fixtureTCP6V1 = "PROXY TCP6 " + IPv6AddressesAndPorts + crlf + "GET /" | |
71 | ||
72 | + fixtureTCP6V1Overflow = "PROXY TCP6 " + IPv6LongAddressesAndPorts | |
73 | + | |
74 | fixtureUnknown = "PROXY UNKNOWN" + crlf | |
75 | fixtureUnknownWithAddresses = "PROXY UNKNOWN " + IPv4AddressesAndInvalidPorts + crlf | |
76 | ) | |
77 | @@ -75,13 +81,18 @@ var invalidParseV1Tests = []struct { | |
78 | reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndInvalidPorts + crlf)), | |
79 | expectedError: ErrInvalidPortNumber, | |
80 | }, | |
81 | + { | |
82 | + desc: "header too long", | |
83 | + reader: newBufioReader([]byte("PROXY UNKNOWN " + IPv6LongAddressesAndPorts + " " + crlf)), | |
84 | + expectedError: ErrVersion1HeaderTooLong, | |
85 | + }, | |
86 | } | |
87 | ||
88 | func TestReadV1Invalid(t *testing.T) { | |
89 | for _, tt := range invalidParseV1Tests { | |
90 | t.Run(tt.desc, func(t *testing.T) { | |
91 | if _, err := Read(tt.reader); err != tt.expectedError { | |
92 | - t.Fatalf("expected %s, actual %s", tt.expectedError, err.Error()) | |
93 | + t.Fatalf("expected %s, actual %v", tt.expectedError, err) | |
94 | } | |
95 | }) | |
96 | } | |
97 | @@ -175,3 +186,150 @@ func TestWriteV1Valid(t *testing.T) { | |
98 | }) | |
99 | } | |
100 | } | |
101 | + | |
102 | +// Tests for parseVersion1 overflow - issue #69. | |
103 | + | |
104 | +type dataSource struct { | |
105 | + NBytes int | |
106 | + NRead int | |
107 | +} | |
108 | + | |
109 | +func (ds *dataSource) Read(b []byte) (int, error) { | |
110 | + if ds.NRead >= ds.NBytes { | |
111 | + return 0, io.EOF | |
112 | + } | |
113 | + avail := ds.NBytes - ds.NRead | |
114 | + if len(b) < avail { | |
115 | + avail = len(b) | |
116 | + } | |
117 | + for i := 0; i < avail; i++ { | |
118 | + b[i] = 0x20 | |
119 | + } | |
120 | + ds.NRead += avail | |
121 | + return avail, nil | |
122 | +} | |
123 | + | |
124 | +func TestParseVersion1Overflow(t *testing.T) { | |
125 | + ds := &dataSource{} | |
126 | + reader := bufio.NewReader(ds) | |
127 | + bufSize := reader.Size() | |
128 | + ds.NBytes = bufSize * 16 | |
129 | + parseVersion1(reader) | |
130 | + if ds.NRead > bufSize { | |
131 | + t.Fatalf("read: expected max %d bytes, actual %d\n", bufSize, ds.NRead) | |
132 | + } | |
133 | +} | |
134 | + | |
135 | +func listen(t *testing.T) *Listener { | |
136 | + l, err := net.Listen("tcp", "127.0.0.1:0") | |
137 | + if err != nil { | |
138 | + t.Fatalf("listen: %v", err) | |
139 | + } | |
140 | + return &Listener{Listener: l} | |
141 | +} | |
142 | + | |
143 | +func client(t *testing.T, addr, header string, length int, terminate bool, wait time.Duration, done chan struct{}) { | |
144 | + c, err := net.Dial("tcp", addr) | |
145 | + if err != nil { | |
146 | + t.Fatalf("dial: %v", err) | |
147 | + } | |
148 | + defer c.Close() | |
149 | + | |
150 | + if terminate && length < 2 { | |
151 | + length = 2 | |
152 | + } | |
153 | + | |
154 | + buf := make([]byte, len(header)+length) | |
155 | + copy(buf, []byte(header)) | |
156 | + for i := 0; i < length-2; i++ { | |
157 | + buf[i+len(header)] = 0x20 | |
158 | + } | |
159 | + if terminate { | |
160 | + copy(buf[len(header)+length-2:], []byte(crlf)) | |
161 | + } | |
162 | + | |
163 | + n, err := c.Write(buf) | |
164 | + if err != nil { | |
165 | + t.Fatalf("write: %v", err) | |
166 | + } | |
167 | + if n != len(buf) { | |
168 | + t.Fatalf("write; short write") | |
169 | + } | |
170 | + | |
171 | + time.Sleep(wait) | |
172 | + close(done) | |
173 | +} | |
174 | + | |
175 | +func TestVersion1Overflow(t *testing.T) { | |
176 | + done := make(chan struct{}) | |
177 | + | |
178 | + l := listen(t) | |
179 | + go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, true, 10*time.Second, done) | |
180 | + | |
181 | + c, err := l.Accept() | |
182 | + if err != nil { | |
183 | + t.Fatalf("accept: %v", err) | |
184 | + } | |
185 | + | |
186 | + b := []byte{} | |
187 | + _, err = c.Read(b) | |
188 | + if err == nil { | |
189 | + t.Fatalf("net.Conn: no error reported for oversized header") | |
190 | + } | |
191 | +} | |
192 | + | |
193 | +func TestVersion1SlowLoris(t *testing.T) { | |
194 | + done := make(chan struct{}) | |
195 | + timeout := make(chan error) | |
196 | + | |
197 | + l := listen(t) | |
198 | + go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 0, false, 10*time.Second, done) | |
199 | + | |
200 | + c, err := l.Accept() | |
201 | + if err != nil { | |
202 | + t.Fatalf("accept: %v", err) | |
203 | + } | |
204 | + | |
205 | + go func() { | |
206 | + b := []byte{} | |
207 | + _, err = c.Read(b) | |
208 | + timeout <- err | |
209 | + }() | |
210 | + | |
211 | + select { | |
212 | + case <-done: | |
213 | + t.Fatalf("net.Conn: reader still blocked after 10 seconds") | |
214 | + case err := <-timeout: | |
215 | + if err == nil { | |
216 | + t.Fatalf("net.Conn: no error reported for incomplete header") | |
217 | + } | |
218 | + } | |
219 | +} | |
220 | + | |
221 | +func TestVersion1SlowLorisOverflow(t *testing.T) { | |
222 | + done := make(chan struct{}) | |
223 | + timeout := make(chan error) | |
224 | + | |
225 | + l := listen(t) | |
226 | + go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, false, 10*time.Second, done) | |
227 | + | |
228 | + c, err := l.Accept() | |
229 | + if err != nil { | |
230 | + t.Fatalf("accept: %v", err) | |
231 | + } | |
232 | + | |
233 | + go func() { | |
234 | + b := []byte{} | |
235 | + _, err = c.Read(b) | |
236 | + timeout <- err | |
237 | + }() | |
238 | + | |
239 | + select { | |
240 | + case <-done: | |
241 | + t.Fatalf("net.Conn: reader still blocked after 10 seconds") | |
242 | + case err := <-timeout: | |
243 | + if err == nil { | |
244 | + t.Fatalf("net.Conn: no error reported for incomplete and overflowed header") | |
245 | + } | |
246 | + } | |
247 | +} |
0 | From: "Iestyn C. Elfick" <dev@chimera.dev> | |
1 | Date: Wed, 3 Mar 2021 02:39:12 +0000 | |
2 | Subject: fixes for #69 | |
3 | ||
4 | (cherry picked from commit a368adcec57657ab34c0b53c8f771f2224cc5d30) | |
5 | --- | |
6 | header.go | 1 + | |
7 | v1.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------- | |
8 | v1_test.go | 2 +- | |
9 | 3 files changed, 73 insertions(+), 9 deletions(-) | |
10 | ||
11 | diff --git a/header.go b/header.go | |
12 | index fd67e17..81ebeb3 100644 | |
13 | --- a/header.go | |
14 | +++ b/header.go | |
15 | @@ -16,6 +16,7 @@ var ( | |
16 | SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'} | |
17 | SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'} | |
18 | ||
19 | + ErrCantReadVersion1Header = errors.New("proxyproto: can't read version 1 header") | |
20 | ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less") | |
21 | ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n") | |
22 | ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command") | |
23 | diff --git a/v1.go b/v1.go | |
24 | index 9ff686a..23de95e 100644 | |
25 | --- a/v1.go | |
26 | +++ b/v1.go | |
27 | @@ -3,6 +3,7 @@ package proxyproto | |
28 | import ( | |
29 | "bufio" | |
30 | "bytes" | |
31 | + "fmt" | |
32 | "net" | |
33 | "strconv" | |
34 | "strings" | |
35 | @@ -22,17 +23,79 @@ func initVersion1() *Header { | |
36 | } | |
37 | ||
38 | func parseVersion1(reader *bufio.Reader) (*Header, error) { | |
39 | - // Read until LF shows up, otherwise fail. | |
40 | - // At this point, can't be sure CR precedes LF which will be validated next. | |
41 | - line, err := reader.ReadString('\n') | |
42 | - if err != nil { | |
43 | - return nil, ErrLineMustEndWithCrlf | |
44 | - } | |
45 | - if !strings.HasSuffix(line, crlf) { | |
46 | + //The header cannot be more than 107 bytes long. Per spec: | |
47 | + // | |
48 | + // (...) | |
49 | + // - worst case (optional fields set to 0xff) : | |
50 | + // "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n" | |
51 | + // => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars | |
52 | + // | |
53 | + // So a 108-byte buffer is always enough to store all the line and a | |
54 | + // trailing zero for string processing. | |
55 | + // | |
56 | + // It must also be CRLF terminated, as above. The header does not otherwise | |
57 | + // contain a CR or LF byte. | |
58 | + // | |
59 | + // ISSUE #69 | |
60 | + // We can't use Peek here as it will block trying to fill the buffer, which | |
61 | + // will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes | |
62 | + // respectively) and the server is expected to speak first. | |
63 | + // | |
64 | + // Similarly, we can't use ReadString or ReadBytes as these will keep reading | |
65 | + // until the delimiter is found; an abusive client could easily disrupt a | |
66 | + // server by sending a large amount of data that do not contain a LF byte. | |
67 | + // Another means of attack would be to start connections and simply not send | |
68 | + // data after the initial PROXY signature bytes, accumulating a large | |
69 | + // number of blocked goroutines on the server. ReadSlice will also block for | |
70 | + // a delimiter when the internal buffer does not fill up. | |
71 | + // | |
72 | + // A plain Read is also problematic since we risk reading past the end of the | |
73 | + // header without being able to easily put the excess bytes back into the reader's | |
74 | + // buffer (with the current implementation's design). | |
75 | + // | |
76 | + // So we use a ReadByte loop, which solves the overflow problem and avoids | |
77 | + // reading beyond the end of the header. However, we need one more trick to harden | |
78 | + // against partial header attacks (slow loris) - per spec: | |
79 | + // | |
80 | + // (..) The sender must always ensure that the header is sent at once, so that | |
81 | + // the transport layer maintains atomicity along the path to the receiver. The | |
82 | + // receiver may be tolerant to partial headers or may simply drop the connection | |
83 | + // when receiving a partial header. Recommendation is to be tolerant, but | |
84 | + // implementation constraints may not always easily permit this. | |
85 | + // | |
86 | + // We are subject to such implementation constraints. So we return an error if | |
87 | + // the header cannot be fully extracted with a single read of the underlying | |
88 | + // reader. | |
89 | + buf := make([]byte, 0, 107) | |
90 | + for { | |
91 | + b, err := reader.ReadByte() | |
92 | + if err != nil { | |
93 | + return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err) | |
94 | + } | |
95 | + buf = append(buf, b) | |
96 | + if b == '\n' { | |
97 | + // End of header found | |
98 | + break | |
99 | + } | |
100 | + if len(buf) == 107 { | |
101 | + // No delimiter in first 107 bytes | |
102 | + return nil, ErrVersion1HeaderTooLong | |
103 | + } | |
104 | + if reader.Buffered() == 0 { | |
105 | + // Header was not buffered in a single read. Since we can't | |
106 | + // differentiate between genuine slow writers and DoS agents, | |
107 | + // we abort. On healthy networks, this should never happen. | |
108 | + return nil, ErrCantReadVersion1Header | |
109 | + } | |
110 | + } | |
111 | + | |
112 | + // Check for CR before LF. | |
113 | + if len(buf) < 2 || buf[len(buf)-2] != '\r' { | |
114 | return nil, ErrLineMustEndWithCrlf | |
115 | } | |
116 | + | |
117 | // Check full signature. | |
118 | - tokens := strings.Split(line[:len(line)-2], separator) | |
119 | + tokens := strings.Split(string(buf[:len(buf)-2]), separator) | |
120 | ||
121 | // Expect at least 2 tokens: "PROXY" and the transport protocol. | |
122 | if len(tokens) < 2 { | |
123 | diff --git a/v1_test.go b/v1_test.go | |
124 | index 1ee218f..0267580 100644 | |
125 | --- a/v1_test.go | |
126 | +++ b/v1_test.go | |
127 | @@ -64,7 +64,7 @@ var invalidParseV1Tests = []struct { | |
128 | { | |
129 | desc: "incomplete signature TCP4", | |
130 | reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndPorts)), | |
131 | - expectedError: ErrLineMustEndWithCrlf, | |
132 | + expectedError: ErrCantReadVersion1Header, | |
133 | }, | |
134 | { | |
135 | desc: "TCP6 with IPv4 addresses", |
0 | From: Marshall Beddoe <mbeddoe@gmail.com> | |
1 | Date: Fri, 9 Apr 2021 20:50:09 -0500 | |
2 | Subject: Add support for ReadHeaderTimeout | |
3 | ||
4 | Set a read deadline when waiting for the PROXY protocol header. | |
5 | Fix for #65 | |
6 | ||
7 | (cherry picked from commit cdc63867da24fc609b727231f682670d0d1cd346) | |
8 | ||
9 | Closes: #991498, CVE-2021-23409 | |
10 | --- | |
11 | README.md | 32 ++++++++++++++++++++++++++++++++ | |
12 | protocol.go | 11 ++++++++--- | |
13 | protocol_test.go | 38 ++++++++++++++++++++++++++++++++++++++ | |
14 | 3 files changed, 78 insertions(+), 3 deletions(-) | |
15 | ||
16 | diff --git a/README.md b/README.md | |
17 | index 1aedea5..982707c 100644 | |
18 | --- a/README.md | |
19 | +++ b/README.md | |
20 | @@ -119,6 +119,38 @@ func main() { | |
21 | } | |
22 | ``` | |
23 | ||
24 | +### HTTP Server | |
25 | +```go | |
26 | +package main | |
27 | + | |
28 | +import ( | |
29 | + "net" | |
30 | + "net/http" | |
31 | + "time" | |
32 | + | |
33 | + "github.com/pires/go-proxyproto" | |
34 | +) | |
35 | + | |
36 | +func main() { | |
37 | + server := http.Server{ | |
38 | + Addr: ":8080", | |
39 | + } | |
40 | + | |
41 | + ln, err := net.Listen("tcp", server.Addr) | |
42 | + if err != nil { | |
43 | + panic(err) | |
44 | + } | |
45 | + | |
46 | + proxyListener := &proxyproto.Listener{ | |
47 | + Listener: ln, | |
48 | + ReadHeaderTimeout: 10 * time.Second, | |
49 | + } | |
50 | + defer proxyListener.Close() | |
51 | + | |
52 | + server.Serve(proxyListener) | |
53 | +} | |
54 | +``` | |
55 | + | |
56 | ## Special notes | |
57 | ||
58 | ### AWS | |
59 | diff --git a/protocol.go b/protocol.go | |
60 | index 878ca1c..ebad481 100644 | |
61 | --- a/protocol.go | |
62 | +++ b/protocol.go | |
63 | @@ -12,9 +12,10 @@ import ( | |
64 | // If the connection is using the protocol, the RemoteAddr() will return | |
65 | // the correct client address. | |
66 | type Listener struct { | |
67 | - Listener net.Listener | |
68 | - Policy PolicyFunc | |
69 | - ValidateHeader Validator | |
70 | + Listener net.Listener | |
71 | + Policy PolicyFunc | |
72 | + ValidateHeader Validator | |
73 | + ReadHeaderTimeout time.Duration | |
74 | } | |
75 | ||
76 | // Conn is used to wrap and underlying connection which | |
77 | @@ -51,6 +52,10 @@ func (p *Listener) Accept() (net.Conn, error) { | |
78 | return nil, err | |
79 | } | |
80 | ||
81 | + if d := p.ReadHeaderTimeout; d != 0 { | |
82 | + conn.SetReadDeadline(time.Now().Add(d)) | |
83 | + } | |
84 | + | |
85 | proxyHeaderPolicy := USE | |
86 | if p.Policy != nil { | |
87 | proxyHeaderPolicy, err = p.Policy(conn.RemoteAddr()) | |
88 | diff --git a/protocol_test.go b/protocol_test.go | |
89 | index f5c08be..b3d61ed 100644 | |
90 | --- a/protocol_test.go | |
91 | +++ b/protocol_test.go | |
92 | @@ -6,11 +6,13 @@ package proxyproto | |
93 | ||
94 | import ( | |
95 | "bytes" | |
96 | + "context" | |
97 | "crypto/tls" | |
98 | "crypto/x509" | |
99 | "fmt" | |
100 | "net" | |
101 | "testing" | |
102 | + "time" | |
103 | ) | |
104 | ||
105 | func TestPassthrough(t *testing.T) { | |
106 | @@ -59,6 +61,42 @@ func TestPassthrough(t *testing.T) { | |
107 | } | |
108 | } | |
109 | ||
110 | +func TestReadHeaderTimeout(t *testing.T) { | |
111 | + l, err := net.Listen("tcp", "127.0.0.1:0") | |
112 | + if err != nil { | |
113 | + t.Fatalf("err: %v", err) | |
114 | + } | |
115 | + | |
116 | + pl := &Listener{ | |
117 | + Listener: l, | |
118 | + ReadHeaderTimeout: 1 * time.Millisecond, | |
119 | + } | |
120 | + | |
121 | + ctx, cancel := context.WithCancel(context.Background()) | |
122 | + defer cancel() | |
123 | + | |
124 | + go func() { | |
125 | + conn, err := net.Dial("tcp", pl.Addr().String()) | |
126 | + if err != nil { | |
127 | + t.Fatalf("err: %v", err) | |
128 | + } | |
129 | + defer conn.Close() | |
130 | + | |
131 | + <-ctx.Done() | |
132 | + }() | |
133 | + | |
134 | + conn, err := pl.Accept() | |
135 | + if err != nil { | |
136 | + t.Fatalf("err: %v", err) | |
137 | + } | |
138 | + defer conn.Close() | |
139 | + | |
140 | + // Read blocks forever if there is no ReadHeaderTimeout | |
141 | + recv := make([]byte, 4) | |
142 | + _, err = conn.Read(recv) | |
143 | + | |
144 | +} | |
145 | + | |
146 | func TestParse_ipv4(t *testing.T) { | |
147 | l, err := net.Listen("tcp", "127.0.0.1:0") | |
148 | if err != nil { |
0 | 01-add-tests-proving-issue-69.patch | |
1 | 02-fixes-for-69.patch | |
2 | 03-Add-support-for-ReadHeaderTimeout.patch |
0 | package main | |
1 | ||
2 | import ( | |
3 | "io" | |
4 | "log" | |
5 | "net" | |
6 | ||
7 | proxyproto "github.com/pires/go-proxyproto" | |
8 | ) | |
9 | ||
10 | func chkErr(err error) { | |
11 | if err != nil { | |
12 | log.Fatalf("Error: %s", err.Error()) | |
13 | } | |
14 | } | |
15 | ||
16 | func main() { | |
17 | // Dial some proxy listener e.g. https://github.com/mailgun/proxyproto | |
18 | target, err := net.ResolveTCPAddr("tcp", "127.0.0.1:9876") | |
19 | chkErr(err) | |
20 | ||
21 | conn, err := net.DialTCP("tcp", nil, target) | |
22 | chkErr(err) | |
23 | ||
24 | defer conn.Close() | |
25 | ||
26 | // Create a proxyprotocol header or use HeaderProxyFromAddrs() if you | |
27 | // have two conn's | |
28 | header := &proxyproto.Header{ | |
29 | Version: 1, | |
30 | Command: proxyproto.PROXY, | |
31 | TransportProtocol: proxyproto.TCPv4, | |
32 | SourceAddr: &net.TCPAddr{ | |
33 | IP: net.ParseIP("10.1.1.1"), | |
34 | Port: 1000, | |
35 | }, | |
36 | DestinationAddr: &net.TCPAddr{ | |
37 | IP: net.ParseIP("20.2.2.2"), | |
38 | Port: 2000, | |
39 | }, | |
40 | } | |
41 | // After the connection was created write the proxy headers first | |
42 | _, err = header.WriteTo(conn) | |
43 | chkErr(err) | |
44 | // Then your data... e.g.: | |
45 | _, err = io.WriteString(conn, "HELO") | |
46 | chkErr(err) | |
47 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "log" | |
4 | "net" | |
5 | "net/http" | |
6 | "time" | |
7 | ||
8 | "github.com/pires/go-proxyproto" | |
9 | ) | |
10 | ||
11 | // TODO: add httpclient example | |
12 | ||
13 | func main() { | |
14 | server := http.Server{ | |
15 | Addr: ":8080", | |
16 | ConnState: func(c net.Conn, s http.ConnState) { | |
17 | if s == http.StateNew { | |
18 | log.Printf("[ConnState] %s -> %s", c.LocalAddr().String(), c.RemoteAddr().String()) | |
19 | } | |
20 | }, | |
21 | Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
22 | log.Printf("[Handler] remote ip %q", r.RemoteAddr) | |
23 | }), | |
24 | } | |
25 | ||
26 | ln, err := net.Listen("tcp", server.Addr) | |
27 | if err != nil { | |
28 | panic(err) | |
29 | } | |
30 | ||
31 | proxyListener := &proxyproto.Listener{ | |
32 | Listener: ln, | |
33 | ReadHeaderTimeout: 10 * time.Second, | |
34 | } | |
35 | defer proxyListener.Close() | |
36 | ||
37 | server.Serve(proxyListener) | |
38 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "log" | |
4 | "net" | |
5 | ||
6 | proxyproto "github.com/pires/go-proxyproto" | |
7 | ) | |
8 | ||
9 | func main() { | |
10 | // Create a listener | |
11 | addr := "localhost:9876" | |
12 | list, err := net.Listen("tcp", addr) | |
13 | if err != nil { | |
14 | log.Fatalf("couldn't listen to %q: %q\n", addr, err.Error()) | |
15 | } | |
16 | ||
17 | // Wrap listener in a proxyproto listener | |
18 | proxyListener := &proxyproto.Listener{Listener: list} | |
19 | defer proxyListener.Close() | |
20 | ||
21 | // Wait for a connection and accept it | |
22 | conn, err := proxyListener.Accept() | |
23 | defer conn.Close() | |
24 | ||
25 | // Print connection details | |
26 | if conn.LocalAddr() == nil { | |
27 | log.Fatal("couldn't retrieve local address") | |
28 | } | |
29 | log.Printf("local address: %q", conn.LocalAddr().String()) | |
30 | ||
31 | if conn.RemoteAddr() == nil { | |
32 | log.Fatal("couldn't retrieve remote address") | |
33 | } | |
34 | log.Printf("remote address: %q", conn.RemoteAddr().String()) | |
35 | } |
15 | 15 | SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'} |
16 | 16 | SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'} |
17 | 17 | |
18 | ErrLineMustEndWithCrlf = errors.New("proxyproto: header is invalid, must end with \\r\\n") | |
18 | ErrCantReadVersion1Header = errors.New("proxyproto: can't read version 1 header") | |
19 | ErrVersion1HeaderTooLong = errors.New("proxyproto: version 1 header must be 107 bytes or less") | |
20 | ErrLineMustEndWithCrlf = errors.New("proxyproto: version 1 header is invalid, must end with \\r\\n") | |
19 | 21 | ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command") |
20 | 22 | ErrCantReadAddressFamilyAndProtocol = errors.New("proxyproto: can't read address family or protocol") |
21 | 23 | ErrCantReadLength = errors.New("proxyproto: can't read length") |
12 | 12 | // Stuff to be used in both versions tests. |
13 | 13 | |
14 | 14 | const ( |
15 | NO_PROTOCOL = "There is no spoon" | |
16 | IP4_ADDR = "127.0.0.1" | |
17 | IP6_ADDR = "::1" | |
18 | PORT = 65533 | |
19 | INVALID_PORT = 99999 | |
15 | NO_PROTOCOL = "There is no spoon" | |
16 | IP4_ADDR = "127.0.0.1" | |
17 | IP6_ADDR = "::1" | |
18 | IP6_LONG_ADDR = "1234:5678:9abc:def0:cafe:babe:dead:2bad" | |
19 | PORT = 65533 | |
20 | INVALID_PORT = 99999 | |
20 | 21 | ) |
21 | 22 | |
22 | 23 | var ( |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | "io" | |
4 | 5 | "net" |
5 | 6 | "sync" |
6 | 7 | "time" |
11 | 12 | // If the connection is using the protocol, the RemoteAddr() will return |
12 | 13 | // the correct client address. |
13 | 14 | type Listener struct { |
14 | Listener net.Listener | |
15 | Policy PolicyFunc | |
16 | ValidateHeader Validator | |
15 | Listener net.Listener | |
16 | Policy PolicyFunc | |
17 | ValidateHeader Validator | |
18 | ReadHeaderTimeout time.Duration | |
17 | 19 | } |
18 | 20 | |
19 | 21 | // Conn is used to wrap and underlying connection which |
48 | 50 | conn, err := p.Listener.Accept() |
49 | 51 | if err != nil { |
50 | 52 | return nil, err |
53 | } | |
54 | ||
55 | if d := p.ReadHeaderTimeout; d != 0 { | |
56 | conn.SetReadDeadline(time.Now().Add(d)) | |
51 | 57 | } |
52 | 58 | |
53 | 59 | proxyHeaderPolicy := USE |
236 | 242 | |
237 | 243 | return err |
238 | 244 | } |
245 | ||
246 | // ReadFrom implements the io.ReaderFrom ReadFrom method | |
247 | func (p *Conn) ReadFrom(r io.Reader) (int64, error) { | |
248 | if rf, ok := p.conn.(io.ReaderFrom); ok { | |
249 | return rf.ReadFrom(r) | |
250 | } | |
251 | return io.Copy(p.conn, r) | |
252 | } | |
253 | ||
254 | // WriteTo implements io.WriterTo | |
255 | func (p *Conn) WriteTo(w io.Writer) (int64, error) { | |
256 | p.once.Do(func() { p.readErr = p.readHeader() }) | |
257 | if p.readErr != nil { | |
258 | return 0, p.readErr | |
259 | } | |
260 | return p.bufReader.WriteTo(w) | |
261 | } |
5 | 5 | |
6 | 6 | import ( |
7 | 7 | "bytes" |
8 | "context" | |
8 | 9 | "crypto/tls" |
9 | 10 | "crypto/x509" |
10 | 11 | "fmt" |
12 | "io" | |
13 | "io/ioutil" | |
11 | 14 | "net" |
12 | 15 | "testing" |
16 | "time" | |
13 | 17 | ) |
14 | 18 | |
15 | 19 | func TestPassthrough(t *testing.T) { |
56 | 60 | if _, err := conn.Write([]byte("pong")); err != nil { |
57 | 61 | t.Fatalf("err: %v", err) |
58 | 62 | } |
63 | } | |
64 | ||
65 | func TestReadHeaderTimeout(t *testing.T) { | |
66 | l, err := net.Listen("tcp", "127.0.0.1:0") | |
67 | if err != nil { | |
68 | t.Fatalf("err: %v", err) | |
69 | } | |
70 | ||
71 | pl := &Listener{ | |
72 | Listener: l, | |
73 | ReadHeaderTimeout: 1 * time.Millisecond, | |
74 | } | |
75 | ||
76 | ctx, cancel := context.WithCancel(context.Background()) | |
77 | defer cancel() | |
78 | ||
79 | go func() { | |
80 | conn, err := net.Dial("tcp", pl.Addr().String()) | |
81 | if err != nil { | |
82 | t.Fatalf("err: %v", err) | |
83 | } | |
84 | defer conn.Close() | |
85 | ||
86 | <-ctx.Done() | |
87 | }() | |
88 | ||
89 | conn, err := pl.Accept() | |
90 | if err != nil { | |
91 | t.Fatalf("err: %v", err) | |
92 | } | |
93 | defer conn.Close() | |
94 | ||
95 | // Read blocks forever if there is no ReadHeaderTimeout | |
96 | recv := make([]byte, 4) | |
97 | _, err = conn.Read(recv) | |
98 | ||
59 | 99 | } |
60 | 100 | |
61 | 101 | func TestParse_ipv4(t *testing.T) { |
738 | 778 | if err.Error() != "tls: first record does not look like a TLS handshake" { |
739 | 779 | t.Fatalf("expected tls handshake error, got %s", err) |
740 | 780 | } |
781 | } | |
782 | ||
783 | type testConn struct { | |
784 | readFromCalledWith io.Reader | |
785 | reads int | |
786 | net.Conn // nil; crash on any unexpected use | |
787 | } | |
788 | ||
789 | func (c *testConn) ReadFrom(r io.Reader) (int64, error) { | |
790 | c.readFromCalledWith = r | |
791 | b, err := ioutil.ReadAll(r) | |
792 | return int64(len(b)), err | |
793 | } | |
794 | func (c *testConn) Write(p []byte) (int, error) { | |
795 | return len(p), nil | |
796 | } | |
797 | func (c *testConn) Read(p []byte) (int, error) { | |
798 | if c.reads == 0 { | |
799 | return 0, io.EOF | |
800 | } | |
801 | c.reads-- | |
802 | return 1, nil | |
803 | } | |
804 | ||
805 | func TestCopyToWrappedConnection(t *testing.T) { | |
806 | innerConn := &testConn{} | |
807 | wrappedConn := NewConn(innerConn) | |
808 | dummySrc := &testConn{reads: 1} | |
809 | ||
810 | io.Copy(wrappedConn, dummySrc) | |
811 | if innerConn.readFromCalledWith != dummySrc { | |
812 | t.Error("Expected io.Copy to delegate to ReadFrom function of inner destination connection") | |
813 | } | |
814 | } | |
815 | ||
816 | func TestCopyFromWrappedConnection(t *testing.T) { | |
817 | wrappedConn := NewConn(&testConn{reads: 1}) | |
818 | dummyDst := &testConn{} | |
819 | ||
820 | io.Copy(dummyDst, wrappedConn) | |
821 | if dummyDst.readFromCalledWith != wrappedConn.conn { | |
822 | t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom method of destination") | |
823 | } | |
824 | } | |
825 | ||
826 | func TestCopyFromWrappedConnectionToWrappedConnection(t *testing.T) { | |
827 | innerConn1 := &testConn{reads: 1} | |
828 | wrappedConn1 := NewConn(innerConn1) | |
829 | innerConn2 := &testConn{} | |
830 | wrappedConn2 := NewConn(innerConn2) | |
831 | ||
832 | io.Copy(wrappedConn1, wrappedConn2) | |
833 | if innerConn1.readFromCalledWith != innerConn2 { | |
834 | t.Errorf("Expected io.Copy to pass inner source connection to ReadFrom of inner destination connection") | |
835 | } | |
836 | } | |
837 | ||
838 | func benchmarkTCPProxy(size int, b *testing.B) { | |
839 | //create and start the echo backend | |
840 | backend, err := net.Listen("tcp", "127.0.0.1:0") | |
841 | if err != nil { | |
842 | b.Fatalf("err: %v", err) | |
843 | } | |
844 | defer backend.Close() | |
845 | go func() { | |
846 | for { | |
847 | conn, err := backend.Accept() | |
848 | if err != nil { | |
849 | break | |
850 | } | |
851 | _, err = io.Copy(conn, conn) | |
852 | conn.Close() | |
853 | if err != nil { | |
854 | b.Fatalf("Failed to read entire payload: %v", err) | |
855 | } | |
856 | } | |
857 | }() | |
858 | ||
859 | //start the proxyprotocol enabled tcp proxy | |
860 | l, err := net.Listen("tcp", "127.0.0.1:0") | |
861 | if err != nil { | |
862 | b.Fatalf("err: %v", err) | |
863 | } | |
864 | defer l.Close() | |
865 | pl := &Listener{Listener: l} | |
866 | go func() { | |
867 | for { | |
868 | conn, err := pl.Accept() | |
869 | if err != nil { | |
870 | break | |
871 | } | |
872 | bConn, err := net.Dial("tcp", backend.Addr().String()) | |
873 | if err != nil { | |
874 | b.Fatalf("failed to dial backend: %v", err) | |
875 | } | |
876 | go func() { | |
877 | _, err = io.Copy(bConn, conn) | |
878 | if err != nil { | |
879 | b.Fatalf("Failed to proxy incoming data to backend: %v", err) | |
880 | } | |
881 | bConn.(*net.TCPConn).CloseWrite() | |
882 | }() | |
883 | _, err = io.Copy(conn, bConn) | |
884 | if err != nil { | |
885 | b.Fatalf("Failed to proxy data from backend: %v", err) | |
886 | } | |
887 | conn.Close() | |
888 | bConn.Close() | |
889 | } | |
890 | }() | |
891 | ||
892 | data := make([]byte, size) | |
893 | ||
894 | header := &Header{ | |
895 | Version: 2, | |
896 | Command: PROXY, | |
897 | TransportProtocol: TCPv4, | |
898 | SourceAddr: &net.TCPAddr{ | |
899 | IP: net.ParseIP("10.1.1.1"), | |
900 | Port: 1000, | |
901 | }, | |
902 | DestinationAddr: &net.TCPAddr{ | |
903 | IP: net.ParseIP("20.2.2.2"), | |
904 | Port: 2000, | |
905 | }, | |
906 | } | |
907 | ||
908 | //now for the actual benchmark | |
909 | b.ResetTimer() | |
910 | for n := 0; n < b.N; n++ { | |
911 | conn, err := net.Dial("tcp", pl.Addr().String()) | |
912 | if err != nil { | |
913 | b.Fatalf("err: %v", err) | |
914 | } | |
915 | // Write out the header! | |
916 | header.WriteTo(conn) | |
917 | //send data | |
918 | go func() { | |
919 | _, err = conn.Write(data) | |
920 | if err != nil { | |
921 | b.Fatalf("Failed to write data: %v", err) | |
922 | } | |
923 | conn.(*net.TCPConn).CloseWrite() | |
924 | ||
925 | }() | |
926 | //receive data | |
927 | n, err := io.Copy(ioutil.Discard, conn) | |
928 | if n != int64(len(data)) { | |
929 | b.Fatalf("Expected to receive %d bytes, got %d", len(data), n) | |
930 | } | |
931 | if err != nil { | |
932 | b.Fatalf("Failed to read data: %v", err) | |
933 | } | |
934 | conn.Close() | |
935 | } | |
936 | } | |
937 | ||
938 | func BenchmarkTCPProxy16KB(b *testing.B) { | |
939 | benchmarkTCPProxy(16*1024, b) | |
940 | } | |
941 | func BenchmarkTCPProxy32KB(b *testing.B) { | |
942 | benchmarkTCPProxy(32*1024, b) | |
943 | } | |
944 | func BenchmarkTCPProxy64KB(b *testing.B) { | |
945 | benchmarkTCPProxy(64*1024, b) | |
946 | } | |
947 | func BenchmarkTCPProxy128KB(b *testing.B) { | |
948 | benchmarkTCPProxy(128*1024, b) | |
949 | } | |
950 | func BenchmarkTCPProxy256KB(b *testing.B) { | |
951 | benchmarkTCPProxy(256*1024, b) | |
952 | } | |
953 | func BenchmarkTCPProxy512KB(b *testing.B) { | |
954 | benchmarkTCPProxy(512*1024, b) | |
955 | } | |
956 | func BenchmarkTCPProxy1024KB(b *testing.B) { | |
957 | benchmarkTCPProxy(1024*1024, b) | |
958 | } | |
959 | func BenchmarkTCPProxy2048KB(b *testing.B) { | |
960 | benchmarkTCPProxy(2048*1024, b) | |
741 | 961 | } |
742 | 962 | |
743 | 963 | // copied from src/net/http/internal/testcert.go |
15 | 15 | PP2_TYPE_AUTHORITY PP2Type = 0x02 |
16 | 16 | PP2_TYPE_CRC32C PP2Type = 0x03 |
17 | 17 | PP2_TYPE_NOOP PP2Type = 0x04 |
18 | PP2_TYPE_UNIQUE_ID PP2Type = 0x05 | |
18 | 19 | PP2_TYPE_SSL PP2Type = 0x20 |
19 | 20 | PP2_SUBTYPE_SSL_VERSION PP2Type = 0x21 |
20 | 21 | PP2_SUBTYPE_SSL_CN PP2Type = 0x22 |
96 | 97 | PP2_TYPE_AUTHORITY, |
97 | 98 | PP2_TYPE_CRC32C, |
98 | 99 | PP2_TYPE_NOOP, |
100 | PP2_TYPE_UNIQUE_ID, | |
99 | 101 | PP2_TYPE_SSL, |
100 | 102 | PP2_SUBTYPE_SSL_VERSION, |
101 | 103 | PP2_SUBTYPE_SSL_CN, |
99 | 99 | |
100 | 100 | func TestV2TLVPP2Registered(t *testing.T) { |
101 | 101 | pp2RegTypes := []PP2Type{ |
102 | PP2_TYPE_ALPN, PP2_TYPE_AUTHORITY, PP2_TYPE_CRC32C, PP2_TYPE_NOOP, | |
102 | PP2_TYPE_ALPN, PP2_TYPE_AUTHORITY, PP2_TYPE_CRC32C, PP2_TYPE_NOOP, PP2_TYPE_UNIQUE_ID, | |
103 | 103 | PP2_TYPE_SSL, PP2_SUBTYPE_SSL_VERSION, PP2_SUBTYPE_SSL_CN, |
104 | 104 | PP2_SUBTYPE_SSL_CIPHER, PP2_SUBTYPE_SSL_SIG_ALG, PP2_SUBTYPE_SSL_KEY_ALG, |
105 | 105 | PP2_TYPE_NETNS, |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | 4 | "bytes" |
5 | "fmt" | |
5 | 6 | "net" |
6 | 7 | "strconv" |
7 | 8 | "strings" |
21 | 22 | } |
22 | 23 | |
23 | 24 | func parseVersion1(reader *bufio.Reader) (*Header, error) { |
24 | // Read until LF shows up, otherwise fail. | |
25 | // At this point, can't be sure CR precedes LF which will be validated next. | |
26 | line, err := reader.ReadString('\n') | |
27 | if err != nil { | |
25 | //The header cannot be more than 107 bytes long. Per spec: | |
26 | // | |
27 | // (...) | |
28 | // - worst case (optional fields set to 0xff) : | |
29 | // "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n" | |
30 | // => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars | |
31 | // | |
32 | // So a 108-byte buffer is always enough to store all the line and a | |
33 | // trailing zero for string processing. | |
34 | // | |
35 | // It must also be CRLF terminated, as above. The header does not otherwise | |
36 | // contain a CR or LF byte. | |
37 | // | |
38 | // ISSUE #69 | |
39 | // We can't use Peek here as it will block trying to fill the buffer, which | |
40 | // will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes | |
41 | // respectively) and the server is expected to speak first. | |
42 | // | |
43 | // Similarly, we can't use ReadString or ReadBytes as these will keep reading | |
44 | // until the delimiter is found; an abusive client could easily disrupt a | |
45 | // server by sending a large amount of data that do not contain a LF byte. | |
46 | // Another means of attack would be to start connections and simply not send | |
47 | // data after the initial PROXY signature bytes, accumulating a large | |
48 | // number of blocked goroutines on the server. ReadSlice will also block for | |
49 | // a delimiter when the internal buffer does not fill up. | |
50 | // | |
51 | // A plain Read is also problematic since we risk reading past the end of the | |
52 | // header without being able to easily put the excess bytes back into the reader's | |
53 | // buffer (with the current implementation's design). | |
54 | // | |
55 | // So we use a ReadByte loop, which solves the overflow problem and avoids | |
56 | // reading beyond the end of the header. However, we need one more trick to harden | |
57 | // against partial header attacks (slow loris) - per spec: | |
58 | // | |
59 | // (..) The sender must always ensure that the header is sent at once, so that | |
60 | // the transport layer maintains atomicity along the path to the receiver. The | |
61 | // receiver may be tolerant to partial headers or may simply drop the connection | |
62 | // when receiving a partial header. Recommendation is to be tolerant, but | |
63 | // implementation constraints may not always easily permit this. | |
64 | // | |
65 | // We are subject to such implementation constraints. So we return an error if | |
66 | // the header cannot be fully extracted with a single read of the underlying | |
67 | // reader. | |
68 | buf := make([]byte, 0, 107) | |
69 | for { | |
70 | b, err := reader.ReadByte() | |
71 | if err != nil { | |
72 | return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err) | |
73 | } | |
74 | buf = append(buf, b) | |
75 | if b == '\n' { | |
76 | // End of header found | |
77 | break | |
78 | } | |
79 | if len(buf) == 107 { | |
80 | // No delimiter in first 107 bytes | |
81 | return nil, ErrVersion1HeaderTooLong | |
82 | } | |
83 | if reader.Buffered() == 0 { | |
84 | // Header was not buffered in a single read. Since we can't | |
85 | // differentiate between genuine slow writers and DoS agents, | |
86 | // we abort. On healthy networks, this should never happen. | |
87 | return nil, ErrCantReadVersion1Header | |
88 | } | |
89 | } | |
90 | ||
91 | // Check for CR before LF. | |
92 | if len(buf) < 2 || buf[len(buf)-2] != '\r' { | |
28 | 93 | return nil, ErrLineMustEndWithCrlf |
29 | 94 | } |
30 | if !strings.HasSuffix(line, crlf) { | |
31 | return nil, ErrLineMustEndWithCrlf | |
32 | } | |
95 | ||
33 | 96 | // Check full signature. |
34 | tokens := strings.Split(line[:len(line)-2], separator) | |
97 | tokens := strings.Split(string(buf[:len(buf)-2]), separator) | |
35 | 98 | |
36 | 99 | // Expect at least 2 tokens: "PROXY" and the transport protocol. |
37 | 100 | if len(tokens) < 2 { |
2 | 2 | import ( |
3 | 3 | "bufio" |
4 | 4 | "bytes" |
5 | "io" | |
6 | "net" | |
5 | 7 | "strconv" |
6 | 8 | "strings" |
7 | 9 | "testing" |
10 | "time" | |
8 | 11 | ) |
9 | 12 | |
10 | 13 | var ( |
11 | 14 | IPv4AddressesAndPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) |
12 | 15 | IPv4AddressesAndInvalidPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(INVALID_PORT), strconv.Itoa(INVALID_PORT)}, separator) |
13 | 16 | IPv6AddressesAndPorts = strings.Join([]string{IP6_ADDR, IP6_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) |
17 | IPv6LongAddressesAndPorts = strings.Join([]string{IP6_LONG_ADDR, IP6_LONG_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator) | |
14 | 18 | |
15 | 19 | fixtureTCP4V1 = "PROXY TCP4 " + IPv4AddressesAndPorts + crlf + "GET /" |
16 | 20 | fixtureTCP6V1 = "PROXY TCP6 " + IPv6AddressesAndPorts + crlf + "GET /" |
21 | ||
22 | fixtureTCP6V1Overflow = "PROXY TCP6 " + IPv6LongAddressesAndPorts | |
17 | 23 | |
18 | 24 | fixtureUnknown = "PROXY UNKNOWN" + crlf |
19 | 25 | fixtureUnknownWithAddresses = "PROXY UNKNOWN " + IPv4AddressesAndInvalidPorts + crlf |
57 | 63 | { |
58 | 64 | desc: "incomplete signature TCP4", |
59 | 65 | reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndPorts)), |
60 | expectedError: ErrLineMustEndWithCrlf, | |
66 | expectedError: ErrCantReadVersion1Header, | |
61 | 67 | }, |
62 | 68 | { |
63 | 69 | desc: "TCP6 with IPv4 addresses", |
73 | 79 | desc: "TCP4 with invalid port", |
74 | 80 | reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndInvalidPorts + crlf)), |
75 | 81 | expectedError: ErrInvalidPortNumber, |
82 | }, | |
83 | { | |
84 | desc: "header too long", | |
85 | reader: newBufioReader([]byte("PROXY UNKNOWN " + IPv6LongAddressesAndPorts + " " + crlf)), | |
86 | expectedError: ErrVersion1HeaderTooLong, | |
76 | 87 | }, |
77 | 88 | } |
78 | 89 | |
80 | 91 | for _, tt := range invalidParseV1Tests { |
81 | 92 | t.Run(tt.desc, func(t *testing.T) { |
82 | 93 | if _, err := Read(tt.reader); err != tt.expectedError { |
83 | t.Fatalf("expected %s, actual %s", tt.expectedError, err.Error()) | |
94 | t.Fatalf("expected %s, actual %v", tt.expectedError, err) | |
84 | 95 | } |
85 | 96 | }) |
86 | 97 | } |
174 | 185 | }) |
175 | 186 | } |
176 | 187 | } |
188 | ||
189 | // Tests for parseVersion1 overflow - issue #69. | |
190 | ||
191 | type dataSource struct { | |
192 | NBytes int | |
193 | NRead int | |
194 | } | |
195 | ||
196 | func (ds *dataSource) Read(b []byte) (int, error) { | |
197 | if ds.NRead >= ds.NBytes { | |
198 | return 0, io.EOF | |
199 | } | |
200 | avail := ds.NBytes - ds.NRead | |
201 | if len(b) < avail { | |
202 | avail = len(b) | |
203 | } | |
204 | for i := 0; i < avail; i++ { | |
205 | b[i] = 0x20 | |
206 | } | |
207 | ds.NRead += avail | |
208 | return avail, nil | |
209 | } | |
210 | ||
211 | func TestParseVersion1Overflow(t *testing.T) { | |
212 | ds := &dataSource{} | |
213 | reader := bufio.NewReader(ds) | |
214 | bufSize := reader.Size() | |
215 | ds.NBytes = bufSize * 16 | |
216 | parseVersion1(reader) | |
217 | if ds.NRead > bufSize { | |
218 | t.Fatalf("read: expected max %d bytes, actual %d\n", bufSize, ds.NRead) | |
219 | } | |
220 | } | |
221 | ||
222 | func listen(t *testing.T) *Listener { | |
223 | l, err := net.Listen("tcp", "127.0.0.1:0") | |
224 | if err != nil { | |
225 | t.Fatalf("listen: %v", err) | |
226 | } | |
227 | return &Listener{Listener: l} | |
228 | } | |
229 | ||
230 | func client(t *testing.T, addr, header string, length int, terminate bool, wait time.Duration, done chan struct{}) { | |
231 | c, err := net.Dial("tcp", addr) | |
232 | if err != nil { | |
233 | t.Fatalf("dial: %v", err) | |
234 | } | |
235 | defer c.Close() | |
236 | ||
237 | if terminate && length < 2 { | |
238 | length = 2 | |
239 | } | |
240 | ||
241 | buf := make([]byte, len(header)+length) | |
242 | copy(buf, []byte(header)) | |
243 | for i := 0; i < length-2; i++ { | |
244 | buf[i+len(header)] = 0x20 | |
245 | } | |
246 | if terminate { | |
247 | copy(buf[len(header)+length-2:], []byte(crlf)) | |
248 | } | |
249 | ||
250 | n, err := c.Write(buf) | |
251 | if err != nil { | |
252 | t.Fatalf("write: %v", err) | |
253 | } | |
254 | if n != len(buf) { | |
255 | t.Fatalf("write; short write") | |
256 | } | |
257 | ||
258 | time.Sleep(wait) | |
259 | close(done) | |
260 | } | |
261 | ||
262 | func TestVersion1Overflow(t *testing.T) { | |
263 | done := make(chan struct{}) | |
264 | ||
265 | l := listen(t) | |
266 | go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, true, 10*time.Second, done) | |
267 | ||
268 | c, err := l.Accept() | |
269 | if err != nil { | |
270 | t.Fatalf("accept: %v", err) | |
271 | } | |
272 | ||
273 | b := []byte{} | |
274 | _, err = c.Read(b) | |
275 | if err == nil { | |
276 | t.Fatalf("net.Conn: no error reported for oversized header") | |
277 | } | |
278 | } | |
279 | ||
280 | func TestVersion1SlowLoris(t *testing.T) { | |
281 | done := make(chan struct{}) | |
282 | timeout := make(chan error) | |
283 | ||
284 | l := listen(t) | |
285 | go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 0, false, 10*time.Second, done) | |
286 | ||
287 | c, err := l.Accept() | |
288 | if err != nil { | |
289 | t.Fatalf("accept: %v", err) | |
290 | } | |
291 | ||
292 | go func() { | |
293 | b := []byte{} | |
294 | _, err = c.Read(b) | |
295 | timeout <- err | |
296 | }() | |
297 | ||
298 | select { | |
299 | case <-done: | |
300 | t.Fatalf("net.Conn: reader still blocked after 10 seconds") | |
301 | case err := <-timeout: | |
302 | if err == nil { | |
303 | t.Fatalf("net.Conn: no error reported for incomplete header") | |
304 | } | |
305 | } | |
306 | } | |
307 | ||
308 | func TestVersion1SlowLorisOverflow(t *testing.T) { | |
309 | done := make(chan struct{}) | |
310 | timeout := make(chan error) | |
311 | ||
312 | l := listen(t) | |
313 | go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, false, 10*time.Second, done) | |
314 | ||
315 | c, err := l.Accept() | |
316 | if err != nil { | |
317 | t.Fatalf("accept: %v", err) | |
318 | } | |
319 | ||
320 | go func() { | |
321 | b := []byte{} | |
322 | _, err = c.Read(b) | |
323 | timeout <- err | |
324 | }() | |
325 | ||
326 | select { | |
327 | case <-done: | |
328 | t.Fatalf("net.Conn: reader still blocked after 10 seconds") | |
329 | case err := <-timeout: | |
330 | if err == nil { | |
331 | t.Fatalf("net.Conn: no error reported for incomplete and overflowed header") | |
332 | } | |
333 | } | |
334 | } |