2023-01-27 07:08:35 +00:00
package netutil
import (
"encoding/binary"
"fmt"
"io"
"net"
2023-01-27 07:45:36 +00:00
"time"
2023-01-27 07:08:35 +00:00
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
)
type proxyProtocolConn struct {
net . Conn
remoteAddr net . Addr
}
func newProxyProtocolConn ( c net . Conn ) ( net . Conn , error ) {
2023-01-27 07:45:36 +00:00
// Limit the time needed for reading the proxy protocol header.
d := time . Now ( ) . Add ( 5 * time . Second )
if err := c . SetReadDeadline ( d ) ; err != nil {
2023-10-25 19:24:01 +00:00
return nil , fmt . Errorf ( "cannot set deadline for reading proxy protocol header: %w" , err )
2023-01-27 07:45:36 +00:00
}
2023-01-27 07:08:35 +00:00
remoteAddr , err := readProxyProto ( c )
if err != nil {
return nil , fmt . Errorf ( "proxy protocol error: %w" , err )
}
if remoteAddr == nil {
remoteAddr = c . RemoteAddr ( )
}
2023-01-27 07:45:36 +00:00
// Reset the read deadline.
if err := c . SetReadDeadline ( time . Time { } ) ; err != nil {
2023-10-25 19:24:01 +00:00
return nil , fmt . Errorf ( "cannot reset deadline after reading proxy protocol header: %w" , err )
2023-01-27 07:45:36 +00:00
}
2023-01-27 07:08:35 +00:00
return & proxyProtocolConn {
Conn : c ,
remoteAddr : remoteAddr ,
} , nil
}
func ( ppc * proxyProtocolConn ) RemoteAddr ( ) net . Addr {
return ppc . remoteAddr
}
func readProxyProto ( r io . Reader ) ( net . Addr , error ) {
bb := bbPool . Get ( )
defer bbPool . Put ( bb )
// Read the first 16 bytes of proxy protocol header:
// - bytes 0-11: v2Identifier
// - byte 12: version and command
// - byte 13: family and protocol
// - bytes 14-15: payload length
//
// See https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
bb . B = bytesutil . ResizeNoCopyMayOverallocate ( bb . B , 16 )
if _ , err := io . ReadFull ( r , bb . B ) ; err != nil {
return nil , fmt . Errorf ( "cannot read proxy protocol header: %w" , err )
}
ident := bb . B [ : 12 ]
if string ( ident ) != v2Identifier {
return nil , fmt . Errorf ( "unexpected proxy protocol header: %q; want %q" , ident , v2Identifier )
}
version := bb . B [ 12 ] >> 4
command := bb . B [ 12 ] & 0x0f
family := bb . B [ 13 ] >> 4
proto := bb . B [ 13 ] & 0x0f
if version != 2 {
return nil , fmt . Errorf ( "unsupported proxy protocol version, only v2 protocol version is supported, got: %d" , version )
}
2023-05-31 13:37:59 +00:00
// check for supported proto:
switch {
case proto == 0 && command == 0 :
// 0 - UNSPEC with LOCAL command 0. Common use case for load balancer health checks.
case proto == 1 :
// 1 - TCP (aka STREAM).
default :
return nil , fmt . Errorf ( "the proxy protocol implementation doesn't support proto %d and command: %d; expecting proto 1 or proto 0 with command 0" , proto , command )
2023-01-27 07:08:35 +00:00
}
// The length of the remainder of the header including any TLVs in network byte order
// 0, 1, 2
blockLen := int ( binary . BigEndian . Uint16 ( bb . B [ 14 : 16 ] ) )
// in general RFC doesn't limit block length, but for sanity check lets limit it to 2kb
// in theory TLVs may occupy some space
if blockLen > 2048 {
return nil , fmt . Errorf ( "too big proxy protocol block length: %d; it mustn't exceed 2048 bytes" , blockLen )
}
// Read the protocol block itself
bb . B = bytesutil . ResizeNoCopyMayOverallocate ( bb . B , blockLen )
if _ , err := io . ReadFull ( r , bb . B ) ; err != nil {
return nil , fmt . Errorf ( "cannot read proxy protocol block with the lehgth %d bytes: %w" , blockLen , err )
}
switch command {
case 0 :
// Proxy LOCAL command. Ignore the protocol block. The real sender address should be used.
return nil , nil
case 1 :
// Parse the protocol block according to the family.
switch family {
case 1 :
// ipv4 (aka AF_INET)
if len ( bb . B ) < 12 {
2023-01-27 18:38:38 +00:00
return nil , fmt . Errorf ( "cannot read ipv4 address from proxy protocol block with the length %d bytes; expected at least 12 bytes" , len ( bb . B ) )
2023-01-27 07:08:35 +00:00
}
remoteAddr := & net . TCPAddr {
IP : net . IPv4 ( bb . B [ 0 ] , bb . B [ 1 ] , bb . B [ 2 ] , bb . B [ 3 ] ) ,
Port : int ( binary . BigEndian . Uint16 ( bb . B [ 8 : 10 ] ) ) ,
}
return remoteAddr , nil
case 2 :
// ipv6 (aka AF_INET6)
if len ( bb . B ) < 36 {
return nil , fmt . Errorf ( "cannot read ipv6 address from proxy protocol block with the length %d bytes; expected at least 36 bytes" , len ( bb . B ) )
}
remoteAddr := & net . TCPAddr {
IP : bb . B [ 0 : 16 ] ,
Port : int ( binary . BigEndian . Uint16 ( bb . B [ 32 : 34 ] ) ) ,
}
return remoteAddr , nil
default :
return nil , fmt . Errorf ( "the proxy protocol implementation doesn't support protocol family %d; supported values: 1, 2" , family )
}
default :
return nil , fmt . Errorf ( "the proxy protocol implementation doesn't support command %d; suppoted values: 0, 1" , command )
}
}
const v2Identifier = "\r\n\r\n\x00\r\nQUIT\n"
var bbPool bytesutil . ByteBufferPool