Update
This commit is contained in:
243
vendor/github.com/pires/go-proxyproto/v1.go
generated
vendored
Normal file
243
vendor/github.com/pires/go-proxyproto/v1.go
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
package proxyproto
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
crlf = "\r\n"
|
||||
separator = " "
|
||||
)
|
||||
|
||||
func initVersion1() *Header {
|
||||
header := new(Header)
|
||||
header.Version = 1
|
||||
// Command doesn't exist in v1
|
||||
header.Command = PROXY
|
||||
return header
|
||||
}
|
||||
|
||||
func parseVersion1(reader *bufio.Reader) (*Header, error) {
|
||||
//The header cannot be more than 107 bytes long. Per spec:
|
||||
//
|
||||
// (...)
|
||||
// - worst case (optional fields set to 0xff) :
|
||||
// "PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535\r\n"
|
||||
// => 5 + 1 + 7 + 1 + 39 + 1 + 39 + 1 + 5 + 1 + 5 + 2 = 107 chars
|
||||
//
|
||||
// So a 108-byte buffer is always enough to store all the line and a
|
||||
// trailing zero for string processing.
|
||||
//
|
||||
// It must also be CRLF terminated, as above. The header does not otherwise
|
||||
// contain a CR or LF byte.
|
||||
//
|
||||
// ISSUE #69
|
||||
// We can't use Peek here as it will block trying to fill the buffer, which
|
||||
// will never happen if the header is TCP4 or TCP6 (max. 56 and 104 bytes
|
||||
// respectively) and the server is expected to speak first.
|
||||
//
|
||||
// Similarly, we can't use ReadString or ReadBytes as these will keep reading
|
||||
// until the delimiter is found; an abusive client could easily disrupt a
|
||||
// server by sending a large amount of data that do not contain a LF byte.
|
||||
// Another means of attack would be to start connections and simply not send
|
||||
// data after the initial PROXY signature bytes, accumulating a large
|
||||
// number of blocked goroutines on the server. ReadSlice will also block for
|
||||
// a delimiter when the internal buffer does not fill up.
|
||||
//
|
||||
// A plain Read is also problematic since we risk reading past the end of the
|
||||
// header without being able to easily put the excess bytes back into the reader's
|
||||
// buffer (with the current implementation's design).
|
||||
//
|
||||
// So we use a ReadByte loop, which solves the overflow problem and avoids
|
||||
// reading beyond the end of the header. However, we need one more trick to harden
|
||||
// against partial header attacks (slow loris) - per spec:
|
||||
//
|
||||
// (..) The sender must always ensure that the header is sent at once, so that
|
||||
// the transport layer maintains atomicity along the path to the receiver. The
|
||||
// receiver may be tolerant to partial headers or may simply drop the connection
|
||||
// when receiving a partial header. Recommendation is to be tolerant, but
|
||||
// implementation constraints may not always easily permit this.
|
||||
//
|
||||
// We are subject to such implementation constraints. So we return an error if
|
||||
// the header cannot be fully extracted with a single read of the underlying
|
||||
// reader.
|
||||
buf := make([]byte, 0, 107)
|
||||
for {
|
||||
b, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(ErrCantReadVersion1Header.Error()+": %v", err)
|
||||
}
|
||||
buf = append(buf, b)
|
||||
if b == '\n' {
|
||||
// End of header found
|
||||
break
|
||||
}
|
||||
if len(buf) == 107 {
|
||||
// No delimiter in first 107 bytes
|
||||
return nil, ErrVersion1HeaderTooLong
|
||||
}
|
||||
if reader.Buffered() == 0 {
|
||||
// Header was not buffered in a single read. Since we can't
|
||||
// differentiate between genuine slow writers and DoS agents,
|
||||
// we abort. On healthy networks, this should never happen.
|
||||
return nil, ErrCantReadVersion1Header
|
||||
}
|
||||
}
|
||||
|
||||
// Check for CR before LF.
|
||||
if len(buf) < 2 || buf[len(buf)-2] != '\r' {
|
||||
return nil, ErrLineMustEndWithCrlf
|
||||
}
|
||||
|
||||
// Check full signature.
|
||||
tokens := strings.Split(string(buf[:len(buf)-2]), separator)
|
||||
|
||||
// Expect at least 2 tokens: "PROXY" and the transport protocol.
|
||||
if len(tokens) < 2 {
|
||||
return nil, ErrCantReadAddressFamilyAndProtocol
|
||||
}
|
||||
|
||||
// Read address family and protocol
|
||||
var transportProtocol AddressFamilyAndProtocol
|
||||
switch tokens[1] {
|
||||
case "TCP4":
|
||||
transportProtocol = TCPv4
|
||||
case "TCP6":
|
||||
transportProtocol = TCPv6
|
||||
case "UNKNOWN":
|
||||
transportProtocol = UNSPEC // doesn't exist in v1 but fits UNKNOWN
|
||||
default:
|
||||
return nil, ErrCantReadAddressFamilyAndProtocol
|
||||
}
|
||||
|
||||
// Expect 6 tokens only when UNKNOWN is not present.
|
||||
if transportProtocol != UNSPEC && len(tokens) < 6 {
|
||||
return nil, ErrCantReadAddressFamilyAndProtocol
|
||||
}
|
||||
|
||||
// When a signature is found, allocate a v1 header with Command set to PROXY.
|
||||
// Command doesn't exist in v1 but set it for other parts of this library
|
||||
// to rely on it for determining connection details.
|
||||
header := initVersion1()
|
||||
|
||||
// Transport protocol has been processed already.
|
||||
header.TransportProtocol = transportProtocol
|
||||
|
||||
// When UNKNOWN, set the command to LOCAL and return early
|
||||
if header.TransportProtocol == UNSPEC {
|
||||
header.Command = LOCAL
|
||||
return header, nil
|
||||
}
|
||||
|
||||
// Otherwise, continue to read addresses and ports
|
||||
sourceIP, err := parseV1IPAddress(header.TransportProtocol, tokens[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destIP, err := parseV1IPAddress(header.TransportProtocol, tokens[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sourcePort, err := parseV1PortNumber(tokens[4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
destPort, err := parseV1PortNumber(tokens[5])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
header.SourceAddr = &net.TCPAddr{
|
||||
IP: sourceIP,
|
||||
Port: sourcePort,
|
||||
}
|
||||
header.DestinationAddr = &net.TCPAddr{
|
||||
IP: destIP,
|
||||
Port: destPort,
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func (header *Header) formatVersion1() ([]byte, error) {
|
||||
// As of version 1, only "TCP4" ( \x54 \x43 \x50 \x34 ) for TCP over IPv4,
|
||||
// and "TCP6" ( \x54 \x43 \x50 \x36 ) for TCP over IPv6 are allowed.
|
||||
var proto string
|
||||
switch header.TransportProtocol {
|
||||
case TCPv4:
|
||||
proto = "TCP4"
|
||||
case TCPv6:
|
||||
proto = "TCP6"
|
||||
default:
|
||||
// Unknown connection (short form)
|
||||
return []byte("PROXY UNKNOWN" + crlf), nil
|
||||
}
|
||||
|
||||
sourceAddr, sourceOK := header.SourceAddr.(*net.TCPAddr)
|
||||
destAddr, destOK := header.DestinationAddr.(*net.TCPAddr)
|
||||
if !sourceOK || !destOK {
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
|
||||
sourceIP, destIP := sourceAddr.IP, destAddr.IP
|
||||
switch header.TransportProtocol {
|
||||
case TCPv4:
|
||||
sourceIP = sourceIP.To4()
|
||||
destIP = destIP.To4()
|
||||
case TCPv6:
|
||||
sourceIP = sourceIP.To16()
|
||||
destIP = destIP.To16()
|
||||
}
|
||||
if sourceIP == nil || destIP == nil {
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 108))
|
||||
buf.Write(SIGV1)
|
||||
buf.WriteString(separator)
|
||||
buf.WriteString(proto)
|
||||
buf.WriteString(separator)
|
||||
buf.WriteString(sourceIP.String())
|
||||
buf.WriteString(separator)
|
||||
buf.WriteString(destIP.String())
|
||||
buf.WriteString(separator)
|
||||
buf.WriteString(strconv.Itoa(sourceAddr.Port))
|
||||
buf.WriteString(separator)
|
||||
buf.WriteString(strconv.Itoa(destAddr.Port))
|
||||
buf.WriteString(crlf)
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func parseV1PortNumber(portStr string) (int, error) {
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 0 || port > 65535 {
|
||||
return 0, ErrInvalidPortNumber
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func parseV1IPAddress(protocol AddressFamilyAndProtocol, addrStr string) (net.IP, error) {
|
||||
addr, err := netip.ParseAddr(addrStr)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case TCPv4:
|
||||
if addr.Is4() {
|
||||
return net.IP(addr.AsSlice()), nil
|
||||
}
|
||||
case TCPv6:
|
||||
if addr.Is6() || addr.Is4In6() {
|
||||
return net.IP(addr.AsSlice()), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
Reference in New Issue
Block a user