当服务资源为 NLB 时,支持通过 ProxyProtocol v2 协议向后端服务器传递服务使用方的真实来源信息,包括客户端真实 IP 和阿里云扩展信息(终端节点 ID、VPC ID和终端节点服务 ID),可用于追溯请求来源。
工作原理
获取客户端真实 IP
ProxyProtocol v2 用于在 NLB 与后端服务器之间透传客户端连接信息。配置 NLB 监听开启 ProxyProtocol 功能后,NLB 在转发客户端请求时,会在 TCP 三次握手成功后,将客户端的真实源信息(如 IP 地址、端口等)附加到发送给后端服务器的第一个 TCP 数据包的 Payload 开头。
获取阿里云扩展信息
NLB 支持通过 ProxyProtocol v2 的 TLV(Type-Length-Value)扩展机制传递 PrivateLink 特有的标识信息。
根据 ProxyProtocol v2 协议规范,Type 值
0xE0~0xEF为用户自定义扩展的保留范围。阿里云使用0xE1作为自定义 Type 标识。阿里云扩展信息格式:
Type:
0xE1,标识阿里云自定义扩展。Length:后续数据的总长度(包含 SubType + Custom Data)。
SubType:扩展字段类型。
0x01:VPC ID。0x02:终端节点 ID。0x03:终端节点服务 ID。
Custom Data:扩展信息的具体 ID。
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ *| Type = 0xE1 | Length | SubType | *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ *| | *| | *| Custom Data | *| | *| | *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
适用范围
仅 NLB 类型的服务资源支持 ProxyProtocol v2 功能,ALB 和 CLB 不支持。
Proxy Protocol 需要后端服务器支持该协议才能正常使用。如果后端服务器不具备解析Proxy Protocol协议能力,直接开启 ProxyProtocol 功能,很可能会导致后端服务解析异常,从而影响服务可用性。
NLB 监听支持通过 Proxy Protocol 携带原始连接信息(源IP、目的IP、源端口、目的端口等)并添加到 TCP 或 UDP 数据头中,且不会丢弃或覆盖任何原有数据。
NLB 仅支持Proxy Protocol v2版本。Proxy Protocol v2版本支持多种传输协议,如TCP和UDP,更多信息,请参见The PROXY protocol。
获取客户端真实 IP
本文仅介绍服务提供方如何获取客户端真实 IP,服务私网访问的完整流程可参考共享用户自建服务。
配置 NLB 监听,开启 ProxyProtocol:前往NLB 控制台 - 实例页面,单击目标 NLB 实例 ID。在实例属性区域,确保配置修改保护未开启。
未配置监听:选择监听页签,单击创建监听,修改高级配置,启用开启ProxyProtocol开关。
已配置监听:单击监听 ID,编辑监听,修改高级配置,启用开启ProxyProtocol开关。
配置后端应用解析协议:
此处以CentOS 7.9操作系统、Nginx 1.20.1 版本配置为例。具体请以您实际使用的环境为准。
登录后端服务器,执行
nginx -t命令查看配置文件所在路径。默认通常为/etc/nginx/nginx.conf。修改 Nginx 配置文件,在
listen指令中添加proxy_protocol参数。http { # 配置日志格式,使用$proxy_protocol_addr变量记录客户端真实IP log_format main '$proxy_protocol_addr - $remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; server { # 在监听端口上添加proxy_protocol参数 listen 80 proxy_protocol; #... } }执行
sudo nginx -s reload命令,重新加载Nginx配置文件。
验证后端服务器可获取客户端真实IP:
服务使用方登录终端节点所属 VPC 的 ECS 实例,执行
curl http://<终端节点域名>。服务提供方登录后端服务器执行
tail -f /var/log/nginx/access.log查看 Nginx 访问日志:Nginx日志文件默认路径为:
/var/log/nginx/access.log。每行日志中,
$proxy_protocol_addr变量对应的IP地址即为客户端真实IP地址。
获取阿里云扩展信息
本文仅介绍服务提供方如何获取阿里云扩展信息,服务私网访问的完整流程可参考共享用户自建服务。
配置 NLB 监听,开启 ProxyProtocol:前往NLB 控制台 - 实例页面,单击目标 NLB 实例 ID。在实例属性区域,确保配置修改保护未开启。
未配置监听:选择监听页签,单击创建监听,修改高级配置,启用开启ProxyProtocol开关。
已配置监听:单击监听 ID,编辑监听,修改高级配置,启用开启ProxyProtocol开关。
调用UpdateListenerAttribute,配置
ProxyProtocolEnabled、Ppv2VpcIdEnabled、Ppv2PrivateLinkEpIdEnabled、Ppv2PrivateLinkEpsIdEnabled均为true,通过 Proxy Protocol 协议携带 PrivateLink 相关信息到后端服务器。配置后端应用解析扩展字段:
标准应用无法直接解析阿里云扩展字段。本文以
scapy抓包库为例,解析并输出阿里云扩展信息。服务提供方登录后端服务器,执行
pip3 install scapy安装 scapy。保存以下脚本为
parse_ppv2_extensions.py:#!/usr/bin/env python3 from scapy.all import sniff import argparse import sys # 禁用输出缓冲 sys.stdout = open(sys.stdout.fileno(), mode='w', buffering=1) sys.stderr = open(sys.stderr.fileno(), mode='w', buffering=1) ppv2_signature = [13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10] # "\r\n\r\n\0\r\nQUIT\n" ep_id = "null" vpc_id = "null" eps_id = "null" client_ip = "" client_port = 0 endpoint_ip = "" endpoint_id = "" parser = argparse.ArgumentParser(description="input parameter") parser.add_argument('--dstport', default='destination port', required=True) args = parser.parse_args() rs_port = args.dstport ppv2_cnt = 0 def convert_list_to_string(l): tmp = "" for c in l: tmp += chr(c) return tmp def convert_list_to_ip(l): return ".".join(map(str, l)) def pack_callback(packet): global endpoint_id, client_port, client_ip, vpc_id, ppv2_cnt, endpoint_ip, eps_id if ('Raw' in packet): byte_list = list(packet['Raw'].load) if (byte_list[0:12] == ppv2_signature): ppv2_cnt += 1 client_ip = convert_list_to_ip(byte_list[16:20]) endpoint_ip = convert_list_to_ip(byte_list[20:24]) client_port = byte_list[24] * 256 + byte_list[25] tlv = byte_list[28:] i = 0 while i < len(tlv): tlv_type = tlv[i] tlv_length = tlv[i + 1] * 256 + tlv[i + 2] tlv_value_first_byte = tlv[i + 3] tlv_value_true_value = tlv[i + 4: i + 4 + tlv_length - 1] if tlv_type == 225: # 0xE1 if tlv_value_first_byte == 1: # vpc_id vpc_id = convert_list_to_string(tlv_value_true_value) if tlv_value_first_byte == 2: # endpoint_id endpoint_id = convert_list_to_string(tlv_value_true_value) if tlv_value_first_byte == 3: # eps_id eps_id = convert_list_to_string(tlv_value_true_value) i += 1 + 2 + tlv_length print("receive total %d ppv2 packet, ClientIp: %s, ClientPort: %d, EndpointIp: %s, EndpointId: %s, VpcId: %s, EndpointServiceId: %s" % ( ppv2_cnt, client_ip, client_port, endpoint_ip, endpoint_id, vpc_id, eps_id)) sys.stdout.flush() # 强制刷新输出 filterstr = "ip and tcp and dst port " + rs_port print("start to capture ppv2 packet on port " + rs_port) sys.stdout.flush() sniff(iface="eth0", filter=filterstr, prn=pack_callback)执行
sudo python3 parse_ppv2_extensions.py --dstport < NLB服务器组的后端服务端口>。服务使用方登录终端节点所属 VPC 的 ECS 实例,执行
curl http://<终端节点域名>访问服务后,可在后端服务器查看到阿里云扩展信息。

