Jump to content
Keesver

Chain of two HttpProxyServer servers

Recommended Posts

Hello,

We want to add remote support to our application and for this we want to use two idHttpProxyServer's chained together. The first server resides on our public server, the second instance resides on the customers network. This is the code we are using:

 

public server:

constructor TRemoteServer.Create(IP: string; Port: Integer);
begin
  IdHTTPProxyServer1 := TIdHTTPProxyServer.Create(nil);
  IdHTTPProxyServer1.ReuseSocket := rsTrue;
  IdHTTPProxyServer1.DefaultPort := Port;

  IdHTTPProxyServer1.OnHTTPBeforeCommand := HTTPBeforeCommandHandler;

  var handler := IdHTTPProxyServer1.CommandHandlers.Add;
  handler.Command := 'DEST';
  handler.HelpSuperScript := 'Sets caller as a DESTINATION for remote support (usage DEST PIN URL)';
  handler.OnCommand := DEST_Handler;

  if (IP <> '') and (Port <> 0) then
  begin
    Writeln('Starting server, binding to: ' + IP + '-' + Port.ToString);
    var bind := IdHTTPProxyServer1.Bindings.Add;
    bind.IP := IP;
    bind.Port := Port;
    bind.ReuseSocket := rsTrue;
  end else
    Writeln('Starting server, default binding');

  IdHTTPProxyServer1.Active := True;

  Writeln('Server running, listening on: ' + IdHTTPProxyServer1.Bindings[0].IP + ' - ' + IdHTTPProxyServer1.Bindings[0].Port.ToString);
end;

procedure TRemoteServer.DEST_Handler(ASender: TIdCommand);
begin
  Writeln(ASender.RawLine);
  dest_ip := ASender.Context.Binding.PeerIP;
  dest_port := ASender.Context.Binding.PeerPort;
  dest_pin := ASender.Params[0];
  dest_url := ASender.Params[1];
  Writeln('Address: ' + dest_ip + ' - ' + dest_port.ToString);
end;

procedure TRemoteServer.HTTPBeforeCommandHandler(AContext: TIdHTTPProxyServerContext);
begin
  if dest_ip <> '' then
  begin
    Writeln('Command redirected to DESTINATION: ' + dest_ip + ' - ' +  dest_port.ToString);

    var tempIO := TIdIOHandlerStack.Create(AContext.OutboundClient);
    tempIO.ReuseSocket := rsTrue;
    // tempIO.BoundIP := '10.0.2.4'; //IdHTTPProxyServer1.Bindings[0].IP;
    tempIO.BoundPort := 443; // IdHTTPProxyServer1.Bindings[0].Port;

    var tempProxy := TIdConnectThroughHttpProxy.Create(AContext.OutboundClient);
    tempProxy.Enabled := True;
    tempProxy.Host := dest_ip;
    tempProxy.Port := dest_port;
    tempIO.TransparentProxy := tempProxy;

    AContext.OutboundClient.IOHandler := tempIO;
  end;
end;

Client code:

procedure TForm1.ConnectToRemote(Connection: string);
begin
  HTTPProxyServer.Active := False;
  IdTCPClient1.Disconnect;

  IdTCPClient1.Host := <SERVER IP/URL>; //Connection.Split([':'])[0];
  IdTCPClient1.Port := <SERVER PORT>; // Connection.Split([':'])[1].ToInteger;
  IdTCPClient1.Connect;

  // Tell server we are there
  IdTCPClient1.SendCmd('DEST 4444 https://abc.com');

  HTTPProxyServer.Bindings.Clear;
  var bind := HTTPProxyServer.Bindings.Add;
  bind.IP := IdTCPClient1.Socket.Binding.IP;
  bind.Port := IdTCPClient1.Socket.Binding.Port;
  bind.ReuseSocket := rsTrue;

  HTTPProxyServer.Active := True;
end;

This setup works when we run both server and client on the same computer. However, when we put the server on a remote computer, it does not work. 

 

Should this work, or do we need other means to make this happen?

 

Thanks and regards,

Kees

Share this post


Link to post

You should investigate at what exact phase the error occurs. Connection to host:port? Sending request? Receiving request? Processing request?

Share this post


Link to post

There is no error, the call from the remote server back into the client simply does not reach it's destination. The remote server does receive a HTTPBeforeCommand event, this line is being called on the server: 

Writeln('Command redirected to DESTINATION: ' + dest_ip + ' - ' +  dest_port.ToString);

The connection between client and server seems to be working. When I use telnet to connect to the server, I do get a response from the server when I execute the 'HELP' command. Similar the call to SendCmd('DEST') in our client returns '200 OK'. Also 'netstat' shows an active connection between client and server.

 

Maybe my question is: what do I need more that an active (TCP) connection between client and server? 

Does the HTTP Proxy server requires additional channels to be opened to make this work? Or should the single channel suffice?

Share this post


Link to post

Update: It works when I turn off the Firewall on my internet router (one step forward). Apparently there are other ports/channels needed to make a HTTPProxy work? Investigating......

Share this post


Link to post
2 hours ago, Keesver said:

Apparently there are other ports/channels needed to make a HTTPProxy work?

More likely firewall blocks your custom app but allows built-in telnet

Share this post


Link to post

On the client side, I would strongly advise you NOT to bind the TIdTCPClient and TIdHTTPProxyServer to the same local IP/Port at all.  Put the destination proxy server's IP/Port in the DEST command itself, and then have the receiving server extract the values from the command, not from the connected socket.  Then you can drop the TIdTCPClient connection after sending the DEST command, and even run the TIdTCPClient on a different PC if you wanted to.

