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!

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
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…