-
Notifications
You must be signed in to change notification settings - Fork 20
Experimental proxy mode #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
6ca14c5
50a447b
ec77ed6
5819f86
33d7e05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) { | ||||||||||||||
| 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| 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 | ||||||||||||||
| } | ||||||||||||||
| 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{} | ||
| } |
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| 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) | ||
| } |
| 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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
There was a problem hiding this comment.
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