• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pires / go-proxyproto / 21589884149

02 Feb 2026 10:49AM UTC coverage: 94.821% (+0.04%) from 94.784%
21589884149

Pull #142

github

clementnuss
fix: Read() data truncation issue

remove the multireader abstraction and modify conn.Read() so that we
start by draining buffered data (from the header parsing), then reading
directly from the underlying connection in a single Read() call

fixes data loss in TLS passthrough scenarios (e.g. ingress-nginx)
when PROXY header + TLS ClientHello exceeds the 256-byte buffer.
Pull Request #142: fix: Read() data truncation issue

11 of 11 new or added lines in 1 file covered. (100.0%)

14 existing lines in 1 file now uncovered.

952 of 1004 relevant lines covered (94.82%)

50.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.23
/protocol.go
1
package proxyproto
2

3
import (
4
        "bufio"
5
        "errors"
6
        "fmt"
7
        "io"
8
        "net"
9
        "sync"
10
        "sync/atomic"
11
        "time"
12
)
13

14
var (
15
        // DefaultReadHeaderTimeout is how long header processing waits for header to
16
        // be read from the wire, if Listener.ReaderHeaderTimeout is not set.
17
        // It's kept as a global variable so to make it easier to find and override,
18
        // e.g. go build -ldflags -X "github.com/pires/go-proxyproto.DefaultReadHeaderTimeout=1s"
19
        DefaultReadHeaderTimeout = 10 * time.Second
20

21
        // ErrInvalidUpstream should be returned when an upstream connection address
22
        // is not trusted, and therefore is invalid.
23
        ErrInvalidUpstream = fmt.Errorf("proxyproto: upstream connection address not trusted for PROXY information")
24
)
25

26
// Listener is used to wrap an underlying listener,
27
// whose connections may be using the HAProxy Proxy Protocol.
28
// If the connection is using the protocol, the RemoteAddr() will return
29
// the correct client address. ReadHeaderTimeout will be applied to all
30
// connections in order to prevent blocking operations. If no ReadHeaderTimeout
31
// is set, a default of 10s will be used. This can be disabled by setting the
32
// timeout to < 0.
33
//
34
// Only one of Policy or ConnPolicy should be provided. If both are provided then
35
// a panic would occur during accept.
36
type Listener struct {
37
        Listener net.Listener
38
        // Deprecated: use ConnPolicyFunc instead. This will be removed in future release.
39
        Policy            PolicyFunc
40
        ConnPolicy        ConnPolicyFunc
41
        ValidateHeader    Validator
42
        ReadHeaderTimeout time.Duration
43
}
44

45
// Conn is used to wrap and underlying connection which
46
// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will
47
// return the address of the client instead of the proxy address. Each connection
48
// will have its own readHeaderTimeout and readDeadline set by the Accept() call.
49
type Conn struct {
50
        readDeadline      atomic.Value // time.Time
51
        once              sync.Once
52
        readErr           error
53
        conn              net.Conn
54
        bufReader         *bufio.Reader
55
        header            *Header
56
        ProxyHeaderPolicy Policy
57
        Validate          Validator
58
        readHeaderTimeout time.Duration
59
}
60

61
// Validator receives a header and decides whether it is a valid one
62
// In case the header is not deemed valid it should return an error.
63
type Validator func(*Header) error
64

65
// ValidateHeader adds given validator for proxy headers to a connection when passed as option to NewConn()
66
func ValidateHeader(v Validator) func(*Conn) {
48✔
67
        return func(c *Conn) {
96✔
68
                if v != nil {
50✔
69
                        c.Validate = v
2✔
70
                }
2✔
71
        }
72
}
73

74
// SetReadHeaderTimeout sets the readHeaderTimeout for a connection when passed as option to NewConn()
75
func SetReadHeaderTimeout(t time.Duration) func(*Conn) {
×
76
        return func(c *Conn) {
×
77
                if t >= 0 {
×
78
                        c.readHeaderTimeout = t
×
79
                }
×
80
        }
81
}
82

