Search code examples
delphiproxyindylazarus

Can I make a TIdMappedPortTCP proxy specific clients to different destinations?


Using Indy Internet Direct for Delphi, with Lazarus on Linux, I developed an application which acts as a proxy with the purpose of redirecting clients to different content based on their IP address.

To do this, I used the TIdMappedPortTCP object.

Some of the things I tried were:

  1. To set the OnConnect notify event, and change the AContext.Binding.Port.
  2. The one above, but the OnBeforeConnect notify event instead.
  3. Again to change the Binding.Port but on OnContextCreate.
  4. Changing the TIdMappedPortTCP.MappedPort

Attempt

As an example, I have attempt number 2, which overrides OnBeforeConnect. I assume this attempt failed as AContext.Connection.Connected is already true at this stage, but I cannot find a way to modify It earlier than this.

procedure TMapperApplication.OnBeforeConnect(AContext: TIdContext);
var
  IP, Host: string;
var
  I: integer;
begin
  // List
  Inc(ConnectionCount);
  AddConnection(AContext);

  // Write
  WriteDate;

  IP := AContext.Binding.PeerIP;
  Host := GStack.HostByAddress(IP);

  TConsole.TextColor := TConsoleColor.LightMagenta;
  TConsole.Write := 'Incoming connection from ';
  TConsole.TextColor := TConsoleColor.LightBlue;
  TConsole.Write := Format('"%S" (%S) on port %D', [Host, IP, AContext.Binding.Port]);

  WriteNewLn;

  // Common date
  WriteDate;

  // Redirect port
  for I := 0 to High(Servers) do
    if IP = Servers[I].Source then
      begin
        AContext.Binding.IP:=Servers[I].Host;
        AContext.Binding.Port:=Servers[I].Port;

        TConsole.TextColor := TConsoleColor.Yellow;
        TConsole.Write := 'Mapped connection to ';
        TConsole.ResetStyle;
        TConsole.WriteLn:=Format('%S:%D', [Servers[I].Host, Servers[I].Port]);
        ResetNewLn;
        Exit;
      end;

  // Default mapping
  if EnableDefault then
    begin
      AContext.Binding.IP:=Servers[I].Host;
      AContext.Binding.Port:=Servers[I].Port;
      Mapper.Active:=true;

      TConsole.WriteLn:='Mapped connection to default host.';
      Exit;
    end;

  // Mapping failed
  TConsole.TextColor:=TConsoleColor.Red;
  TConsole.WriteLn:='Connection was dropped. No mapping found.';
  AContext.Connection.Disconnect;
  TConsole.ResetStyle;
end; 

Why use IP to differentiate the client?

After I tackle this issue, I plan to replace the IP address detection, and use a way to detect server1.site.com and server2.site.com, but I'm unsure If that's even possible in Delphi with the available libraries. I tried GStack.HostByAddress(AContext.Binding.PeerIP), but that returns an empty string. A problem for another time.

Research

I would have also searched www.indyproject.org, but It seems the web server is down at the moment. The downloads do not work. There is also this, but It does not provide any information useful for this problem.

I also found this old forum, but unfortunately the solution seems to be outdated as TIdMappedPortThread is an undefined declaration and is not used in the TIdMappedPortTCP. Also this, but It's also outdated.

So currently, I have exhausted all the ideas I have. Maybe creating a custom TIdMappedPortTCP could help? But I'm unsure at this point.


Solution

  • The older solution you found is for Indy 9, but the same approach still applies in Indy 10. Simply replace TIdMappedPortThread with TIdMappedPortContext in Indy 10.

    You cannot change the AContext.Binding.IP/Port, that is the listening IP/port where the client connected to your proxy.

    To change where the proxy forwards to next on a per-connection basis (ie, not using the MappedHost/MappedPort properties), then you have to use the OnConnect event not the OnBeforeConnect event, and cast AContext to TIdMappedPortContext, then cast its OutboundClient to TIdTCPClient, and then set its Host and Port as needed.

    procedure TMapperApplication.OnConnect(AContext: TIdContext);
    begin
      ...
      with TIdTCPClient(TIdMappedPortContext(AContext).OutboundClient) do
      begin
        Host := ...;
        Port := ...;
      end;
      ...
    end;