Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions proxy/cloudflare_simple_proxy_protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package proxy

import (
"encoding/binary"
"net"
)

const CLOUDFLARE_SIMPLE_PROXY_PROTOCOL_MAGIC = 0x56EC

// CloudflareSimpleProxyProtocol implements Cloudflares Simple Proxy Protocol
// https://developers.cloudflare.com/spectrum/reference/simple-proxy-protocol-header/
type CloudflareSimpleProxyProtocol struct{}

func (cspp *CloudflareSimpleProxyProtocol) HeaderSize() int {
return 38
}

func (cspp *CloudflareSimpleProxyProtocol) Parse(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we want to reject the packet if it doesn't have the magic

realClientIP := net.IP(packet[2:18])
realProxyIP := net.IP(packet[18:34])
realClientPort := int(binary.BigEndian.Uint16(packet[34:36]))
realProxyPort := int(binary.BigEndian.Uint16(packet[36:38]))

switch v := clientAddress.(type) {
case *net.TCPAddr:
v.IP = realClientIP
v.Port = realClientPort
case *net.UDPAddr:
v.IP = realClientIP
v.Port = realClientPort
}

switch v := proxyAddress.(type) {
case *net.TCPAddr:
v.IP = realProxyIP
v.Port = realProxyPort
case *net.UDPAddr:
v.IP = realProxyIP
v.Port = realProxyPort
}

return packet[cspp.HeaderSize():], nil
}

func (cspp *CloudflareSimpleProxyProtocol) Encode(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
var clientIP net.IP
var clientPort int
var proxyIP net.IP
var proxyPort int

switch v := clientAddress.(type) {
case *net.TCPAddr:
clientIP = v.IP
clientPort = v.Port
case *net.UDPAddr:
clientIP = v.IP
clientPort = v.Port
}

switch v := proxyAddress.(type) {
case *net.TCPAddr:
proxyIP = v.IP
proxyPort = v.Port
case *net.UDPAddr:
proxyIP = v.IP
proxyPort = v.Port
}

if clientIP.To4() != nil {
clientIP = clientIP.To16()
}
Comment on lines +69 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if clientIP.To4() != nil {
clientIP = clientIP.To16()
}
if clientIP.To4() == nil {
clientIP = clientIP.To16()
}

To4 returns nil when it isn't IPv4. I assume this is what was meant? same below


if proxyIP.To4() != nil {
proxyIP = proxyIP.To16()
}

newData := make([]byte, cspp.HeaderSize()+len(packet))
binary.BigEndian.PutUint16(newData[0:2], CLOUDFLARE_SIMPLE_PROXY_PROTOCOL_MAGIC)
copy(newData[2:18], clientIP)
copy(newData[18:34], proxyIP)
binary.BigEndian.PutUint16(newData[34:36], uint16(clientPort))
binary.BigEndian.PutUint16(newData[36:38], uint16(proxyPort))
copy(newData[38:], packet)

return newData, nil
}
24 changes: 24 additions & 0 deletions proxy/dummy_proxy_protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package proxy

import (
"net"
)

// DummyProxyProtocol has no proxy header. Returns the data as-is
type DummyProxyProtocol struct{}

func (dpp *DummyProxyProtocol) HeaderSize() int {
return 0
}

func (dpp *DummyProxyProtocol) Parse(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
return packet, nil
}

func (dpp *DummyProxyProtocol) Encode(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
return packet, nil
}

func NewDummyProxyProtocol() *DummyProxyProtocol {
return &DummyProxyProtocol{}
}
175 changes: 175 additions & 0 deletions proxy/haproxy_proxy_protocol_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package proxy

import (
"encoding/binary"
"fmt"
"net"
)

