proxy protocol

什么是Proxy Protocol?

Proxy Protocol是一种用于在代理服务器和目标服务器之间传递连接信息的协议。在传统的网络通信中,当客户端连接通过代理服务器时,代理服务器会在转发连接之前修改源IP和端口等连接信息。这导致目标服务器无法正确识别客户端的真实IP和端口。
Proxy Protocol的出现解决了这个问题,它允许代理服务器在转发连接之前,将原始客户端连接的相关信息封装在特殊的协议头部中传递给目标服务器。目标服务器可以解析该头部信息,获取客户端的真实IP和端口等连接信息。

Proxy Protocol的工作原理

Proxy Protocol使用一种简单而有效的协议头部格式来传递连接信息。协议头部被插入到原始客户端数据之前,以确保目标服务器能够正确解析它。

Proxy Protocol v1的协议头部格式如下:
PROXY <TCP4|TCP6> <SOURCE_IP> <DESTINATION_IP> <SOURCE_PORT> <DESTINATION_PORT>\r\n

1
2
3
4
5
6
<TCP4|TCP6> 表示使用IPv4或IPv6协议
<SOURCE_IP> 是原始客户端的IP地址
<DESTINATION_IP> 是目标服务器的IP地址
<SOURCE_PORT> 是原始客户端的端口号
<DESTINATION_PORT> 是目标服务器的端口号
协议头部以字符串形式表示,并以\r\n作为结束符

如:
PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n

当代理服务器收到客户端连接后,它会在转发连接之前将Proxy Protocol头部插入到客户端数据之前。目标服务器在接收连接时会检查是否存在Proxy Protocol头部,并解析其中的连接信息以确定客户端的真实来源。

Proxy Protocol的使用场景

Proxy Protocol在以下场景中非常有用:

透明代理:当代理服务器处于透明模式时,Proxy Protocol可以确保目标服务器能够获得客户端的真实IP和端口信息。
负载均衡:当使用负载均衡器时,Proxy Protocol允许负载均衡器将客户端的连接信息传递给后端服务器,以便后端服务器能够正确处理请求。
反向代理:当反向代理服务器将请求转发给后端服务器时,Proxy Protocol可以确保后端服务器获得客户端的真实连接信息,以便进行合适的处理和记录

使用golang简单模拟proxy protocol

client.go

发送请求给proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
"bufio"
"fmt"
"log"
"net"
)

func main() {
// 建立 TCP 连接
conn, err := net.Dial("tcp", "127.0.0.1:40010")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Println("LocalAddr: ", conn.LocalAddr())

_, err = conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
if err != nil {
log.Fatal(err)
}
// 读取响应
response := bufio.NewReader(conn)
for {
line, err := response.ReadString('\n')
if err != nil {
log.Fatal(err)
}
fmt.Print(line)

if line == "\r\n" {
break
}
}

// 读取响应内容
var body []byte
for {
buf := make([]byte, 1024)
n, err := response.Read(buf)
if err != nil {
break
}
body = append(body, buf[:n]...)
}

// 打印响应内容
fmt.Println(string(body))
}

proxy.go

接收客户端的请求,将PROXY写入连接中,并将请求转发给真实Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package main

import (
"bufio"
"fmt"
"log"
"net"
"strings"
)

func main() {
l, err := net.Listen("tcp", "127.0.0.1:40010")
if err != nil {
log.Fatal(err)
}
defer l.Close()

fmt.Println("proxy server listening on 127.0.0.1:40010...")

for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
continue
}
go handleProxyConnectionProtocol(conn)
}
}

func handleProxyConnectionProtocol(conn net.Conn) {
defer conn.Close()

newConn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
fmt.Println("Proxy NewConn Addr: ", newConn.LocalAddr())
defer newConn.Close()
// 对于proxy来说,这里的RemotAddr()其实就是客户端的IP
remoteAddr := strings.Split(conn.RemoteAddr().String(), ":")
localHost, localPort := remoteAddr[0], remoteAddr[1]

forwardAddr := strings.Split(newConn.LocalAddr().String(), ":")
remoteHost, remotePort := forwardAddr[0], forwardAddr[1]

proxy := fmt.Sprintf("PROXY TCP4 %s %s %s %s\r\n", localHost, remoteHost, localPort, remotePort)

n, err := newConn.Write([]byte(proxy + "GET / HTTP/1.0\r\n\r\n"))
if err != nil {
log.Fatal(err)
}
fmt.Printf("write %d bytes to newConn\n", n)

response := bufio.NewReader(newConn)
for {
line, err := response.ReadString('\n')
if err != nil {
log.Fatal(err)
}
_, err = conn.Write([]byte(line))
if err != nil {
log.Fatal(err)
}

if line == "\r\n" {
_, err = conn.Write([]byte("\r\n\r\n"))
if err != nil {
log.Fatal(err)
}
break
}
}

// 读取响应内容
var body []byte
for {
buf := make([]byte, 1024)
n, err := response.Read(buf)
if err != nil {
break
}
body = append(body, buf[:n]...)
}
_, err = conn.Write(body)
if err != nil {
log.Fatal(err)
}
}

server.go

处理来自客户端的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package main

import (
"bufio"
"fmt"
"log"
"net"
"strings"
)

func main() {
l, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
defer l.Close()
fmt.Println("server protocol listening on 127.0.0.1:8080...")

for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
continue
}

go handleServerConnectionProtocol(conn)
}
}

func handleServerConnectionProtocol(conn net.Conn) {
resp := bufio.NewReader(conn)
line, err := resp.ReadString('\n')
if err != nil {
log.Fatal(err)
}
fmt.Println(line)
s := strings.Split(line, " ")
fmt.Println("Client Addr: ", s[2]+":"+s[4])

defer conn.Close()
response := "HTTP/1.1 200 OK\r\n" +
"Content-Length: 13\r\n" +
"Content-Type: text/plain\r\n" +
"\r\n" +
"Hello, world!"
_, err = conn.Write([]byte(response))
if err != nil {
log.Fatal(err)
}

}

模拟运行

go run server.go
go run proxy.go
go run client.go
可以发现server中打印的是client的IP和端口。

小结

以上代码是对proxy protocol的一个简单模拟,只是为了更好的理解proxy protocol本身。


REF:

  1. https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt

  2. https://inkel.github.io/posts/proxy-protocol/