Posts: 50
Threads: 16
Joined: Apr 2018
Reputation:
0
Location: New Zealand
04-26-2018, 05:16 AM
(This post was last modified: 04-26-2018, 06:53 AM by BartKindt.)
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
---
Bart Kindt
CEO and Developer
SARTrack Limited
New Zealand
www.sartrack.nz
Posts: 1,136
Threads: 37
Joined: Mar 2018
Reputation:
30
Location: Limassol, Cyprus
(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/
Posts: 628
Threads: 2
Joined: Mar 2018
Reputation:
35
Location: USA
04-26-2018, 05:24 PM
(This post was last modified: 08-23-2018, 11:36 PM by rlebeau.)
(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.
Posts: 50
Threads: 16
Joined: Apr 2018
Reputation:
0
Location: New Zealand
(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
---
Bart Kindt
CEO and Developer
SARTrack Limited
New Zealand
www.sartrack.nz
Posts: 628
Threads: 2
Joined: Mar 2018
Reputation:
35
Location: USA
04-28-2018, 02:36 PM
(This post was last modified: 08-23-2018, 11:37 PM by rlebeau.)
(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.
Posts: 50
Threads: 16
Joined: Apr 2018
Reputation:
0
Location: New Zealand
04-29-2018, 06:50 AM
(This post was last modified: 04-29-2018, 07:04 AM by BartKindt.)
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
---
Bart Kindt
CEO and Developer
SARTrack Limited
New Zealand
www.sartrack.nz
Posts: 628
Threads: 2
Joined: Mar 2018
Reputation:
35
Location: USA
04-30-2018, 06:46 PM
(This post was last modified: 08-23-2018, 11:41 PM by rlebeau.)
(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
code.png (Size: 872 bytes / Downloads: 55)
button for inserting code snippets.
Posts: 50
Threads: 16
Joined: Apr 2018
Reputation:
0
Location: New Zealand
05-05-2018, 09:02 AM
(This post was last modified: 05-07-2018, 01:08 PM by BartKindt.)
(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
---
Bart Kindt
CEO and Developer
SARTrack Limited
New Zealand
www.sartrack.nz
Posts: 628
Threads: 2
Joined: Mar 2018
Reputation:
35
Location: USA
05-07-2018, 06:24 PM
(This post was last modified: 08-23-2018, 11:42 PM by rlebeau.)
(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.
Posts: 50
Threads: 16
Joined: Apr 2018
Reputation:
0
Location: New Zealand
(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
---
Bart Kindt
CEO and Developer
SARTrack Limited
New Zealand
www.sartrack.nz
|