83
// Accept waits for and returns the next valid connection to the listener.
84
func (p *Listener) Accept() (net.Conn, error) {
60✔
85
        for {
122✔
86
                // Get the underlying connection
62✔
87
                conn, err := p.Listener.Accept()
62✔
88
                if err != nil {
62✔
89
                        return nil, err
×
90
                }
×
91

92
                proxyHeaderPolicy := USE
62✔
93
                if p.Policy != nil && p.ConnPolicy != nil {
64✔
94
                        panic("only one of policy or connpolicy must be provided.")
2✔
95
                }
96
                if p.Policy != nil || p.ConnPolicy != nil {
100✔
97
                        if p.Policy != nil {
72✔
98
                                proxyHeaderPolicy, err = p.Policy(conn.RemoteAddr())
32✔
99
                        } else {
40✔
100
                                proxyHeaderPolicy, err = p.ConnPolicy(ConnPolicyOptions{
8✔
101
                                        Upstream:   conn.RemoteAddr(),
8✔
102
                                        Downstream: conn.LocalAddr(),
8✔
103
                                })
8✔
104
                        }
8✔
105
                        if err != nil {
48✔
106
                                // can't decide the policy, we can't accept the connection
8✔
107
                                conn.Close()
8✔
108

8✔
109
                                if errors.Is(err, ErrInvalidUpstream) {
10✔
110
                                        // keep listening for other connections
2✔
111
                                        continue
2✔
112
                                }
113

114
                                return nil, err
6✔
115
                        }
116
                        // Handle a connection as a regular one
117
                        if proxyHeaderPolicy == SKIP {
36✔
118
                                return conn, nil
4✔
119
                        }
4✔
120
                }
121

122
                newConn := NewConn(
48✔
123
                        conn,
48✔
124
                        WithPolicy(proxyHeaderPolicy),
48✔
125
                        ValidateHeader(p.ValidateHeader),
48✔
126
                )
48✔
127

48✔
128
                // If the ReadHeaderTimeout for the listener is unset, use the default timeout.
48✔
129
                if p.ReadHeaderTimeout == 0 {
80✔
130
                        p.ReadHeaderTimeout = DefaultReadHeaderTimeout
32✔
131
                }
32✔
132

133
                // Set the readHeaderTimeout of the new conn to the value of the listener
134
                newConn.readHeaderTimeout = p.ReadHeaderTimeout
48✔
135

48✔
136
                return newConn, nil
48✔
137
        }
138
}
139

140
// Close closes the underlying listener.
141
func (p *Listener) Close() error {
4✔
142
        return p.Listener.Close()
4✔
143
}
4✔
144

145
// Addr returns the underlying listener's network address.
146
func (p *Listener) Addr() net.Addr {
58✔
147
        return p.Listener.Addr()
58✔
148
}
58✔
149

150
// NewConn is used to wrap a net.Conn that may be speaking
151
// the proxy protocol into a proxyproto.Conn
152
func NewConn(conn net.Conn, opts ...func(*Conn)) *Conn {
60✔
153
        // For v1 the header length is at most 108 bytes.
60✔
154
        // For v2 the header length is at most 52 bytes plus the length of the TLVs.
60✔
155
        // We use 256 bytes to be safe.
60✔
156
        const bufSize = 256
60✔
157
        br := bufio.NewReaderSize(conn, bufSize)
60✔
158

60✔
159
        pConn := &Conn{
60✔
160
                bufReader: br,
60✔
161
                conn:      conn,
60✔
162
        }
60✔
163

60✔
164
        for _, opt := range opts {
160✔
165
                opt(pConn)
100✔
166
        }
100✔
167

168
        return pConn
60✔
169
}
170

171
// Read is check for the proxy protocol header when doing
172
// the initial scan. If there is an error parsing the header,
173
// it is returned and the socket is closed.
174
func (p *Conn) Read(b []byte) (int, error) {
48✔
175
        p.once.Do(func() {
92✔
176
                p.readErr = p.readHeader()
44✔
177
        })
44✔
178
        if p.readErr != nil {
72✔
179
                return 0, p.readErr
24✔
180
        }
24✔
181

182
        // First drain any buffered data from header parsing,
183
        // then read directly from the underlying connection.
184
        n := 0
24✔
185
        if p.bufReader.Buffered() > 0 {
30✔
186
                n, _ = p.bufReader.Read(b)
6✔
187
        }
6✔
188

189
        if n < len(b) {
44✔
190
                nn, err := p.conn.Read(b[n:])
20✔
191
                return n + nn, err
20✔
192
        }
20✔
193
        return n, nil
4✔
194
}
195