constructor TRemoteServer.Create(IP: string; Port: Integer);
begin
  IdHTTPProxyServer1 := TIdHTTPProxyServer.Create(nil);
  IdHTTPProxyServer1.ReuseSocket := rsTrue;
  IdHTTPProxyServer1.DefaultPort := Port;

  IdHTTPProxyServer1.OnHTTPBeforeCommand := HTTPBeforeCommandHandler;

  var handler := IdHTTPProxyServer1.CommandHandlers.Add;
  handler.Command := 'DEST';
  handler.HelpSuperScript := 'Sets caller as a DESTINATION for remote support (usage DEST PIN IP PORT URL)';
  handler.OnCommand := DEST_Handler;

  if (IP <> '') and (Port <> 0) then
  begin
    Writeln('Starting server, binding to: ' + IP + '-' + Port.ToString);
    var bind := IdHTTPProxyServer1.Bindings.Add;
    bind.IP := IP;
    bind.Port := Port;
    bind.ReuseSocket := rsTrue;
  end else
    Writeln('Starting server, default binding');

  IdHTTPProxyServer1.Active := True;

  Writeln('Server running, listening on: ' + IdHTTPProxyServer1.Bindings[0].IP + ' - ' + IdHTTPProxyServer1.Bindings[0].Port.ToString);
end;

procedure TRemoteServer.DEST_Handler(ASender: TIdCommand);
begin
  Writeln(ASender.RawLine);
  dest_pin := ASender.Params[0];
  dest_ip := ASender.Params[1];
  dest_port := ASender.Params[2];
  dest_url := ASender.Params[3];
  Writeln('Address: ' + dest_ip + ' - ' + dest_port.ToString);
end;

procedure TRemoteServer.HTTPBeforeCommandHandler(AContext: TIdHTTPProxyServerContext);
begin
  if dest_ip <> '' then
  begin
    Writeln('Command redirected to DESTINATION: ' + dest_ip + ' - ' +  dest_port.ToString);

    var tempIO := TIdIOHandlerStack.Create(AContext.OutboundClient);
    tempIO.ReuseSocket := rsTrue;
    // tempIO.BoundIP := '10.0.2.4'; //IdHTTPProxyServer1.Bindings[0].IP;
    tempIO.BoundPort := 443; // IdHTTPProxyServer1.Bindings[0].Port;

    var tempProxy := TIdConnectThroughHttpProxy.Create(AContext.OutboundClient);
    tempProxy.Enabled := True;
    tempProxy.Host := dest_ip;
    tempProxy.Port := dest_port;
    tempIO.TransparentProxy := tempProxy;

    AContext.OutboundClient.IOHandler := tempIO;
  end;
end;
procedure TForm1.ConnectToRemote(Connection: string);
begin
  HTTPProxyServer.Active := False;

  HTTPProxyServer.Bindings.Clear;
  var bind := HTTPProxyServer.Bindings.Add;
  bind.IP := ...;
  bind.Port := ...;
  bind.ReuseSocket := rsTrue;

  HTTPProxyServer.Active := True;

  // Tell server we are there
  IdTCPClient1.Host := <SERVER IP/URL>; //Connection.Split([':'])[0];
  IdTCPClient1.Port := <SERVER PORT>; // Connection.Split([':'])[1].ToInteger;
  IdTCPClient1.Connect;
  try
    IdTCPClient1.SendCmd('DEST 4444 ' + bind.IP + ' ' + bind.Port.ToString + ' https://abc.com');
  finally
    IdTCPClient1.Disconnect;
  end;
end;

 

Share this post


Link to post

I have done some more testing and found that my setup fails because the proxy on the server tries to connect to the local proxy from a different port number/connection. This communication is (logically) blocked by the firewall. 

 

I thought this code made the server proxy communicate through the same channel as previously established between client and server:

    var tempIO := TIdIOHandlerStack.Create(AContext.OutboundClient);
    tempIO.OnBeforeBind := IOHandlerStack_BeforeBind;
    tempIO.ReuseSocket := rsTrue;
    tempIO.BoundPort := IdHTTPProxyServer1.Bindings[0].Port; <== Re-use outbound port from server to prevent blocking by firewall
Quote

On the client side, I would strongly advise you NOT to bind the TIdTCPClient and TIdHTTPProxyServer to the same local IP/Port at all.  Put the destination proxy server's IP/Port in the DEST command itself, and then have the receiving server extract the values from the command, not from the connected socket.  Then you can drop the TIdTCPClient connection after sending the DEST command, and even run the TIdTCPClient on a different PC if you wanted to.

I think this cannot work because the server proxy cannot reach the local proxy through the firewall. The connection between the two proxies must be initiated from the "inside".

 

This is what I'm trying to achieve:

 

Remote <x> Server proxy <x> local (reverse) proxy < > Internal network
'x' = firewall

 

1. local proxy opens connection with Server proxy
2. local proxy sends 'DEST commend' to register itself with Server proxy
3. Remote queries Server proxy for available clients
4. Remote sends 'REMOTE' command to server to to connect to a certain 'DEST'
5. Remote starts HTTP communication with given 'DEST' ==> here it failes because server proxy opens new channel to local proxy

 

Maybe my setup will never work and should I follow a very different path. Hints and tips more than welcome.


Greetings

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×