Android Tutorial [B4X] FTP Server code update for multi-network card hosts

Update (2017/10/17): For code changes that make this a true B4X update, see post #4

This relates to the FTP Server code posted here (https://www.b4x.com/android/forum/t...d-with-socket-and-asyncstreams.74320/#content).

@Sergio83 discovered an issue with the FTP server when it is deployed in a machine that has multiple active network cards (see https://www.b4x.com/android/forum/threads/ip-address-confusing-when-upload-file-with-ftp.84816/). In such an environment, it can happen that the FTPDataConnection may be assigned a different IP address than the FTPClient command connection, which will cause connection issues (as per the post linked above). With @Sergio83's help in testing the development and refining of a solution and @Erel's help in using Reflection vs JavaObject, the following code changes will allow for the proper handling of client connections:

1) Add a new variable in Class_Globals
B4X:
Private IPAddress As String
2) Add the following lines to Initialize
B4X:
#If B4A
    Dim r As Reflector
    r.Target = socket
    Dim jo As JavaObject = r.GetField("socket")
#Else
    Dim jo As JavaObject = socket
    jo = jo.GetField("socket")
#End If
    jo = jo.RunMethod("getLocalAddress", Null) 'InetAddress
    IPAddress = jo.RunMethod("getHostAddress", Null)
#if debug
    Log(IPAddress)
#end if
3) Modify the Case "PASV" in the HandleClientCommand
B4X:
Case "PASV"
   PrepareDataConnection
   'SendResponse (227, mServer.ssocket.GetMyIP.Replace(".", ",") & "," & Floor(mDataPort / 256) & "," & (mDataPort Mod 256))
   SendResponse (227, IPAddress.Replace(".", ",") & "," & Floor(mDataPort / 256) & "," & (mDataPort Mod 256))
Please note that
 
Last edited:

OliverA

Expert
Licensed User
Longtime User
This code will work with B4A and B4J (the [B4X] tag suggests that it will also work with B4i which is not the case here).
Darn cross-platform system! :D:p:D

Please note I'm working on trying to fix this. Please give me some time (days). I'm trying to learn Objective C, Apple's Frameworks, and how to inline Objective C. I know that someone else can do this in probably 5-10 minutes, but I'm slow and error prone (I'm having errors on almost every build - poor hosted builder). In the end, I may cry "Uncle", at which time I'll post a "Please help me" thread on the B4i forum.
 

OliverA

Expert
Licensed User
Longtime User
1) Add a new variable in Class_Globals (same as Post #1)
B4X:
Private IPAddress As String
2) Add the following lines to Initialize (changed from Post #1)
B4X:
   IPAddress = GetSocketLocalIPAddress(socket)
#if debug
   Log(IPAddress)
#end if
3) Modify the Case "PASV" in the HandleClientCommand (same as Post #1)
B4X:
Case "PASV"
   PrepareDataConnection
   'SendResponse (227, mServer.ssocket.GetMyIP.Replace(".", ",") & "," & Floor(mDataPort / 256) & "," & (mDataPort Mod 256))
   SendResponse (227, IPAddress.Replace(".", ",") & "," & Floor(mDataPort / 256) & "," & (mDataPort Mod 256))
4) Add this Private Sub (new)
B4X:
Private Sub GetSocketLocalIPAddress(boundSocket As Socket) As String
   Dim localIP As String
#if B4A or B4J
#If B4A
   Dim r As Reflector
   r.Target = boundSocket
   Dim jo As JavaObject = r.GetField("socket")
#Else
   Dim jo As JavaObject = boundSocket
   jo = jo.GetField("socket")
#End If // B4A
   jo = jo.RunMethod("getLocalAddress", Null) 'InetAddress
   localIP = jo.RunMethod("getHostAddress", Null)
#End If // B4A or B4J
#If B4I
   Dim no As NativeObject = boundSocket
   Dim socketid As Int = no.GetField("socket").GetField("sockfd").AsNumber
   Dim no2 As NativeObject = Me
   localIP = no2.RunMethod("getBoundSocketIPAddress:", Array(socketid)).AsString
#End if // B4I
   Return localIP
End Sub
5) Add this Objective C code (new)
B4X:
#if objc

#import <Foundation/Foundation.h>
//#import <CoreFoundation/CoreFoundation.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <errno.h>
#import <string.h>

// Sources:
// https://stackoverflow.com/a/20374511
// http://alas.matf.bg.ac.rs/manuals/lspe/snode=48.html
// https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/UsingSocketsandSocketStreams.html
// https://stackoverflow.com/questions/503878/how-to-know-what-the-errno-means

- (NSString *)getBoundSocketIPAddress: (int)socketnumber {
  NSString *socketIP = @"0.0.0.0";
  
   // Get hands on appropriate data structures via the socket number
  CFSocketNativeHandle nativeSocketHandle = socketnumber;
  uint8_t name[SOCK_MAXADDRLEN];
  socklen_t namelen = sizeof(name);
//  if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) {  
   if (0 == getsockname(nativeSocketHandle, (struct sockaddr *)name, &namelen)) {
     struct sockaddr *socketinfo = (struct sockaddr*)name;
     if(AF_INET == socketinfo->sa_family) {
       // If v4 is used
       struct sockaddr_in *socketaddress = (struct sockaddr_in*)name;
       // convert port to int
       //int portnumber = socketaddress->sin_port
     // convert ip to string
       char ipstr[INET_ADDRSTRLEN];
     struct in_addr *ipv4addr = &socketaddress->sin_addr;
       if (inet_ntop(AF_INET, ipv4addr, ipstr, sizeof(ipstr)))
         socketIP = [NSString stringWithFormat:@"%s", ipstr];
       else
         socketIP = [NSString stringWithFormat:@"ERROR: function inet_ntop(): %s", strerror(errno)];
     } else if(AF_INET6 == socketinfo->sa_family) {
       // if IPv6 is used
       struct sockaddr_in6 *socketaddress = (struct sockaddr_in6*)name;
     // convert port to int
     //int portnumber = socketaddress->sin6_port;
     // convert ip to string
     char ipstr[INET6_ADDRSTRLEN];
     struct in6_addr *ipv6addr = &socketaddress->sin6_addr;
     if (inet_ntop(AF_INET6, ipv6addr, ipstr, sizeof(ipstr)))
         socketIP = [NSString stringWithFormat:@"%s", ipstr];
       else
         socketIP = [NSString stringWithFormat:@"ERROR: function inet_ntop(): %s", strerror(errno)];
     } else {
       socketIP = [NSString stringWithFormat:@"ERROR: Unsupported Address Family: %d", &socketinfo->sa_family];
     }
   } else {
     socketIP = [NSString stringWithFormat:@"ERROR: function getsockname(): %s", strerror(errno)];
   }
  
   return socketIP;
}
#End If
 
Top