196
// Write wraps original conn.Write
197
func (p *Conn) Write(b []byte) (int, error) {
12✔
198
        return p.conn.Write(b)
12✔
199
}
12✔
200

201
// Close wraps original conn.Close
202
func (p *Conn) Close() error {
44✔
203
        return p.conn.Close()
44✔
204
}
44✔
205

206
// ProxyHeader returns the proxy protocol header, if any. If an error occurs
207
// while reading the proxy header, nil is returned.
208
func (p *Conn) ProxyHeader() *Header {
6✔
209
        p.once.Do(func() { p.readErr = p.readHeader() })
6✔
210
        return p.header
6✔
211
}
212

213
// LocalAddr returns the address of the server if the proxy
214
// protocol is being used, otherwise just returns the address of
215
// the socket server. In case an error happens on reading the
216
// proxy header the original LocalAddr is returned, not the one
217
// from the proxy header even if the proxy header itself is
218
// syntactically correct.
219
func (p *Conn) LocalAddr() net.Addr {
2✔
220
        p.once.Do(func() { p.readErr = p.readHeader() })
4✔
221
        if p.header == nil || p.header.Command.IsLocal() || p.readErr != nil {
4✔
222
                return p.conn.LocalAddr()
2✔
223
        }
2✔
224

UNCOV
225
        return p.header.DestinationAddr
×
226
}
227

228
// RemoteAddr returns the address of the client if the proxy
229
// protocol is being used, otherwise just returns the address of
230
// the socket peer. In case an error happens on reading the
231
// proxy header the original RemoteAddr is returned, not the one
232
// from the proxy header even if the proxy header itself is
233
// syntactically correct.
234
func (p *Conn) RemoteAddr() net.Addr {
14✔
235
        p.once.Do(func() { p.readErr = p.readHeader() })
16✔
236
        if p.header == nil || p.header.Command.IsLocal() || p.readErr != nil {
20✔
237
                return p.conn.RemoteAddr()
6✔
238
        }
6✔
239

240
        return p.header.SourceAddr
8✔
241
}
242

243
// Raw returns the underlying connection which can be casted to
244
// a concrete type, allowing access to specialized functions.
245
//
246
// Use this ONLY if you know exactly what you are doing.
247
func (p *Conn) Raw() net.Conn {
2✔
248
        return p.conn
2✔
249
}
2✔
250

251
// TCPConn returns the underlying TCP connection,
252
// allowing access to specialized functions.
253
//
254
// Use this ONLY if you know exactly what you are doing.
255
func (p *Conn) TCPConn() (conn *net.TCPConn, ok bool) {
2✔
256
        conn, ok = p.conn.(*net.TCPConn)
2✔
257
        return
2✔
258
}
2✔
259

260
// UnixConn returns the underlying Unix socket connection,
261
// allowing access to specialized functions.
262
//
263
// Use this ONLY if you know exactly what you are doing.
264
func (p *Conn) UnixConn() (conn *net.UnixConn, ok bool) {
2✔
265
        conn, ok = p.conn.(*net.UnixConn)
2✔
266
        return
2✔
267
}
2✔
268

269
// UDPConn returns the underlying UDP connection,
270
// allowing access to specialized functions.
271
//
272
// Use this ONLY if you know exactly what you are doing.
273
func (p *Conn) UDPConn() (conn *net.UDPConn, ok bool) {
2✔
274
        conn, ok = p.conn.(*net.UDPConn)
2✔
275
        return
2✔
276
}
2✔
277

278
// SetDeadline wraps original conn.SetDeadline
279
func (p *Conn) SetDeadline(t time.Time) error {
6✔
280
        p.readDeadline.Store(t)
6✔
281
        return p.conn.SetDeadline(t)
6✔
282
}
6✔
283

284
// SetReadDeadline wraps original conn.SetReadDeadline
285
func (p *Conn) SetReadDeadline(t time.Time) error {
4✔
286
        // Set a local var that tells us the desired deadline. This is
4✔
287
        // needed in order to reset the read deadline to the one that is
4✔
288
        // desired by the user, rather than an empty deadline.
4✔
289
        p.readDeadline.Store(t)
4✔
290
        return p.conn.SetReadDeadline(t)
4✔
291
}
4✔
292

293
// SetWriteDeadline wraps original conn.SetWriteDeadline
294
func (p *Conn) SetWriteDeadline(t time.Time) error {
2✔
295
        return p.conn.SetWriteDeadline(t)
2✔
296
}
2✔
297

