如何在Delphi中获取启用Proxy Protocol V2的负载均衡/全代理后真实客户端IP
Delphi 环境下 Proxy Protocol V2 真实客户端IP获取实现
核心逻辑说明
Proxy Protocol V2 是负载均衡设备常用的客户端地址透传协议,报文头部以12字节固定签名开头,后续携带地址族、传输协议、源/目标地址与端口信息,仅需在TCP连接建立后,优先读取头部解析即可得到真实客户端IP。
通用解析函数实现
unit ProxyProtocolV2Parser; interface uses SysUtils, WinSock; const // Proxy Protocol V2 固定签名 PPV2_SIGNATURE: array[0..11] of Byte = ($0D, $0A, $0D, $0A, $00, $0D, $0A, $51, $55, $49, $54, $0A); PPV2_CMD_PROXY = $02; PPV2_AF_INET = $10; PPV2_AF_INET6 = $20; PPV2_TRANSPORT_STREAM = $01; type TPPV2Header = packed record Signature: array[0..11] of Byte; VerCmd: Byte; FamilyTransport: Byte; AddrLen: Word; end; TPPV2AddrIPv4 = packed record SrcAddr: TInAddr; DstAddr: TInAddr; SrcPort: Word; DstPort: Word; end; TPPV2AddrIPv6 = packed record SrcAddr: TIn6Addr; DstAddr: TIn6Addr; SrcPort: Word; DstPort: Word; end; function ParsePPV2Header(const Buffer: TBytes; out SrcIP, DstIP: string; out SrcPort, DstPort: Word): Boolean; implementation function ParsePPV2Header(const Buffer: TBytes; out SrcIP, DstIP: string; out SrcPort, DstPort: Word): Boolean; var Header: TPPV2Header; AddrIPv4: TPPV2AddrIPv4; AddrIPv6: TPPV2AddrIPv6; AddrLen: Integer; begin Result := False; SrcIP := ''; DstIP := ''; SrcPort := 0; DstPort := 0; // 校验缓冲区长度是否足够容纳基础头部 if Length(Buffer) < SizeOf(TPPV2Header) then Exit; Move(Buffer[0], Header, SizeOf(TPPV2Header)); // 校验签名是否为PPV2 if not CompareMem(@Header.Signature, @PPV2_SIGNATURE, SizeOf(PPV2_SIGNATURE)) then Exit; // 校验是代理命令 if (Header.VerCmd and $0F) <> PPV2_CMD_PROXY then Exit; AddrLen := ntohs(Header.AddrLen); if Length(Buffer) < SizeOf(TPPV2Header) + AddrLen then Exit; // 解析IPv4地址 if (Header.FamilyTransport and $F0) = PPV2_AF_INET then begin if AddrLen < SizeOf(TPPV2AddrIPv4) then Exit; Move(Buffer[SizeOf(TPPV2Header)], AddrIPv4, SizeOf(TPPV2AddrIPv4)); SrcIP := inet_ntoa(AddrIPv4.SrcAddr); DstIP := inet_ntoa(AddrIPv4.DstAddr); SrcPort := ntohs(AddrIPv4.SrcPort); DstPort := ntohs(AddrIPv4.DstPort); Result := True; end // 解析IPv6地址 else if (Header.FamilyTransport and $F0) = PPV2_AF_INET6 then begin if AddrLen < SizeOf(TPPV2AddrIPv6) then Exit; Move(Buffer[SizeOf(TPPV2Header)], AddrIPv6, SizeOf(TPPV2AddrIPv6)); SrcIP := WideCharToString(inet_ntop(AF_INET6, @AddrIPv6.SrcAddr, nil, 0)); DstIP := WideCharToString(inet_ntop(AF_INET6, @AddrIPv6.DstAddr, nil, 0)); SrcPort := ntohs(AddrIPv6.SrcPort); DstPort := ntohs(AddrIPv6.DstPort); Result := True; end; end; end.
Synapse 组件调用示例
使用Synapse的TTCPBlockSocket组件时,在接受客户端连接后优先读取头部解析即可:
uses ..., blcksock, ProxyProtocolV2Parser; procedure TForm1.ServerAccept(Sender: TObject; Client: TTCPBlockSocket); var HeadBuffer: TBytes; ReadLen: Integer; RealIP, DstIP: string; RealPort, DstPort: Word; begin // 先读取12字节PPV2头部签名 SetLength(HeadBuffer, SizeOf(TPPV2Header)); ReadLen := Client.RecvBuffer(@HeadBuffer[0], SizeOf(TPPV2Header), 5000); // 超时5秒可自行调整 if ReadLen = SizeOf(TPPV2Header) then begin // 判断是否为PPV2报文 if CompareMem(@HeadBuffer[0], @PPV2_SIGNATURE, SizeOf(PPV2_SIGNATURE)) then begin // 读取剩余的地址段长度 SetLength(HeadBuffer, SizeOf(TPPV2Header) + ntohs(PWord(@HeadBuffer[14])^)); ReadLen := Client.RecvBuffer(@HeadBuffer[SizeOf(TPPV2Header)], Length(HeadBuffer) - SizeOf(TPPV2Header), 5000); if ReadLen = Length(HeadBuffer) - SizeOf(TPPV2Header) then begin if ParsePPV2Header(HeadBuffer, RealIP, DstIP, RealPort, DstPort) then begin // 此处RealIP即真实客户端IP,可自行存储使用 ShowMessage('真实客户端IP:' + RealIP); end; end; end else begin // 非PPV2报文,将已读取的12字节塞回缓冲区,供后续业务读取 Client.PeekBuffer := HeadBuffer; // 无代理时直接取远端IP RealIP := Client.GetRemoteSinIP; end; end; // 后续正常处理业务逻辑 end;
注意事项
- 仅当确认上游负载均衡启用了Proxy Protocol V2时可使用该逻辑,否则会误解析普通业务报文
- 超时时间可根据业务场景自行调整,避免异常连接阻塞服务
- 如果使用Indy等其他Socket组件,仅需调整读取缓冲区、塞回预读数据的逻辑,核心解析函数可直接复用
内容的提问来源于stack exchange,提问作者pcpestolante
相关产品推荐
相关产品推荐

