Skip to content

Commit ac2f466

Browse files
author
Iestyn C. Elfick
committed
add tests proving issue #69
1 parent c4bcea2 commit ac2f466

File tree

3 files changed

+167
-7
lines changed

3 files changed

+167
-7
lines changed

header.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ var (
1616
SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'}
1717
SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'}
1818

19-
ErrLineMustEndWithCrlf = errors.New("proxyproto: header is invalid, must end with \\r\\n")
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")
2021
ErrCantReadProtocolVersionAndCommand = errors.New("proxyproto: can't read proxy protocol version and command")
2122
ErrCantReadAddressFamilyAndProtocol = errors.New("proxyproto: can't read address family or protocol")
2223
ErrCantReadLength = errors.New("proxyproto: can't read length")

header_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import (
1313
// Stuff to be used in both versions tests.
1414

1515
const (
16-
NO_PROTOCOL = "There is no spoon"
17-
IP4_ADDR = "127.0.0.1"
18-
IP6_ADDR = "::1"
19-
PORT = 65533
20-
INVALID_PORT = 99999
16+
NO_PROTOCOL = "There is no spoon"
17+
IP4_ADDR = "127.0.0.1"
18+
IP6_ADDR = "::1"
19+
IP6_LONG_ADDR = "1234:5678:9abc:def0:cafe:babe:dead:2bad"
20+
PORT = 65533
21+
INVALID_PORT = 99999
2122
)
2223

2324
var (

v1_test.go

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@ package proxyproto
33
import (
44
"bufio"
55
"bytes"
6+
"io"
7+
"net"
68
"strconv"
79
"strings"
810
"testing"
11+
"time"
912
)
1013

1114
var (
1215
IPv4AddressesAndPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator)
1316
IPv4AddressesAndInvalidPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(INVALID_PORT), strconv.Itoa(INVALID_PORT)}, separator)
1417
IPv6AddressesAndPorts = strings.Join([]string{IP6_ADDR, IP6_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator)
18+
IPv6LongAddressesAndPorts = strings.Join([]string{IP6_LONG_ADDR, IP6_LONG_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, separator)
1519

1620
fixtureTCP4V1 = "PROXY TCP4 " + IPv4AddressesAndPorts + crlf + "GET /"
1721
fixtureTCP6V1 = "PROXY TCP6 " + IPv6AddressesAndPorts + crlf + "GET /"
1822

23+
fixtureTCP6V1Overflow = "PROXY TCP6 " + IPv6LongAddressesAndPorts
24+
1925
fixtureUnknown = "PROXY UNKNOWN" + crlf
2026
fixtureUnknownWithAddresses = "PROXY UNKNOWN " + IPv4AddressesAndInvalidPorts + crlf
2127
)
@@ -75,13 +81,18 @@ var invalidParseV1Tests = []struct {
7581
reader: newBufioReader([]byte("PROXY TCP4 " + IPv4AddressesAndInvalidPorts + crlf)),
7682
expectedError: ErrInvalidPortNumber,
7783
},
84+
{
85+
desc: "header too long",
86+
reader: newBufioReader([]byte("PROXY UNKNOWN " + IPv6LongAddressesAndPorts + " " + crlf)),
87+
expectedError: ErrVersion1HeaderTooLong,
88+
},
7889
}
7990

8091
func TestReadV1Invalid(t *testing.T) {
8192
for _, tt := range invalidParseV1Tests {
8293
t.Run(tt.desc, func(t *testing.T) {
8394
if _, err := Read(tt.reader); err != tt.expectedError {
84-
t.Fatalf("expected %s, actual %s", tt.expectedError, err.Error())
95+
t.Fatalf("expected %s, actual %v", tt.expectedError, err)
8596
}
8697
})
8798
}
@@ -175,3 +186,150 @@ func TestWriteV1Valid(t *testing.T) {
175186
})
176187
}
177188
}
189+
190+
// Tests for parseVersion1 overflow - issue #69.
191+
192+
type dataSource struct {
193+
NBytes int
194+
NRead int
195+
}
196+
197+
func (ds *dataSource) Read(b []byte) (int, error) {
198+
if ds.NRead >= ds.NBytes {
199+
return 0, io.EOF
200+
}
201+
avail := ds.NBytes - ds.NRead
202+
if len(b) < avail {
203+
avail = len(b)
204+
}
205+
for i := 0; i < avail; i++ {
206+
b[i] = 0x20
207+
}
208+
ds.NRead += avail
209+
return avail, nil
210+
}
211+
212+
func TestParseVersion1Overflow(t *testing.T) {
213+
ds := &dataSource{}
214+
reader := bufio.NewReader(ds)
215+
bufSize := reader.Size()
216+
ds.NBytes = bufSize * 16
217+
parseVersion1(reader)
218+
if ds.NRead > bufSize {
219+
t.Fatalf("read: expected max %d bytes, actual %d\n", bufSize, ds.NRead)
220+
}
221+
}
222+
223+
func listen(t *testing.T) *Listener {
224+
l, err := net.Listen("tcp", "127.0.0.1:0")
225+
if err != nil {
226+
t.Fatalf("listen: %v", err)
227+
}
228+
return &Listener{Listener: l}
229+
}
230+
231+
func client(t *testing.T, addr, header string, length int, terminate bool, wait time.Duration, done chan struct{}) {
232+
c, err := net.Dial("tcp", addr)
233+
if err != nil {
234+
t.Fatalf("dial: %v", err)
235+
}
236+
defer c.Close()
237+
238+
if terminate && length < 2 {
239+
length = 2
240+
}
241+
242+
buf := make([]byte, len(header)+length)
243+
copy(buf, []byte(header))
244+
for i := 0; i < length-2; i++ {
245+
buf[i+len(header)] = 0x20
246+
}
247+
if terminate {
248+
copy(buf[len(header)+length-2:], []byte(crlf))
249+
}
250+
251+
n, err := c.Write(buf)
252+
if err != nil {
253+
t.Fatalf("write: %v", err)
254+
}
255+
if n != len(buf) {
256+
t.Fatalf("write; short write")
257+
}
258+
259+
time.Sleep(wait)
260+
close(done)
261+
}
262+
263+
func TestVersion1Overflow(t *testing.T) {
264+
done := make(chan struct{})
265+
266+
l := listen(t)
267+
go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, true, 10*time.Second, done)
268+
269+
c, err := l.Accept()
270+
if err != nil {
271+
t.Fatalf("accept: %v", err)
272+
}
273+
274+
b := []byte{}
275+
_, err = c.Read(b)
276+
if err == nil {
277+
t.Fatalf("net.Conn: no error reported for oversized header")
278+
}
279+
}
280+
281+
func TestVersion1SlowLoris(t *testing.T) {
282+
done := make(chan struct{})
283+
timeout := make(chan error)
284+
285+
l := listen(t)
286+
go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 0, false, 10*time.Second, done)
287+
288+
c, err := l.Accept()
289+
if err != nil {
290+
t.Fatalf("accept: %v", err)
291+
}
292+
293+
go func() {
294+
b := []byte{}
295+
_, err = c.Read(b)
296+
timeout <- err
297+
}()
298+
299+
select {
300+
case <-done:
301+
t.Fatalf("net.Conn: reader still blocked after 10 seconds")
302+
case err := <-timeout:
303+
if err == nil {
304+
t.Fatalf("net.Conn: no error reported for incomplete header")
305+
}
306+
}
307+
}
308+
309+
func TestVersion1SlowLorisOverflow(t *testing.T) {
310+
done := make(chan struct{})
311+
timeout := make(chan error)
312+
313+
l := listen(t)
314+
go client(t, l.Addr().String(), fixtureTCP6V1Overflow, 10240, false, 10*time.Second, done)
315+
316+
c, err := l.Accept()
317+
if err != nil {
318+
t.Fatalf("accept: %v", err)
319+
}
320+
321+
go func() {
322+
b := []byte{}
323+
_, err = c.Read(b)
324+
timeout <- err
325+
}()
326+
327+
select {
328+
case <-done:
329+
t.Fatalf("net.Conn: reader still blocked after 10 seconds")
330+
case err := <-timeout:
331+
if err == nil {
332+
t.Fatalf("net.Conn: no error reported for incomplete and overflowed header")
333+
}
334+
}
335+
}

0 commit comments

Comments
 (0)