Atozed Forums

Full Version: IdTCPClient on Android: Connecting to non-active network
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
I am not sure if the Indy system can help here, or that this is a pure Android issue (and forums.embarcadero.com is down again).

Android has an 'Active Network', which is set by the system. I get a notification from the 'ConnectionManager' when Android is changing this 'Active' network.
Problem is that Android claims the active network is "connected" when it has Internet access. NOT when it is in fact connected to a Network!

So I have this situation.
Android is connected to a WIFI network on which runs my Local Database Server. But this WIFI network does NOT have Internet access.
At the same time the Android device is also connected to a 'Mobile' network, which DOES have Internet access.
The ConnectionManager sends a notification that the Active Network is the Mobile network and that the WIFI network is NOT connected (but it is).

My App is aware that on the WIFI my Server is active (because I get a broadcast message from it via the IdUDPServer) on the WIFI network, which includes its IP address).

But when I try to connect to it, this connect request goes out over the Mobile network, NOT to the WIFI network.

So. Is there any way I can force the IdTCPClient to specifically do a 'Connect' to via the WIFI network (of which I can get all details) as opposed to the default 'Active' network?

PS:
I tried to set the BoundIP with the local IP address assigned to the WIFI network; But this does not have any effect:
{code}
IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
          IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmClient;  // sslmUnassigned
          IdSSLIOHandlerSocketOpenSSL1.SSLOptions.SSLVersions := [sslvTLSv1_2];
          IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1_2;
          IdSSLIOHandlerSocketOpenSSL1.SSLOptions.VerifyDepth := 0;
          IdSSLIOHandlerSocketOpenSSL1.OnStatusInfo := TDTBEventHandlers.IdSSLIOHandlerSocketOpenSSL1StatusInfo;
          IdSSLIOHandlerSocketOpenSSL1.OnStatus := TDTBEventHandlers.IdSSLIOHandlerSocketOpenSSL1Status;
          IdSSLIOHandlerSocketOpenSSL1.OnVerifyPeer := TDTBEventHandlers.IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
          IdSSLIOHandlerSocketOpenSSL1.PassThrough := true;

          IdTCPClient1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;
          if NetworkData.LocalIP <> '' then
          begin
            IdTCPClient1.BoundIP := NetworkData.LocalIP;
            IdSSLIOHandlerSocketOpenSSL1.BoundIP := NetworkData.LocalIP;
            LocalLog('=== TCPThread.Execute: Connect using BoundIP: '+NetworkData.LocalIP);
          end;
{code}


Thanks, Bart
(04-26-2018, 05:16 AM)BartKindt Wrote: [ -> ](and forums.embarcadero.com is down again).

https://www.atozed.com/2018/04/embt-squi...ion-again/
(04-26-2018, 05:16 AM)BartKindt Wrote: [ -> ]But when I try to connect to it, this connect request goes out over the Mobile network, NOT to the WIFI network.

Android is likely choosing to use the "active" network to make the connection. By default, Indy lets the OS pick an appropriate network interface when making outbound connections.

(04-26-2018, 05:16 AM)BartKindt Wrote: [ -> ]So. Is there any way I can force the IdTCPClient to specifically do a 'Connect' to via the WIFI network (of which I can get all details) as opposed to the default 'Active' network?

If you want to make an outbound connection using a specific network interface, you can set the client's BoundIP property to the IP address of that interface.

(04-26-2018, 05:16 AM)BartKindt Wrote: [ -> ]I tried to set the BoundIP with the local IP address assigned to the WIFI network; But this does not have any effect

Internally, the BoundIP is passed to the socket API bind() function. On most platforms, that is usually enough. But, on some systems, binding to a specific interface can also be done using the SO_BINDTODEVICE option of the socket API setsockopt() function instead (especially if you have multiple interfaces with the same IP address assigned to them). Indy does not currently support SO_BINDTODEVICE, but you can use it manually, such as in the client's OnSocketAllocated, On(Before|After)Bind, or OnStatus(hsConnecting) events, which are all fired before the socket API connect() function is called to make the connection.
(04-26-2018, 05:24 PM)rlebeau Wrote: [ -> ]Internally, the BoundIP is passed to the socket API bind() function.  On most platforms, that is usually enough.  But, on some systems, binding to a specific interface can also be done using the SO_BINDTODEVICE option of the socket API setsockopt() function instead