const (
HAPROXY_V2_SIGNATURE = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"

// * Version and Command
HAPROXY_V2_VERSION = 0x2
HAPROXY_V2_CMD_LOCAL = 0x0
HAPROXY_V2_CMD_PROXY = 0x1

// * Address families
HAPROXY_V2_AF_UNSPEC = 0x0
HAPROXY_V2_AF_INET = 0x1
HAPROXY_V2_AF_INET6 = 0x2
HAPROXY_V2_AF_UNIX = 0x3

// * Transport protocols
HAPROXY_V2_TRANSPORT_UNSPEC = 0x0
HAPROXY_V2_TRANSPORT_STREAM = 0x1
HAPROXY_V2_TRANSPORT_DGRAM = 0x2

// * Combined protocol bytes
HAPROXY_V2_PROTO_UNSPEC = 0x00
HAPROXY_V2_PROTO_TCP4 = 0x11
HAPROXY_V2_PROTO_UDP4 = 0x12
HAPROXY_V2_PROTO_TCP6 = 0x21
HAPROXY_V2_PROTO_UDP6 = 0x22
HAPROXY_V2_PROTO_UNIX_STREAM = 0x31
HAPROXY_V2_PROTO_UNIX_DGRAM = 0x32
)

// HAProxyProxyProtocolV2 implements HAProxys PROXY protocol version 2 (binary format)
// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt#:~:text=2.2.%20Binary%20header%20format%20(version%202)
type HAProxyProxyProtocolV2 struct{}

func (hpp *HAProxyProxyProtocolV2) HeaderSize() int {
// * V2 is variable length (16 bytes minimum + address length)
// * Return -1 to indicate variable length
return -1
}

func (hpp *HAProxyProxyProtocolV2) Parse(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

// TODO - Add validation checks here

versionCommand := packet[12]
version := (versionCommand >> 4) & 0x0F
command := versionCommand & 0x0F

if version != HAPROXY_V2_VERSION {
return nil, fmt.Errorf("unsupported PROXY protocol version: 0x%x", version)
}

family := packet[13]
addressLengthgth := binary.BigEndian.Uint16(packet[14:16])
totalHeaderLen := 16 + int(addressLengthgth)

if command == HAPROXY_V2_CMD_LOCAL {
return packet[totalHeaderLen:], nil
}

addressData := packet[16:totalHeaderLen]

switch family {
case HAPROXY_V2_PROTO_TCP4, HAPROXY_V2_PROTO_UDP4:
realClientIP := net.IPv4(addressData[0], addressData[1], addressData[2], addressData[3])
realClientPort := int(binary.BigEndian.Uint16(addressData[8:10]))

switch v := clientAddress.(type) {
case *net.TCPAddr:
v.IP = realClientIP
v.Port = realClientPort
case *net.UDPAddr:
v.IP = realClientIP
v.Port = realClientPort
}
case HAPROXY_V2_PROTO_TCP6, HAPROXY_V2_PROTO_UDP6:
realClientIP := net.IP(addressData[0:16])
realClientPort := int(binary.BigEndian.Uint16(addressData[32:34]))

switch v := clientAddress.(type) {
case *net.TCPAddr:
v.IP = realClientIP
v.Port = realClientPort
case *net.UDPAddr:
v.IP = realClientIP
v.Port = realClientPort
}
}

return packet[totalHeaderLen:], nil
}

