Get host ip address behind load balancer using proxy protocol.

Download Source Code

The PROXY protocol provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT, TCP proxies or load balancers. The connection information comes within the first portion of bytes after the connection has been established. The described code parses this information and removes the proxy header packet from the data stream.

The PROXY protocol

As an example, consider running the IMAP server component behind an Amazon load balancer (ELB). This load balancer can forward the client IP using proxy protocol version 1. We need to override the ReadData method of the IMAP connection class and call to the Check method of the proxy parser class.

Optionally, the parser can remove the proxy information from the data stream. Otherwise, the ImapServer component will try to extract the proxy header as IMAP command and as a result, the "unknown command" error occurs.

The extracted connection information can be accessed anytime by using of the TProxiedConnection.Proxy property. E.g., you can read this information within the OnReceiveCommand event handler of the ImapServer component. You will need to typecast the provided connection instance to your TProxiedConnection type in order to access the Proxy property. The TProxiedConnection instance is created by using of the OnCreateConnection event of the ImapServer component.

procedure TMainForm.clImap4Server1CreateConnection(Sender: TObject; var AConnection: TclUserConnection);
begin
  AConnection := TProxiedConnection.Create();
end;

procedure TMainForm.clImap4Server1ReceiveCommand(Sender: TObject; AConnection: TclCommandConnection;
  ACommandParams: TclTcpCommandParams);
var
  connection: TProxiedConnection;
begin
  connection := AConnection as TProxiedConnection;
 
  if not connection.ConnectionInfoRead then
  begin
    connection.ConnectionInfoRead := True;
 
    PutLogMessage('Source IP: ' + connection.Proxy.SourceAddress + ', Port: ' + IntToStr(connection.Proxy.SourcePort));
    PutLogMessage('Destination IP: ' + connection.Proxy.DestinationAddress + ', Port: ' + IntToStr(connection.Proxy.DestinationPort));
  end;
 
  PutLogMessage('Command: ' + ACommandParams.Command + ' ' + ACommandParams.Parameters);
end;

TProxiedConnection = class(TclImap4CommandConnection)
private
  FProxy: TProxyHeaderParser;
  FConnectionInfoRead: Boolean;
protected
  procedure DoDestroy; override;
public
  constructor Create;
 
  function ReadData(AData: TStream): Boolean; override;
 
  property Proxy: TProxyHeaderParser read FProxy;
  property ConnectionInfoRead: Boolean read FConnectionInfoRead write FConnectionInfoRead;
end;

function TProxyHeaderParser.Check(AData: TStream): Boolean;
begin
  if FChecked then
  begin
    Result := FProxyPacketReceived;
    Exit;
  end;
  FChecked := True;
 
  FProxyPacketReceived := Parse(AData);
 
  Result := FProxyPacketReceived;
end;

function TProxyHeaderParser.Parse(AData: TStream): Boolean;
const
  ProxyLexem = 'PROXY ';
var
  buf, crlf: TclByteArray;
  eofBuf, len: Integer;
begin
  Result := False;
 
  Init();
 
  if (AData.Size < Length(ProxyLexem)) then Exit;
 
  AData.Position := 0;
 
  SetLength(buf, Length(ProxyLexem));
  AData.ReadBuffer(buf, Length(ProxyLexem));
 
  if (TclTranslator.GetString(buf, 'us-ascii') <> ProxyLexem) then Exit;
 
  len := 108 - Length(ProxyLexem);
  if (AData.Size - AData.Position < len) then
  begin
    len := AData.Size - AData.Position;
  end;
  SetLength(buf, len);
 
  AData.ReadBuffer(buf, len);
 
  crlf := TclTranslator.GetBytes(#13#10, 'us-ascii');
  eofBuf := BytesPos(crlf, buf, 0, len);
 
  if (eofBuf < 0) then Exit;
 
  ExtractHeader(TclTranslator.GetString(buf, 0, eofBuf, 'us-ascii'));
 
  if (FRemoveProxyHeader) then
  begin
    RemoveHeader(AData, eofBuf + Length(crlf) + Length(ProxyLexem));
  end;
 
  AData.Position := 0;
end;

procedure TProxyHeaderParser.ExtractHeader(const AHeader: string);
var
  list: TStrings;
begin
  list := TStringList.Create();
  try
    SplitText(AHeader, list, ' ');
 
    if (list.Count < 5) then Exit;
 
    FProxiedProtocol := list[0];
    FSourceAddress := list[1];
    FDestinationAddress := list[2];
    FSourcePort := StrToIntDef(list[3], 0);
    FDestinationPort := StrToIntDef(list[3], 0);
  finally
    list.Free();
  end;
end;

procedure TProxyHeaderParser.RemoveHeader(AData: TStream; ACount: Integer);
var
  buf: TclByteArray;
  len: Integer;
begin
  AData.Position := ACount;
 
  len := Integer(AData.Size - AData.Position);
  SetLength(buf, len);
 
  AData.ReadBuffer(buf, len);
 
  AData.Size := 0;
 
  AData.WriteBuffer(buf, len);
end;

Article ID: 84, Created On: 11/24/2016, Modified: 11/24/2016