I spend hours trying to get my head around this.
I tried this:
IdTCPClient1.Socket.Binding.SetBinding(NetworkData.LocalIP,APort);
This has no effect. PS: What function is the Port in this case? Does this have any effect on setting the socket, and if so, what Port am I supposed to use?
Then,
SetSockOpt(ALevel: TIdSocketOptionLevel;  AOptName: TIdSocketOption; AOptVal: Integer);

Those are three integers I am supposed to supply. I have no idea what these mean.
Also, after hours digging through the Android API (24) help, I cannot find any way to retrieve an integer which I could use to call SetSockOpt() with.
https://developer.android.com/reference/...et/Network

Going through the source of Indy, it ends with an GStack.SetSocketOption(Handle, ALevel, AOptName, AOptVal); overload; virtual; abstract;
of which I cannot find the actual source code to see what it does...

PS: After I assign the "IdTCPClient1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;" , calls to the IdTCPClient1.Socket.Binding.SetBinding will be passed on to IdSSLIOHandlerSocketOpenSSL1 where required?

Thanks, Bart
(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]I tried this:
IdTCPClient1.Socket.Binding.SetBinding(NetworkData.LocalIP,APort);

You don't need to call SetBinding() manually, Indy handles the binding for you, using the values of the BoundIP and BoundPort properties.

(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]What function is the Port in this case? Does this have any effect on setting the socket, and if so, what Port am I supposed to use?


It is the local Port that the client binds to before connecting to the remote server. A TCP connection is uniquely identified by the combination of the socket's protocol, client's bound IP/Port, and the server's bound IP/Port.

(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]Then,
SetSockOpt(ALevel: TIdSocketOptionLevel;  AOptName: TIdSocketOption; AOptVal: Integer);

Those are three integers I am supposed to supply. I have no idea what these mean.

Read the Linux man documentation for the setsockopt() function, and the SO_BINDTODEVICE socket option in particular:

https://linux.die.net/man/2/setsockopt

https://linux.die.net/man/7/socket

(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]Also, after hours digging through the Android API (24) help, I cannot find any way to retrieve an integer which I could use to call SetSockOpt() with.

Android runs on top of Linux. Indy accesses Linux's socket APIs directly, it does not use Android's APIs.

(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]Going through the source of Indy, it ends with an GStack.SetSocketOption(Handle, ALevel, AOptName, AOptVal); overload; virtual; abstract;
of which I cannot find the actual source code to see what it does...


On Android, it is implemented in the Lib\System\IdStackVCLPosix.pas source file.

(04-28-2018, 05:57 AM)BartKindt Wrote: [ -> ]PS: After I assign the "IdTCPClient1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;" , calls to the IdTCPClient1.Socket.Binding.SetBinding will be passed on to IdSSLIOHandlerSocketOpenSSL1 where required?

Yes. Connect() handles that for you.
Remy,

Trying to set the outgoing network using the allocated IP address does not work.
This is what I got:
Using Delphi 10.2.3 and the default installation Indy10 10.6.2.5366
Testing on my Android 7.0 phone.

State:
- Mobile Network connected with Internet access. "Active Network" is the Mobile network.
- WIFI is enabled, the assigned IP address is 192.168.53.102
- My Local Database Server is connected to the WIFI network, and broadcasts its own IP address via the IdUDPServer (which I receive on the phone via the WIFI). Its IP address is 192.168.53.101

{code}
IdTCPClient1 := TIdTCPClient.Create(nil);
       with IdTCPClient1 do
       begin
         OnConnected := IdTCPClient1Connected;
         OnDisconnected := IdTCPClient1Disconnected;
         OnStatus := IdTCPClient1Status;
         IdTCPClient1.OnSocketAllocated := IdTCPClient1OnSocketAllocated;
       end;

       try
         IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
         IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Mode := sslmClient;  // sslmUnassigned
         IdSSLIOHandlerSocketOpenSSL1.SSLOptions.SSLVersions := [sslvTLSv1_2];
         IdSSLIOHandlerSocketOpenSSL1.SSLOptions.Method := sslvTLSv1_2;
         IdSSLIOHandlerSocketOpenSSL1.SSLOptions.VerifyDepth := 0;
         IdSSLIOHandlerSocketOpenSSL1.OnStatusInfo := IdSSLIOHandlerSocketOpenSSL1StatusInfo;
         IdSSLIOHandlerSocketOpenSSL1.OnStatus := IdSSLIOHandlerSocketOpenSSL1Status;
         IdSSLIOHandlerSocketOpenSSL1.OnVerifyPeer := IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
         IdSSLIOHandlerSocketOpenSSL1.PassThrough := true;
         IdTCPClient1.IOHandler := IdSSLIOHandlerSocketOpenSSL1;

         IdOpenSSLSetLibPath(DirNames.SSLDir);

       except
         on E:Exception do
         begin
           LocalLog('TCPThread.Execute: Creating IdSSLIOHandlerSocketOpenSSL: '+E.Message,d_error);
           sleep(1000);
           continue;   // Main Loop
         end;
       end;

       try // finally
         try // except
           with IdTCPClient1 do
           begin
             if NetworkData.LocalIP <> '' then
             begin
               BoundIP := NetworkData.LocalIP;
               BoundPort := 8051;
               LocalLog('=== TCPThread.Execute: Connect using BoundIP: '+NetworkData.LocalIP);
             end;
             host := NetworkData.DTBActiveServer;
             port := Communications.DTBPort2;
             ConnectTimeout := 10000; // Increased from 5000 to 10000 29-12-2016 v09768
             SARTrackServiceDM.SetDTBConnectStatus(IS_CONNECTPENDING);
             LocalLog('TCPThread.Execute: DTB Client trying connect to ' + host + ' on port ' + IntToStr(port));

             UAndroidPowerManagement.AcquireWakeLock;
             Connect;
             UAndroidPowerManagement.ReleaseWakeLock;

           end;
         except
           on E: Exception do
           begin
             LocalLog('TCPThread.Execute: During Connect:'+ E.Message,d_error);
             SARTrackServiceDM.SetDTBConnectStatus(IS_DISCONNECTED);
{code}

The Connect attempt is not going out over the WIFI network. It probably goes out over the Mobile network.
Reading all the documentation on the SetSockOpt() has unfortunately not helped me much, I do not understand much of it.
The Indy SetSockOpt() requires 3 integers.
The Linux doc talks about the SO_BINDTODEVICE; Which I assume is Constant?, which I cannot find anywhere.
"SO_BINDTODEVICE:   Bind this socket to a particular device like "eth0", as specified in the passed interface name."
So where do I enter this string?
And unfortunately I have still not been able to find any type of Android string or integer reference linked to a given Network, so that I can supply this to Indy. The only thing I get is the assigned IP address. (or lots of information about bandwidth, frequencies, etc.)
CORRECTION: I did find this:
"getNetworkId
int getNetworkId ()
Each configured network has a unique small integer ID, used to identify the network when performing operations on the supplicant. This method returns the ID for the currently connected network."

But no string name, and I am not sure if the ID is a Linux or internal Android ID.
I do have the MAC address.

If you could give me some example on how to use the SetSockOpt() or any other way of setting the required network, would be great...

Bart
(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]Trying to set the outgoing network using the allocated IP address does not work.

But you haven't shown proof of that yet.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]- WIFI is enabled, the assigned IP address is 192.168.53.102

Is that the IP address that you are assigning to the BoundIP?  You haven't shown how you are populating your NetwordData object.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]- My Local Database Server is connected to the WIFI network, and broadcasts its own IP address via the IdUDPServer (which I receive on the phone via the WIFI). Its IP address is 192.168.53.101

Without explicitly binding the TIdTCPClient to a specific local IP/Port, I would expect Android to be smart enough to be able to pick the appropriate network adapter that has a route to reach 192.168.53.101, which would be the WiFi adapter since 192.168.53.102 is on the same subnet as 192.168.53.101.  That is key to any OS's network routing system.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]
Code:
if NetworkData.LocalIP <> '' then
begin
  BoundIP := NetworkData.LocalIP;
  BoundPort := 8051;
  LocalLog('=== TCPThread.Execute: Connect using BoundIP: '+NetworkData.LocalIP);
end;

Are you SURE that NetworkData.LocalIP is being set to 192.168.53.102?  Is that what is in your log message?  Can you show the code that is populating NetworkData.LocalIP?

On a side note, you don't need to set a value for BoundPort, unless your router/firewall requires it.  Otherwise, I would leave it 0 and let Android pick an available port for you.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]
Code:
UAndroidPowerManagement.AcquireWakeLock;
Connect;
UAndroidPowerManagement.ReleaseWakeLock;

Make sure to wrap Connect() in a try/finally or try/except block so you release the WakeLock whether Connect() succeeds or fails.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]The Connect attempt is not going out over the WIFI network.

How do you know that for sure?  Do you have a packet sniffer running that is showing you the network packets being generated by the Android device?

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]It probably goes out over the Mobile network.

Now you are just assuming.  Stop assuming and start debugging.  Find out FOR SURE which network adapter the traffic is actually using.

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]Reading all the documentation on the SetSockOpt() has unfortunately not helped me much, I do not understand much of it.
The Indy SetSockOpt() requires 3 integers.
The Linux doc talks about the SO_BINDTODEVICE; Which I assume is Constant?, which I cannot find anywhere.
"SO_BINDTODEVICE:   Bind this socket to a particular device like "eth0", as specified in the passed interface name."
So where do I enter this string?

You won't be able to use Indy's SetSockOpt() wrapper in this situation (nor did I tell you to do so), as SO_BINDTODEVICE does not pass in integer data as input, per its documentation.  You will have to call the socket API setsockopt() function directly instead.  It takes a pointer and length as input arguments.  You can use those to pass in the adapter name.  Delphi defines Linux's setsockopt() function in the Posix.SysSocket unit, eg (untested):

Code:
uses
 ..., SysUtils, IdStack, Posix.SysSocket;

const
 SO_BINDTODEVICE = 25;

var
 ...
 DeviceName: String;
 M: TMarshaller;
begin
 ...
 DeviceName := ...; // device name of WiFi adapter
 GStack.CheckForSocketError(
    Posix.SysSocket.setsockopt(
      IdTCPClient1.Socket.Binding.Handle,
      Id_SOL_SOCKET,
      SO_BINDTODEVICE,
      M.AsAnsi(DeviceName).ToPointer^,
      Length(DeviceName)+1 // assuming the device name uses ASCII chars only
    )
  );
  ...
end;

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]And unfortunately I have still not been able to find any type of Android string or integer reference linked to a given Network, so that I can supply this to Indy.

Look at Android's NetworkInterface interface, which has getByInetAddress() and getName() methods.

I don't know if getName() returns the same name that SO_BINDTODEVICE expects, though.

Otherwise, since Delphi operates at the native (NDK) level, and thus Indy operates at the Linux level, not at the Android level, you can use Linux APIs to query the adapter information, such as getifaddrs() (which unfortunately Android does not implement, but there is a 3rd party implementation at https://github.com/morristech/android-ifaddrs).

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]The only thing I get is the assigned IP address. (or lots of information about bandwidth, frequencies, etc.)

You are referring to Android's WiFiInfo interface, correct?

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]CORRECTION: I did find this:
"getNetworkId
int getNetworkId ()
Each configured network has a unique small integer ID, used to identify the network when performing operations on the supplicant. This method returns the ID for the currently connected network."

That network ID is not useful in this situation.



PS. on a side note, when posting code snippets to this forum, please use [ code]...[ /code] tags, not {code}...{code} tags like in the Embarcadero forums.  When writing a post in this forum, the full editor has a [attachment=11] button for inserting code snippets.
(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]Remy, There is no unit in Delphi 10.2 which contains access to the Android JNetworkInterface.
Googling, I found a unit, part of another program, which looks like it may work:
https://github.com/LongDirtyAnimAlf/Delp...ddress.pas

Problem is, I have no idea how to call this. As far as I can understand, it should be called by something like this:
Code:
var LNetworkInterface: JNetworkInterface;

LNetworkInterface := TJNetworkInterface.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.xxxx ???

But there is no JavaClass which contains anything like 'NetworkInterface'.

Any idea how to proceed?

PS: "such as getifaddrs() (...but there is a 3rd party implementation at https://github.com/morristech/android-ifaddrs)." is in C code, which I cannot use..

Many thanks for your patience...

Bart
(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]Problem is, I have no idea how to call this. As far as I can understand, it should be called by something like this:
Code:
var LNetworkInterface: JNetworkInterface;

LNetworkInterface := TJNetworkInterface.Wrap(TAndroidHelper.Context.getSystemService(TJContext.JavaClass.xxxx ???

No, that is not correct. As I said earlier, given an IP address of a network interface, use the static NetworkInterface.getByInetAddress() method, eg:

Code:
LNetworkInterface := TJNetworkInterface.JavaClass.getByInetAddress(TJInetAddress.JavaClass.getByName(StringToJString('IP Address here')));

(04-29-2018, 06:50 AM)BartKindt Wrote: [ -> ]PS: "such as getifaddrs() (...but there is a 3rd party implementation at https://github.com/morristech/android-ifaddrs)." is in C code, which I cannot use..

You can, if you translate it to Pascal, or pre-compile it into a static native library that your Pascal code then links to.
(05-07-2018, 06:24 PM)rlebeau Wrote: [ -> ]
Code:
LNetworkInterface := TJNetworkInterface.JavaClass.getByInetAddress(TJInetAddress.JavaClass.getByName(StringToJString('IP Address here')));

That works perfectly, thanks!

But now I get an AV:
Code:
with IdTCPClient1 do
           begin
             {if NetworkData.LocalIP <> '' then
             begin
               BoundIP := NetworkData.LocalIP;
               BoundPort := 8051;
               LocalLog('=== TCPThread.Execute: Connect using BoundIP: '+NetworkData.LocalIP);
             end; }
             try
               if NetworkData.WIFIInterface <> '' then
               begin
                 GStack.CheckForSocketError(
                 Posix.SysSocket.setsockopt(
                   IdTCPClient1.Socket.Binding.Handle,
                   Id_SOL_SOCKET,
                   SO_BINDTODEVICE,
                   M.AsAnsi(NetworkData.WIFIInterface).ToPointer^,
                   Length(NetworkData.WIFIInterface)+1 // assuming the device name uses ASCII chars only
                   )
                 );
               end;
             except
               on E:Exception do LocalLog('TCPThread.Execute: GStack failed: WIFIInterface='+NetworkData.WIFIInterface
                 +' Length(WIFIInterface)='+IntToStr(length(NetworkData.WIFIInterface))
                 +': '+E.Message,d_error);
             end;

             host := NetworkData.DTBActiveServer;
             port := Communications.DTBPort2;
             ConnectTimeout := 10000; // Increased from 5000 to 10000 29-12-2016 v09768
             SARTrackServiceDM.SetDTBConnectStatus(IS_CONNECTPENDING);
             LocalLog('TCPThread.Execute: DTB Client trying connect to ' + host + ' on port ' + IntToStr(port)+' on Interface '+NetworkData.WIFIInterface);

             UAndroidPowerManagement.AcquireWakeLock;
             try
               Connect;
             finally
               UAndroidPowerManagement.ReleaseWakeLock;
             end;
           end;
I get an error:
TCPThread.Execute: Start main loop
ERROR: TCPThread.Execute: GStack failed: WIFIInterface=wlan0 Length(WIFIInterface)=5: Access violation at address 90CC4666, accessing address 00000014

So the interface name seems correct, being "wlan0" and it IS connected. The length shows as "5". I also tried to remove the length "-1" but no difference.
The Delphi debugger does not react to the exception, probably because it is in a thread.


Any idea?

Thanks, Bart
Pages: 1 2