func (hpp *HAProxyProxyProtocolV2) Encode(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
var clientIP net.IP
var clientPort int
var proxyIP net.IP
var proxyPort int
var protocol byte
var addressLength uint16

switch v := clientAddress.(type) {
case *net.TCPAddr:
clientIP = v.IP
clientPort = v.Port
case *net.UDPAddr:
clientIP = v.IP
clientPort = v.Port
}

// TODO - I'm almost positive this is wrong, the PROXY protocol docs say this is the "destination" but it's unclear if that's the proxy server or ustream server
switch v := proxyAddress.(type) {
case *net.TCPAddr:
proxyIP = v.IP
proxyPort = v.Port
case *net.UDPAddr:
proxyIP = v.IP
proxyPort = v.Port
}

var addressData []byte
if clientIP.To4() != nil {
clientIP = clientIP.To4()
proxyIP = proxyIP.To4()

switch clientAddress.(type) {
case *net.TCPAddr:
protocol = HAPROXY_V2_PROTO_TCP4
case *net.UDPAddr:
protocol = HAPROXY_V2_PROTO_UDP4
}

addressLength = 12
addressData = make([]byte, 12)
copy(addressData[0:4], clientIP)
copy(addressData[4:8], proxyIP)
binary.BigEndian.PutUint16(addressData[8:10], uint16(clientPort))
binary.BigEndian.PutUint16(addressData[10:12], uint16(proxyPort))
} else {
clientIP = clientIP.To16()
proxyIP = proxyIP.To16()

switch clientAddress.(type) {
case *net.TCPAddr:
protocol = HAPROXY_V2_PROTO_TCP6
case *net.UDPAddr:
protocol = HAPROXY_V2_PROTO_UDP6
}

addressLength = 36
addressData = make([]byte, 36)
copy(addressData[0:16], clientIP)
copy(addressData[16:32], proxyIP)
binary.BigEndian.PutUint16(addressData[32:34], uint16(clientPort))
binary.BigEndian.PutUint16(addressData[34:36], uint16(proxyPort))
}

header := make([]byte, 16)
copy(header[0:12], []byte(HAPROXY_V2_SIGNATURE))
header[12] = (HAPROXY_V2_VERSION << 4) | HAPROXY_V2_CMD_PROXY
header[13] = protocol
binary.BigEndian.PutUint16(header[14:16], addressLength)

newData := make([]byte, 16+int(addressLength)+len(packet))
copy(newData[0:16], header)
copy(newData[16:16+addressLength], addressData)
copy(newData[16+addressLength:], packet)

return newData, nil
}
16 changes: 16 additions & 0 deletions proxy/proxy_protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package proxy

import "net"

type ProxyProtocol interface {
// HeaderSize returns the size of the proxy protocol header
// attached to the start of all packets
HeaderSize() int

// Parse extracts proxy header from the packet. The client address and proxy address
// are updated in this function. Returns the real packet payload after the proxy header
Parse(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error)

// Encode wraps a payload with the proxy header for the given addresses
Encode(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error)
}
54 changes: 54 additions & 0 deletions proxy/prudp_simple_proxy_protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package proxy

import (
"encoding/binary"
"net"
)

const PRUDP_SIMPLE_PROXY_PROTOCOL_VERSION = 0

// PRUDPSimpleProxyProtocol implements a custom proxy header for PRUDP. Should only be used when
// one of the other protocols cannot be used
type PRUDPSimpleProxyProtocol struct{}

func (pspp *PRUDPSimpleProxyProtocol) HeaderSize() int {
return 7
}

func (pspp *PRUDPSimpleProxyProtocol) Parse(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should reject the packet if the version isn't lower or equal to the current version for good measure

realClientIP := net.IP(packet[1:5])
realClientPort := int(binary.BigEndian.Uint16(packet[5:7]))

switch v := clientAddress.(type) {
case *net.TCPAddr:
v.IP = realClientIP
v.Port = realClientPort
case *net.UDPAddr:
v.IP = realClientIP
v.Port = realClientPort
}

return packet[pspp.HeaderSize():], nil
}

func (pspp *PRUDPSimpleProxyProtocol) Encode(clientAddress net.Addr, proxyAddress net.Addr, packet []byte) ([]byte, error) {
var ipv4 net.IP
var port int

switch v := clientAddress.(type) {
case *net.TCPAddr:
ipv4 = v.IP.To4()
port = v.Port
case *net.UDPAddr:
ipv4 = v.IP.To4()
port = v.Port
}

newData := make([]byte, pspp.HeaderSize()+len(packet))
newData[0] = PRUDP_SIMPLE_PROXY_PROTOCOL_VERSION
copy(newData[1:5], ipv4)
binary.BigEndian.PutUint16(newData[5:7], uint16(port))
copy(newData[7:], packet)

return newData, nil
}
Loading
Loading