由于在家里搭建了博客,Git 等服务, 经过了阿里云用 Nginx 做了 TCP Proxy, 导致了一个问题,家里看到的服务访问 IP 都是转发服务器的 IP,无法看到真实IP。联想到平常 LB 上的是支持 PROXY Protocol 转发真实IP, 于是搜了一下,Nginx 还真支持!

PROXY Protocol

什么是 PROXY Protocol ? 其实很简单,就是为了解决多层NET或TCP转发时 无法获取客户端真实IP的问题,在 TCP 第一行加入了一些信息标识协议、客户端地址、转发地址以及端口等。目前有 v1 和 v2 两个版本。

v1 格式如下:

# PROXY 传输协议 客户端IP 目标IP 客户端端口 目标端口
PROXY_STRING + single space + INET_PROTOCOL + single space + CLIENT_IP + single space + PROXY_IP + single space + CLIENT_PORT + single space + PROXY_PORT + "\r\n"

示例

# IPV4
PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n
# IPV6
PROXY TCP6 2001:DB8::21f:5bff:febf:ce22:8a2e 2001:DB8::12f:8baa:eafc:ce29:6b2e 35646 80\r\n
# 未知
PROXY UNKNOWN\r\n

v2 格式如下:

  • 12 字节固定签名

    \x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A
    
  • 13字节包含协议版本和命令

    # 版本
    \x2
    # 命令
    \x0  # LOCAL ,客户段直连
    \x1  # PROXY ,进过转发
    
  • 第 14 个字节包含传输协议和地址族

    # 传输协议
    \x0 # AF_UNSPEC
    \x1 # AF_INET  (IPv4)
    \x2 # AF_INET6 (IPv6)
    \x3 # AF_UNIX  (UNIX)
    # 地址族
    \x0 # UNSPEC
    \x1 # STREAM  (如:TCP 或 UNIX_STREAM)
    \x2 # DGRAM   (如:UDP 或 UNIX_DGRAM)
    
  • 第15、16字节为长度标记(剩下的报头长度)

    # 前面的结构
        struct proxy_hdr_v2 { 
            uint8_t sig[12]; /* 十六进制 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ 
            uint8_t ver_cmd; /* 协议版本和命令 */ 
            uint8_t fam; /* 协议族和地址 */ 
            uint16_t len; /* 头的后续字节数 */ 
        }; 
    
  • 接下来就是

    • 源第三层地址
    • 目标第三层地址
    • 源第四层地址(如果有)
    • 目标第四层地址(如果有)
      union proxy_addr { 
            struct { /* for TCP/ UDP over IPv4, len = 12 */ 
                uint32_t src_addr; 
                uint32_t dst_addr; 
                uint16_t src_port; 
                uint16_t dst_port;
            } ipv4_addr; 
            struct { /* for TCP/UDP over IPv6, len = 36 */ 
                 uint8_t src_addr[16]; 
                 uint8_t dst_addr[16]; 
                 uint16_t src_port; 
                 uint16_t dst_port; 
            } ipv6_addr; 
            struct { /* 对于 AF_UNIX 套接字,len = 216 */ 
                 uint8_t src_addr[108]; 
                 uint8_t dst_addr[108]; 
            } unix_addr; 
        }; 
    

但,要开启 PROXY Protocol , 必须在转发器和服务端同时支持该协议, 很庆幸 Nginx 早就支持了。

  • 开源版本 v1.5.12 以及之后支持v1; v1.13.11以及之后支持v2。

配置

首先转发服务上 streamstream_realip_module 模块肯定要编译到 Nginx 中去,服务端http_realip_module也要编辑进去。

然后,简单配置如下:

转发服务

stream {
	map $remote_addr $homeserv {
    default  h.razeen.me:50080;
	}

	resolver 223.5.5.5 valid=15s;

	server {
    listen 80;
    proxy_pass $homeserv;
    proxy_protocol on;
	}
}

服务端

服务端通过proxy_protocol_addr就能拿到真实IP了,于是我们可以通过Header转发到对应后面的服务。

http {
	server {
    listen                       50080 proxy_protocol;
    
    
    proxy_set_header X-Real-IP       $proxy_protocol_addr;
    proxy_set_header X-Forwarded-For $proxy_protocol_addr;
  
    ....
	}
}

这样就大功告成,成功通过Nginx TCP Proxy 获取真实客户端IP~

参考