298
func (p *Conn) readHeader() error {
52✔
299
        // If the connection's readHeaderTimeout is more than 0,
52✔
300
        // push our deadline back to now plus the timeout. This should only
52✔
301
        // run on the connection, as we don't want to override the previous
52✔
302
        // read deadline the user may have used.
52✔
303
        if p.readHeaderTimeout > 0 {
96✔
304
                if err := p.conn.SetReadDeadline(time.Now().Add(p.readHeaderTimeout)); err != nil {
44✔
UNCOV
305
                        return err
×
UNCOV
306
                }
×
307
        }
308

309
        header, err := Read(p.bufReader)
52✔
310

52✔
311
        // If the connection's readHeaderTimeout is more than 0, undo the change to the
52✔
312
        // deadline that we made above. Because we retain the readDeadline as part of our
52✔
313
        // SetReadDeadline override, we know the user's desired deadline so we use that.
52✔
314
        // Therefore, we check whether the error is a net.Timeout and if it is, we decide
52✔
315
        // the proxy proto does not exist and set the error accordingly.
52✔
316
        if p.readHeaderTimeout > 0 {
96✔
317
                t := p.readDeadline.Load()
44✔
318
                if t == nil {
80✔
319
                        t = time.Time{}
36✔
320
                }
36✔
321
                if err := p.conn.SetReadDeadline(t.(time.Time)); err != nil {
44✔
UNCOV
322
                        return err
×
UNCOV
323
                }
×
324
                if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
46✔
325
                        err = ErrNoProxyProtocol
2✔
326
                }
2✔
327
        }
328

329
        // For the purpose of this wrapper shamefully stolen from armon/go-proxyproto
330
        // let's act as if there was no error when PROXY protocol is not present.
331
        if err == ErrNoProxyProtocol {
78✔
332
                // but not if it is required that the connection has one
26✔
333
                if p.ProxyHeaderPolicy == REQUIRE {
38✔
334
                        return err
12✔
335
                }
12✔
336

337
                return nil
14✔
338
        }
339

340
        // proxy protocol header was found
341
        if err == nil && header != nil {
44✔
342
                switch p.ProxyHeaderPolicy {
18✔
343
                case REJECT:
2✔
344
                        // this connection is not allowed to send one
2✔
345
                        return ErrSuperfluousProxyHeader
2✔
346
                case USE, REQUIRE:
14✔
347
                        if p.Validate != nil {
16✔
348
                                err = p.Validate(header)
2✔
349
                                if err != nil {
4✔
350
                                        return err
2✔
351
                                }
2✔
352
                        }
353

354
                        p.header = header
12✔
355
                }
356
        }
357

358
        return err
22✔
359
}
360

361
// ReadFrom implements the io.ReaderFrom ReadFrom method
362
func (p *Conn) ReadFrom(r io.Reader) (int64, error) {
4✔
363
        if rf, ok := p.conn.(io.ReaderFrom); ok {
8✔
364
                return rf.ReadFrom(r)
4✔
365
        }
4✔
UNCOV
366
        return io.Copy(p.conn, r)
×
367
}
368

369
// WriteTo implements io.WriterTo
370
func (p *Conn) WriteTo(w io.Writer) (int64, error) {
4✔
371
        p.once.Do(func() { p.readErr = p.readHeader() })
8✔
372
        if p.readErr != nil {
4✔
UNCOV
373
                return 0, p.readErr
×
UNCOV
374
        }
×
375

376
        b := make([]byte, p.bufReader.Buffered())
4✔
377
        if _, err := p.bufReader.Read(b); err != nil {
4✔
UNCOV
378
                return 0, err // this should never as we read buffered data
×
UNCOV
379
        }
×
380

381
        var n int64
4✔
382
        {
8✔
383
                nn, err := w.Write(b)
4✔
384
                n += int64(nn)
4✔
385
                if err != nil {
4✔
UNCOV
386
                        return n, err
×
UNCOV
387
                }
×
388
        }
389
        {
4✔
390
                nn, err := io.Copy(w, p.conn)
4✔
391
                n += nn
4✔
392
                if err != nil {
4✔
UNCOV
393
                        return n, err
×
UNCOV
394
                }
×
395
        }
396

397
        return n, nil
4✔
398
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc