Atozed Forums

Full Version: How to create a "TCP/IP relay" server?
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I am using Indy in a server for handling external equipment which is monitoring dams and waste dumps for leakage.
This system might be powered by solar cells on very remote locations.

The server has been ported from Delphi on Windows to FreePascal on Linux and runs on a RaspberryPi4 device.
The old client on Windows works also with this new server but to accommodate the differences between Windows and Linux I have had to also port the client application to FreePascal/Lazarus and add Unix/Windows difference handling.
When doing this I have replaced the "native" Delphi TCP/IP components with Indy server and client objects in both applications.

So now (almost) everything is running as intended but I have run up against a connectivity problem...

When the system is deployed it will use a mobile broadband connection to the Internet and I have found that the way these are done today often (always?) do not include for the router to get a public IP address so that one can use DDNS to find the address and connect to the server on RPi4 for configuration, monitoring and data download.

So now I wonder if there is a way to use Indy to make some kind of "relaying server" that could run on a Linux box with public IP to which the RPi4 remote server can connect and to which our client could also connect and specify which system it wants to talk to, whereupon the relaying server channels all traffic from the client over to the connected remote server and vice versa (if it is actually connected) so that both can start talking to each other using our already established specific protocol?

I don't know what this kind of operation could be called so it is hard to google, I hope you get the idea anyway...

(Also posted on the Lazarus forum)
Indy has a mapped relay component for this.
Are you referring to the TIdMappedPortTCP Class? I have searched the documentation for "mapped" and this is the closest match I found.
Are there any examples available on how to use this class?

Can a socket server connect to this component and then use the other end of it as its own listening socket?
I need to have the unreachable server use the mapped port as its own listening port but located on a reachable server.
The mapped port should act as if it were the main socket on the unreachable server.

Then another computer could connect to the hidden server by using the mapped port on the reachable server.
Is this possible?

Notice that the actual server is behind a router with a non-public IP address so it needs to extend its listening port to a computer which does have a public IP...
(04-24-2021, 01:42 PM)BosseB Wrote: [ -> ]So now I wonder if there is a way to use Indy to make some kind of "relaying server" that could run on a Linux box with public IP to which the RPi4 remote server can connect and to which our client could also connect and specify which system it wants to talk to, whereupon the relaying server channels all traffic from the client over to the connected remote server and vice versa (if it is actually connected) so that both can start talking to each other using our already established specific protocol?

If the RPi4's IP is dynamic, you can accomplish this by using a combination of TIdMappedPortTCP with either TIdTCPServer (if you need multiple RPi4's) or TIdSimpleServer (if you need only 1 RPi4) on the relay server, and then having the RPi4 use a client socket to connect to the relay, and a server socket to accept clients.

Each RPi4 client would connect to the relay's TIdTCPServer/TIdSimpleServer so the relay can see the RPi4's current IP (in the AContext.Binding.PeerIP or TIdSimpleServer.Binding.PeerIP property, respectively) and save it somewhere for later use. The RPi4 can then disconnect at this point, and reconnect whenever its IP changes.

Then, outside clients can connect to the relay's TIdMappedPortTCP. You can use its OnConnect event to determine (such as by communicating with the client) which saved IP to forward to, and then assign that IP to the TIdTCPClient(TIdMappedPortContext(AContext).OutboundClient).Host property. When the event handler exits, TIdMappedPortTCP will then connect to the assigned IP, and forward data back and forth between the outside client and RPi4 for the lifetime of those connections.

On the other hand, if the RPi4's IP is static (or the RPi4 can publish its current IP somewhere the relay can read it from), then you don't really need the TIdTCPServer/TIdSimpleServer at all. Just provide your relay with the necessary IP(s) up front using some kind of configuration setup in the relay's code (or make the relay read the published info periodically).

(04-25-2021, 08:46 PM)BosseB Wrote: [ -> ]Can a socket server connect to this component and then use the other end of it as its own listening socket?

No. They would be independent listening sockets. The TIdMappedPortTCP would first accept an incoming connection from an outside client, and then create its own client socket to connect to the RPi4's server, and then pass data back and forth between the 2 connections.
Remy, thanks for the clarifications!
I thought as much too but when I saw the reference by @kudzu to TIdMappedPortTCP I just had to ask so I would not re-invent the wheel.
About 4-5 years back I used this component but that was for a different purpose, to connect to a wifi network available on the remote RPi4 but running through VPN all the way from Sweden to Texas. That was elegantly solved by this class (and some advice from you!). That solution was hand-in-glove what the class was created for.
The Rpi4 where the server runs has a DHCP generated address, but it could easily be set as a fixed address either by reservation in the router or otherwise. In fact the port forward idea would necessitate that if that was the solution.

So now I have 2 options:
1) Create the relaying application on the intermediate server by using two server sockets, one for the RPi4 to connect to and the other for the config client to connect to. There are some hairy details I need to handle to make that work, for instance the configuration needs to be done such that the individual RPi4 device can be identified and channeled to as needed (if it is connected). I also need to modify the server to run in this way by connecting to the relaying server etc.
And it needs to handle multiple connection pairs.

2) Or else, as has been suggested, set up an OpenVPN server to which a daemon on the Rpi4 would connect and likewise the client computer needs to connect to and then when both are on the VPN communications should be possible from the client by using the server's VPN tunnel address. I can set up the VPN such that each connecting RPi4 gets a unique tunnel address so the client can select which RPi4 to connect to based on this IP.

Right now I am leaning towards the second solution which looks like a lot less work to implement.
I don't have to modify existing code just add the openvpn client to the two endpoint systems and configure the OpenVPN server in the middle...

Thanks for your enlightenment!
(As always)