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.
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 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.Check(AData: TStream): Boolean;
begin
if FChecked then
begin
Result := FProxyPacketReceived;
Exit;
end;
FChecked := True;
FProxyPacketReceived := Parse(AData);
Result := FProxyPacketReceived;
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;