source: code/trunk/vendor/github.com/pires/go-proxyproto/v1.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 6.9 KB
RevLine 
[822]1package proxyproto
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "net"
8 "net/netip"
9 "strconv"
10 "strings"
11)
12
13const (
14 crlf = "\r\n"
15 separator = " "
16)
17
18func initVersion1() *Header {
19 header := new(Header)
20 header.Version = 1
21 // Command doesn't exist in v1
22 header.Command = PROXY
23 return header
24}
25
26func parseVersion1(reader *bufio.Reader) (*Header, error) {
27 //The header cannot be more than 107 bytes long. Per spec:
28 //
29 // (...)
30 // - worst case (optional fields set to 0xff) :
31 // "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
32 // => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars
33 //
34 // So a 108-byte buffer is always enough to store all the line and a
35 // trailing zero for string processing.
36 //
37 // It must also be CRLF terminated, as above. The header does not otherwise
38 // contain a CR or LF byte.
39 //
40 // ISSUE #69
41 // We can't use Peek here as it will block trying to fill the buffer, which
42 // will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes
43 // respectively) and the server is expected to speak first.
44 //
45 // Similarly, we can't use ReadString or ReadBytes as these will keep reading
46 // until the delimiter is found; an abusive client could easily disrupt a
47 // server by sending a large amount of data that do not contain a LF byte.
48 // Another means of attack would be to start connections and simply not send
49 // data after the initial PROXY signature bytes, accumulating a large
50 // number of blocked goroutines on the server. ReadSlice will also block for
51 // a delimiter when the internal buffer does not fill up.
52 //
53 // A plain Read is also problematic since we risk reading past the end of the
54 // header without being able to easily put the excess bytes back into the reader's
55 // buffer (with the current implementation's design).
56 //
57 // So we use a ReadByte loop, which solves the overflow problem and avoids
58 // reading beyond the end of the header. However, we need one more trick to harden
59 // against partial header attacks (slow loris) - per spec:
60 //
61 // (..) The sender must always ensure that the header is sent at once, so that
62 // the transport layer maintains atomicity along the path to the receiver. The
63 // receiver may be tolerant to partial headers or may simply drop the connection
64 // when receiving a partial header. Recommendation is to be tolerant, but
65 // implementation constraints may not always easily permit this.
66 //
67 // We are subject to such implementation constraints. So we return an error if
68 // the header cannot be fully extracted with a single read of the underlying
69 // reader.
70 buf := make([]byte, 0, 107)
71 for {
72 b, err := reader.ReadByte()
73 if err != nil {
74 return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err)
75 }
76 buf = append(buf, b)
77 if b == '\n' {
78 // End of header found
79 break
80 }
81 if len(buf) == 107 {
82 // No delimiter in first 107 bytes
83 return nil, ErrVersion1HeaderTooLong
84 }
85 if reader.Buffered() == 0 {
86 // Header was not buffered in a single read. Since we can't
87 // differentiate between genuine slow writers and DoS agents,
88 // we abort. On healthy networks, this should never happen.
89 return nil, ErrCantReadVersion1Header
90 }
91 }
92
93 // Check for CR before LF.
94 if len(buf) < 2 || buf[len(buf)-2] != '\r' {
95 return nil, ErrLineMustEndWithCrlf
96 }
97
98 // Check full signature.
99 tokens := strings.Split(string(buf[:len(buf)-2]), separator)
100
101 // Expect at least 2 tokens: "PROXY" and the transport protocol.
102 if len(tokens) < 2 {
103 return nil, ErrCantReadAddressFamilyAndProtocol
104 }
105
106 // Read address family and protocol
107 var transportProtocol AddressFamilyAndProtocol
108 switch tokens[1] {
109 case "TCP4":
110 transportProtocol = TCPv4
111 case "TCP6":
112 transportProtocol = TCPv6
113 case "UNKNOWN":
114 transportProtocol = UNSPEC // doesn't exist in v1 but fits UNKNOWN
115 default:
116 return nil, ErrCantReadAddressFamilyAndProtocol
117 }
118
119 // Expect 6 tokens only when UNKNOWN is not present.
120 if transportProtocol != UNSPEC && len(tokens) < 6 {
121 return nil, ErrCantReadAddressFamilyAndProtocol
122 }
123
124 // When a signature is found, allocate a v1 header with Command set to PROXY.
125 // Command doesn't exist in v1 but set it for other parts of this library
126 // to rely on it for determining connection details.
127 header := initVersion1()
128
129 // Transport protocol has been processed already.
130 header.TransportProtocol = transportProtocol
131
132 // When UNKNOWN, set the command to LOCAL and return early
133 if header.TransportProtocol == UNSPEC {
134 header.Command = LOCAL
135 return header, nil
136 }
137
138 // Otherwise, continue to read addresses and ports
139 sourceIP, err := parseV1IPAddress(header.TransportProtocol, tokens[2])
140 if err != nil {
141 return nil, err
142 }
143 destIP, err := parseV1IPAddress(header.TransportProtocol, tokens[3])
144 if err != nil {
145 return nil, err
146 }
147 sourcePort, err := parseV1PortNumber(tokens[4])
148 if err != nil {
149 return nil, err
150 }
151 destPort, err := parseV1PortNumber(tokens[5])
152 if err != nil {
153 return nil, err
154 }
155 header.SourceAddr = &net.TCPAddr{
156 IP: sourceIP,
157 Port: sourcePort,
158 }
159 header.DestinationAddr = &net.TCPAddr{
160 IP: destIP,
161 Port: destPort,
162 }
163
164 return header, nil
165}
166
167func (header *Header) formatVersion1() ([]byte, error) {
168 // As of version 1, only "TCP4" ( \x54 \x43 \x50 \x34 ) for TCP over IPv4,
169 // and "TCP6" ( \x54 \x43 \x50 \x36 ) for TCP over IPv6 are allowed.
170 var proto string
171 switch header.TransportProtocol {
172 case TCPv4:
173 proto = "TCP4"
174 case TCPv6:
175 proto = "TCP6"
176 default:
177 // Unknown connection (short form)
178 return []byte("PROXY UNKNOWN" + crlf), nil
179 }
180
181 sourceAddr, sourceOK := header.SourceAddr.(*net.TCPAddr)
182 destAddr, destOK := header.DestinationAddr.(*net.TCPAddr)
183 if !sourceOK || !destOK {
184 return nil, ErrInvalidAddress
185 }
186
187 sourceIP, destIP := sourceAddr.IP, destAddr.IP
188 switch header.TransportProtocol {
189 case TCPv4:
190 sourceIP = sourceIP.To4()
191 destIP = destIP.To4()
192 case TCPv6:
193 sourceIP = sourceIP.To16()
194 destIP = destIP.To16()
195 }
196 if sourceIP == nil || destIP == nil {
197 return nil, ErrInvalidAddress
198 }
199
200 buf := bytes.NewBuffer(make([]byte, 0, 108))
201 buf.Write(SIGV1)
202 buf.WriteString(separator)
203 buf.WriteString(proto)
204 buf.WriteString(separator)
205 buf.WriteString(sourceIP.String())
206 buf.WriteString(separator)
207 buf.WriteString(destIP.String())
208 buf.WriteString(separator)
209 buf.WriteString(strconv.Itoa(sourceAddr.Port))
210 buf.WriteString(separator)
211 buf.WriteString(strconv.Itoa(destAddr.Port))
212 buf.WriteString(crlf)
213
214 return buf.Bytes(), nil
215}
216
217func parseV1PortNumber(portStr string) (int, error) {
218 port, err := strconv.Atoi(portStr)
219 if err != nil || port < 0 || port > 65535 {
220 return 0, ErrInvalidPortNumber
221 }
222 return port, nil
223}
224
225func parseV1IPAddress(protocol AddressFamilyAndProtocol, addrStr string) (net.IP, error) {
226 addr, err := netip.ParseAddr(addrStr)
227 if err != nil {
228 return nil, ErrInvalidAddress
229 }
230
231 switch protocol {
232 case TCPv4:
233 if addr.Is4() {
234 return net.IP(addr.AsSlice()), nil
235 }
236 case TCPv6:
237 if addr.Is6() || addr.Is4In6() {
238 return net.IP(addr.AsSlice()), nil
239 }
240 }
241
242 return nil, ErrInvalidAddress
243}
Note: See TracBrowser for help on using the repository browser.