You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.14 06:24:00