1428 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			1428 lines
		
	
	
		
			44 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
| unit IdStackVCLPosix;
 | |
| 
 | |
| interface
 | |
| 
 | |
| {$I IdCompilerDefines.inc}
 | |
| 
 | |
| {IMPORTANT!!!
 | |
| 
 | |
| Platform warnings in this unit should be disabled because Indy we have no
 | |
| intention of porting this unit to Windows or any non-Unix-like operating system.
 | |
| 
 | |
| Any differences between Unix-like operating systems have to dealt with in other
 | |
| ways.
 | |
| }
 | |
| 
 | |
| {$I IdSymbolPlatformOff.inc}
 | |
| {$I IdUnitPlatformOff.inc}
 | |
| 
 | |
| uses
 | |
|   Classes,
 | |
|   IdCTypes,
 | |
|   Posix.SysSelect,
 | |
|   Posix.SysSocket,
 | |
|   Posix.SysTime,
 | |
|   IdStack,
 | |
|   IdStackConsts,
 | |
|   IdGlobal,
 | |
|   IdStackBSDBase;
 | |
| 
 | |
| type
 | |
|   {$IFDEF USE_VCL_POSIX}
 | |
|     {$IFDEF ANDROID}
 | |
|   EIdAccessWifiStatePermissionNeeded = class(EIdAndroidPermissionNeeded);
 | |
|   EIdAccessNetworkStatePermissionNeeded = class(EIdAndroidPermissionNeeded);
 | |
|     {$ENDIF}
 | |
|   {$ENDIF}
 | |
| 
 | |
|   TIdSocketListVCLPosix = class (TIdSocketList)
 | |
|   protected
 | |
|     FCount: Integer;
 | |
|     FFDSet: fd_set;
 | |
|     //
 | |
|     class function FDSelect(AReadSet, AWriteSet,
 | |
|       AExceptSet: Pfd_set; const ATimeout: Integer): Integer;
 | |
|     function GetItem(AIndex: Integer): TIdStackSocketHandle; override;
 | |
|   public
 | |
|     procedure Add(AHandle: TIdStackSocketHandle); override;
 | |
|     procedure Remove(AHandle: TIdStackSocketHandle); override;
 | |
|     function Count: Integer; override;
 | |
|     procedure Clear; override;
 | |
|     function Clone: TIdSocketList; override;
 | |
|     function ContainsSocket(AHandle: TIdStackSocketHandle): Boolean; override;
 | |
|     procedure GetFDSet(var VSet: fd_set);
 | |
|     procedure SetFDSet(var VSet: fd_set);
 | |
|     class function Select(AReadList: TIdSocketList; AWriteList: TIdSocketList;
 | |
|       AExceptList: TIdSocketList; const ATimeout: Integer = IdTimeoutInfinite): Boolean; override;
 | |
|     function SelectRead(const ATimeout: Integer = IdTimeoutInfinite): Boolean; override;
 | |
|     function SelectReadList(var VSocketList: TIdSocketList;
 | |
|       const ATimeout: Integer = IdTimeoutInfinite): Boolean; override;
 | |
|   end;
 | |
| 
 | |
|   TIdStackVCLPosix = class(TIdStackBSDBase)
 | |
|   protected
 | |
|     procedure WriteChecksumIPv6(s: TIdStackSocketHandle; var VBuffer: TIdBytes;
 | |
|       const AOffset: Integer; const AIP: String; const APort: TIdPort);
 | |
|     function GetLastError: Integer;
 | |
|     procedure SetLastError(const AError: Integer);
 | |
|     function HostByName(const AHostName: string;
 | |
|       const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION): string; override;
 | |
|     function ReadHostName: string; override;
 | |
|     function WSCloseSocket(ASocket: TIdStackSocketHandle): Integer; override;
 | |
|     function WSRecv(ASocket: TIdStackSocketHandle; var ABuffer;
 | |
|       const ABufferLength, AFlags: Integer): Integer; override;
 | |
|     function WSSend(ASocket: TIdStackSocketHandle; const ABuffer;
 | |
|       const ABufferLength, AFlags: Integer): Integer; override;
 | |
|     function WSShutdown(ASocket: TIdStackSocketHandle; AHow: Integer): Integer; override;
 | |
|     {$IFNDEF VCL_XE3_OR_ABOVE}
 | |
|     procedure WSGetSocketOption(ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel;
 | |
|       AOptName: TIdSocketOption; var AOptVal; var AOptLen: Integer); override;
 | |
|     procedure WSSetSocketOption(ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel;
 | |
|       AOptName: TIdSocketOption; const AOptVal; const AOptLen: Integer); override;
 | |
|     {$ENDIF}
 | |
|   public
 | |
|     constructor Create; override;
 | |
|     destructor Destroy; override;
 | |
|     procedure SetBlocking(ASocket: TIdStackSocketHandle; const ABlocking: Boolean); override;
 | |
|     function WouldBlock(const AResult: Integer): Boolean; override;
 | |
|     function Accept(ASocket: TIdStackSocketHandle; var VIP: string; var VPort: TIdPort;
 | |
|       var VIPVersion: TIdIPVersion): TIdStackSocketHandle; override;
 | |
|     procedure Bind(ASocket: TIdStackSocketHandle; const AIP: string;
 | |
|      const APort: TIdPort; const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION); override;
 | |
|     procedure Connect(const ASocket: TIdStackSocketHandle; const AIP: string;
 | |
|      const APort: TIdPort; const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION); override;
 | |
|     function HostByAddress(const AAddress: string;
 | |
|       const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION): string; override;
 | |
|     function WSGetLastError: Integer; override;
 | |
|     procedure WSSetLastError(const AErr : Integer); override;
 | |
|     function WSGetServByName(const AServiceName: string): TIdPort; override;
 | |
|     procedure AddServByPortToList(const APortNumber: TIdPort; AAddresses: TStrings); override;
 | |
|     procedure GetPeerName(ASocket: TIdStackSocketHandle; var VIP: string;
 | |
|      var VPort: TIdPort; var VIPVersion: TIdIPVersion); override;
 | |
|     procedure GetSocketName(ASocket: TIdStackSocketHandle; var VIP: string;
 | |
|      var VPort: TIdPort; var VIPVersion: TIdIPVersion); override;
 | |
|     procedure Listen(ASocket: TIdStackSocketHandle; ABackLog: Integer); override;
 | |
|     function HostToNetwork(AValue: UInt16): UInt16; override;
 | |
|     function NetworkToHost(AValue: UInt16): UInt16; override;
 | |
|     function HostToNetwork(AValue: UInt32): UInt32; override;
 | |
|     function NetworkToHost(AValue: UInt32): UInt32; override;
 | |
|     function HostToNetwork(AValue: TIdUInt64): TIdUInt64; override;
 | |
|     function NetworkToHost(AValue: TIdUInt64): TIdUInt64; override;
 | |
|     function RecvFrom(const ASocket: TIdStackSocketHandle;
 | |
|       var VBuffer; const ALength, AFlags: Integer; var VIP: string;
 | |
|       var VPort: TIdPort; var VIPVersion: TIdIPVersion): Integer; override;
 | |
|     function ReceiveMsg(ASocket: TIdStackSocketHandle;
 | |
|       var VBuffer: TIdBytes; APkt: TIdPacketInfo): UInt32;  override;
 | |
|     procedure WSSendTo(ASocket: TIdStackSocketHandle; const ABuffer;
 | |
|       const ABufferLength, AFlags: Integer; const AIP: string; const APort: TIdPort;
 | |
|       AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION); override;
 | |
|     function WSSocket(AFamily : Integer; AStruct : TIdSocketType; AProtocol: Integer;
 | |
|       const AOverlapped: Boolean = False): TIdStackSocketHandle; override;
 | |
|     procedure Disconnect(ASocket: TIdStackSocketHandle); override;
 | |
|     {$IFDEF VCL_XE3_OR_ABOVE}
 | |
|     procedure GetSocketOption(ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel;
 | |
|       AOptName: TIdSocketOption; var AOptVal; var AOptLen: Integer); override;
 | |
|     procedure SetSocketOption(ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel;
 | |
|       AOptName: TIdSocketOption; const AOptVal; const AOptLen: Integer); override;
 | |
|     {$ENDIF}
 | |
|     function SupportsIPv6: Boolean; overload; override;
 | |
|     function CheckIPVersionSupport(const AIPVersion: TIdIPVersion): boolean; override;
 | |
|     //In Windows, this writes a checksum into a buffer.  In Linux, it would probably
 | |
|     //simply have the kernal write the checksum with something like this (RFC 2292):
 | |
| //
 | |
| //    int  offset = 2;
 | |
| //    setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset));
 | |
| //
 | |
| //  Note that this should be called
 | |
|     //IMMEDIATELY before you do a SendTo because the Local IPv6 address might change
 | |
| 
 | |
|     procedure WriteChecksum(s : TIdStackSocketHandle; var VBuffer : TIdBytes;
 | |
|       const AOffset : Integer; const AIP : String; const APort : TIdPort;
 | |
|       const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION); override;
 | |
|     function IOControl(const s: TIdStackSocketHandle; const cmd: UInt32;
 | |
|       var arg: UInt32): Integer; override;
 | |
| 
 | |
|     procedure GetLocalAddressList(AAddresses: TIdStackLocalAddressList); override;
 | |
|   end;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| {$O-}
 | |
| 
 | |
| uses
 | |
|   IdResourceStrings,
 | |
|   IdResourceStringsUnix,
 | |
|   IdResourceStringsVCLPosix,
 | |
|   IdException,
 | |
|   IdVCLPosixSupplemental,
 | |
|   Posix.Base,
 | |
|   Posix.ArpaInet,
 | |
|   Posix.Errno,
 | |
|   Posix.NetDB,
 | |
|   {$IFDEF HAS_getifaddrs}
 | |
|   Posix.NetIf,
 | |
|   {$ENDIF}
 | |
|   Posix.NetinetIn,
 | |
|   Posix.StrOpts,
 | |
|   Posix.SysTypes,
 | |
|   Posix.SysUio,
 | |
|   Posix.Unistd,
 | |
|   SysUtils;
 | |
| 
 | |
|   {$UNDEF HAS_MSG_NOSIGNAL}
 | |
|   {$IFDEF LINUX}  //this LINUX ifdef is deliberate
 | |
|     {$DEFINE HAS_MSG_NOSIGNAL}
 | |
|   {$ENDIF}
 | |
| 
 | |
| 
 | |
| const
 | |
|   {$IFDEF HAS_MSG_NOSIGNAL}
 | |
|   //fancy little trick since OS X does not have MSG_NOSIGNAL
 | |
|   Id_MSG_NOSIGNAL = MSG_NOSIGNAL;
 | |
|   {$ELSE}
 | |
|   Id_MSG_NOSIGNAL = 0;
 | |
|   {$ENDIF}
 | |
|   Id_WSAEPIPE = EPIPE;
 | |
| 
 | |
| 
 | |
| 
 | |
| //helper functions for some structs
 | |
| 
 | |
| {Note:  These hide an API difference in structures.
 | |
| 
 | |
| BSD 4.4 introduced a minor API change.  sa_family was changed from a 16bit
 | |
| word to an 8 bit byteee and an 8 bit byte feild named sa_len was added.
 | |
| 
 | |
| }
 | |
| procedure InitSockAddr_In(var VSock : SockAddr_In);
 | |
| {$IFDEF USE_INLINE} inline; {$ENDIF}
 | |
| begin
 | |
|   FillChar(VSock, SizeOf(SockAddr_In), 0);
 | |
|   VSock.sin_family := PF_INET;
 | |
|   {$IFDEF SOCK_HAS_SINLEN}
 | |
|   VSock.sin_len := SizeOf(SockAddr_In);
 | |
|   {$ENDIF}
 | |
| end;
 | |
| 
 | |
| procedure InitSockAddr_in6(var VSock : SockAddr_in6);
 | |
| {$IFDEF USE_INLINE} inline; {$ENDIF}
 | |
| begin
 | |
|   FillChar(VSock, SizeOf(SockAddr_in6), 0);
 | |
|   {$IFDEF SOCK_HAS_SINLEN}
 | |
|   VSock.sin6_len := SizeOf(SockAddr_in6);
 | |
|   {$ENDIF}
 | |
|   VSock.sin6_family := PF_INET6;
 | |
| end;
 | |
| //
 | |
| 
 | |
| { TIdSocketListVCLPosix }
 | |
| 
 | |
| procedure TIdSocketListVCLPosix.Add(AHandle: TIdStackSocketHandle);
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     if not __FD_ISSET(AHandle, FFDSet) then begin
 | |
|       if Count >= FD_SETSIZE then begin
 | |
|         raise EIdStackSetSizeExceeded.Create(RSSetSizeExceeded);
 | |
|       end;
 | |
|       __FD_SET(AHandle, FFDSet);
 | |
|       Inc(FCount);
 | |
|     end;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdSocketListVCLPosix.Clear;
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     __FD_ZERO(FFDSet);
 | |
|     FCount := 0;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.Clone: TIdSocketList;
 | |
| begin
 | |
|   Result := TIdSocketListVCLPosix.Create;
 | |
|   try
 | |
|     Lock;
 | |
|     try
 | |
|       TIdSocketListVCLPosix(Result).SetFDSet(FFDSet);
 | |
|     finally
 | |
|       Unlock;
 | |
|     end;
 | |
|   except
 | |
|     FreeAndNil(Result);
 | |
|     raise;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.ContainsSocket(
 | |
|   AHandle: TIdStackSocketHandle): Boolean;
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     Result := __FD_ISSET(AHandle, FFDSet);
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.Count: Integer;
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     Result := FCount;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| class function TIdSocketListVCLPosix.FDSelect(AReadSet, AWriteSet,
 | |
|   AExceptSet: Pfd_set; const ATimeout: Integer): Integer;
 | |
| var
 | |
|   LTime: TimeVal;
 | |
|   LTimePtr: PTimeVal;
 | |
| begin
 | |
|   if ATimeout = IdTimeoutInfinite then begin
 | |
|     LTimePtr := nil;
 | |
|   end else begin
 | |
|     LTime.tv_sec := ATimeout div 1000;
 | |
|     LTime.tv_usec := (ATimeout mod 1000) * 1000;
 | |
|     LTimePtr := @LTime;
 | |
|   end;
 | |
|   // TODO: calculate the actual nfds value based on the Sets provided...
 | |
|   Result := Posix.SysSelect.select(FD_SETSIZE, AReadSet, AWriteSet, AExceptSet, LTimePtr);
 | |
| end;
 | |
| 
 | |
| procedure TIdSocketListVCLPosix.GetFDSet(var VSet: fd_set);
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     VSet := FFDSet;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.GetItem(AIndex: Integer): TIdStackSocketHandle;
 | |
| var
 | |
|   LIndex, i: Integer;
 | |
| begin
 | |
|   Result := 0;
 | |
|   Lock;
 | |
|   try
 | |
|     LIndex := 0;
 | |
|     //? use FMaxHandle div x
 | |
|     for i:= 0 to FD_SETSIZE - 1 do begin
 | |
|       if __FD_ISSET(i, FFDSet) then begin
 | |
|         if LIndex = AIndex then begin
 | |
|           Result := i;
 | |
|           Break;
 | |
|         end;
 | |
|         Inc(LIndex);
 | |
|       end;
 | |
|     end;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdSocketListVCLPosix.Remove(AHandle: TIdStackSocketHandle);
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     if __FD_ISSET(AHandle, FFDSet) then begin
 | |
|       Dec(FCount);
 | |
|       __FD_CLR(AHandle, FFDSet);
 | |
|     end;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| class function TIdSocketListVCLPosix.Select(AReadList, AWriteList,
 | |
|   AExceptList: TIdSocketList; const ATimeout: Integer): Boolean;
 | |
| 
 | |
| var
 | |
|   LReadSet: fd_set;
 | |
|   LWriteSet: fd_set;
 | |
|   LExceptSet: fd_set;
 | |
|   LPReadSet: Pfd_set;
 | |
|   LPWriteSet: Pfd_set;
 | |
|   LPExceptSet: Pfd_set;
 | |
| 
 | |
|   procedure ReadSet(AList: TIdSocketList; var ASet: fd_set; var APSet: Pfd_set);
 | |
|   begin
 | |
|     if AList <> nil then begin
 | |
|       TIdSocketListVCLPosix(AList).GetFDSet(ASet);
 | |
|       APSet := @ASet;
 | |
|     end else begin
 | |
|       APSet := nil;
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| begin
 | |
|   ReadSet(AReadList, LReadSet, LPReadSet);
 | |
|   ReadSet(AWriteList, LWriteSet, LPWriteSet);
 | |
|   ReadSet(AExceptList, LExceptSet, LPExceptSet);
 | |
|   //
 | |
|   Result := FDSelect(LPReadSet, LPWriteSet, LPExceptSet, ATimeout) >0;
 | |
|   //
 | |
|   if AReadList <> nil then begin
 | |
|     TIdSocketListVCLPosix(AReadList).SetFDSet(LReadSet);
 | |
|   end;
 | |
|   if AWriteList <> nil then begin
 | |
|     TIdSocketListVCLPosix(AWriteList).SetFDSet(LWriteSet);
 | |
|   end;
 | |
|   if AExceptList <> nil then begin
 | |
|     TIdSocketListVCLPosix(AExceptList).SetFDSet(LExceptSet);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.SelectRead(const ATimeout: Integer): Boolean;
 | |
| var
 | |
|   LSet: fd_set;
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     LSet := FFDSet;
 | |
|     // select() updates this structure on return,
 | |
|     // so we need to copy it each time we need it
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
|   Result := FDSelect(@LSet, nil, nil, ATimeout) > 0;
 | |
| end;
 | |
| 
 | |
| function TIdSocketListVCLPosix.SelectReadList(var VSocketList: TIdSocketList;
 | |
|   const ATimeout: Integer): Boolean;
 | |
| var
 | |
|   LSet: fd_set;
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     LSet := FFDSet;
 | |
|     // select() updates this structure on return,
 | |
|     // so we need to copy it each time we need it
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
|   Result := FDSelect(@LSet, nil, nil, ATimeout) > 0;
 | |
|   if Result then begin
 | |
|     if VSocketList = nil then begin
 | |
|       VSocketList := TIdSocketList.CreateSocketList;
 | |
|     end;
 | |
|     TIdSocketListVCLPosix(VSocketList).SetFDSet(LSet);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdSocketListVCLPosix.SetFDSet(var VSet: fd_set);
 | |
| begin
 | |
|   Lock;
 | |
|   try
 | |
|     FFDSet := VSet;
 | |
|   finally
 | |
|     Unlock;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| { TIdStackVCLPosix }
 | |
| 
 | |
| {
 | |
| IMPORTANT!!!
 | |
| 
 | |
| Throughout much of this code, you will see stuff such as:
 | |
| 
 | |
| var
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| 
 | |
| This is just a fancy way to do typecasting with various types of address type.
 | |
| Many functions take a sockaddr parameter but that parameter is typecast for various
 | |
| address types.  The structures mentioned above are designed just for such
 | |
| typecasting.  The reason we use sockaddr_storage instead of sockaddr is that
 | |
| we need something that is guaranteed to be able to contain various address types
 | |
| and sockaddr would be too short for some of them and we can't know what
 | |
| someone else will add to Indy as time goes by.
 | |
| }
 | |
| 
 | |
| function TIdStackVCLPosix.Accept(ASocket: TIdStackSocketHandle; var VIP: string;
 | |
|   var VPort: TIdPort; var VIPVersion: TIdIPVersion): TIdStackSocketHandle;
 | |
| var
 | |
|   LN: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| 
 | |
| begin
 | |
|   LN := SizeOf(LAddrStore);
 | |
|   Result := Posix.SysSocket.accept(ASocket, LAddr, LN);
 | |
|   if Result <> -1 then begin
 | |
|     case LAddrStore.ss_family of
 | |
|       Id_PF_INET4: begin
 | |
|         VIP := TranslateTInAddrToString( LAddrIPv4.sin_addr, Id_IPv4);
 | |
|         VPort := ntohs(LAddrIPv4.sin_port);
 | |
|         VIPVersion := Id_IPV4;
 | |
|       end;
 | |
|       Id_PF_INET6: begin
 | |
|         VIP := TranslateTInAddrToString(LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|         VPort := ntohs(LAddrIPv6.sin6_port);
 | |
|         VIPVersion := Id_IPV6;
 | |
|       end
 | |
|       else begin
 | |
|         __close(Result);
 | |
|         Result := Id_INVALID_SOCKET;
 | |
|         IPVersionUnsupported;
 | |
|       end;
 | |
|     end;
 | |
|   end else begin
 | |
|     if GetLastError = EBADF then begin
 | |
|       SetLastError(EINTR);
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| {$IFDEF HAS_getifaddrs}
 | |
| function getifaddrs(ifap: pifaddrs): Integer; cdecl; external libc name _PU + 'getifaddrs'; {do not localize}
 | |
| procedure freeifaddrs(ifap: pifaddrs); cdecl; external libc name _PU + 'freeifaddrs'; {do not localize}
 | |
| {$ELSE}
 | |
|   {$IFDEF ANDROID}
 | |
|   // TODO: implement getifaddrs() manually using code from https://github.com/kmackay/android-ifaddrs
 | |
|   {.$DEFINE HAS_getifaddrs}
 | |
|   {$ENDIF}
 | |
| {$ENDIF}
 | |
| 
 | |
| procedure TIdStackVCLPosix.GetLocalAddressList(AAddresses: TIdStackLocalAddressList);
 | |
| var
 | |
|   {$IFDEF HAS_getifaddrs}
 | |
|   LAddrList, LAddrInfo: pifaddrs;
 | |
|   LSubNetStr: String;
 | |
|   {$ELSE}
 | |
|   LRetVal: Integer;
 | |
|   LHostName: string;
 | |
|   Hints: AddrInfo;
 | |
|   LAddrList, LAddrInfo: pAddrInfo;
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   M: TMarshaller;
 | |
|     {$ENDIF}
 | |
|   {$ENDIF}
 | |
| begin
 | |
|   // TODO: Using gethostname() and getaddrinfo() like this may not always return just
 | |
|   // the machine's IP addresses. Technically speaking, they will return the local
 | |
|   // hostname, and then return the address(es) to which that hostname resolves.
 | |
|   // It is possible for a machine to (a) be configured such that its name does
 | |
|   // not resolve to an IP, or (b) be configured such that its name resolves to
 | |
|   // multiple IPs, only one of which belongs to the local machine. For better
 | |
|   // results, we should use getifaddrs() on platforms that support it...
 | |
| 
 | |
|   {$IFDEF HAS_getifaddrs}
 | |
| 
 | |
|   if getifaddrs(@LAddrList) = 0 then // TODO: raise an exception if it fails
 | |
|   try
 | |
|     AAddresses.BeginUpdate;
 | |
|     try
 | |
|       LAddrInfo := LAddrList;
 | |
|       repeat
 | |
|         if (LAddrInfo^.ifa_addr <> nil) and ((LAddrInfo^.ifa_flags and IFF_LOOPBACK) = 0) then
 | |
|         begin
 | |
|           case LAddrInfo^.ifa_addr^.sa_family of
 | |
|             Id_PF_INET4: begin
 | |
|               if LAddrInfo^.ifa_netmask <> nil then begin
 | |
|                 LSubNetStr := TranslateTInAddrToString( PSockAddr_In(LAddrInfo^.ifa_netmask)^.sin_addr, Id_IPv4);
 | |
|               end else begin
 | |
|                 LSubNetStr := '';
 | |
|               end;
 | |
|               TIdStackLocalAddressIPv4.Create(AAddresses, TranslateTInAddrToString( PSockAddr_In(LAddrInfo^.ifa_addr)^.sin_addr, Id_IPv4), LSubNetStr);
 | |
|             end;
 | |
|             Id_PF_INET6: begin
 | |
|               TIdStackLocalAddressIPv6.Create(AAddresses, TranslateTInAddrToString( PSockAddr_In6(LAddrInfo^.ifa_addr)^.sin6_addr, Id_IPv6));
 | |
|             end;
 | |
|           end;
 | |
|         end;
 | |
|         LAddrInfo := LAddrInfo^.ifa_next;
 | |
|       until LAddrInfo = nil;
 | |
|     finally
 | |
|       AAddresses.EndUpdate;
 | |
|     end;
 | |
|   finally
 | |
|     freeifaddrs(LAddrList);
 | |
|   end;
 | |
| 
 | |
|   {$ELSE}
 | |
| 
 | |
|   // TODO: on Android, either implement getifaddrs() (https://github.com/kmackay/android-ifaddrs)
 | |
|   // or use the Java API to enumerate the local network interfaces and their IP addresses, eg:
 | |
|   {
 | |
|   var
 | |
|     en, enumIpAddr: Enumeration;
 | |
|     intf: NetworkInterface;
 | |
|     inetAddress: InetAddress;
 | |
|   begin
 | |
|     try
 | |
|       en := NetworkInterface.getNetworkInterfaces;
 | |
|       if en.hasMoreElements then begin
 | |
|         AAddresses.BeginUpdate;
 | |
|         try
 | |
|           repeat
 | |
|             intf := en.nextElement;
 | |
|             enumIpAddr := intf.getInetAddresses();
 | |
|             while enumIpAddr.hasMoreElements do begin
 | |
|               inetAddress := enumIpAddr.nextElement;
 | |
|               if not inetAddress.isLoopbackAddress then begin
 | |
|                 if (inetAddress instanceof Inet4Address) then begin
 | |
|                   TIdStackLocalAddressIPv4.Create(AAddresses, inetAddress.getHostAddress.toString, '');
 | |
|                 end
 | |
|                 else if (inetAddress instanceof Inet6Address) then begin
 | |
|                   TIdStackLocalAddressIPv6.Create(AAddresses, inetAddress.getHostAddress.toString);
 | |
|                 end;
 | |
|               end;
 | |
|             end;
 | |
|           until not en.hasMoreElements;
 | |
|         finally
 | |
|           AAddresses.EndUpdate;
 | |
|         end;
 | |
|        end;
 | |
|      except
 | |
|        if not HasAndroidPermission('android.permission.INTERNET') then begin
 | |
|          IndyRaiseOuterException(EIdInternetPermissionNeeded.CreateError(0, ''));
 | |
|        end;
 | |
|        if not HasAndroidPermission('android.permission.ACCESS_NETWORK_STATE') then begin
 | |
|          IndyRaiseOuterException(EIdAccessNetworkStatePermissionNeeded.CreateError(0, ''));
 | |
|        end;
 | |
|        raise;
 | |
|      end;
 | |
|    end;
 | |
| 
 | |
|   Note that this require the application to have INTERNET and ACCESS_NETWORK_STATE permissions.
 | |
| 
 | |
|   Or:
 | |
| 
 | |
|   uses
 | |
|     if XE7+
 | |
|       Androidapi.Helpers 
 | |
|     else
 | |
|       FMX.Helpers.Android 
 | |
|     ;
 | |
| 
 | |
|   var
 | |
|     wifiManager: WifiManager;
 | |
|     ipAddress: Integer;
 | |
|   begin
 | |
|     try
 | |
|       wifiManager := (WifiManager) SharedActivityContext.getSystemService(WIFI_SERVICE);
 | |
|       ipAddress := wifiManager.getConnectionInfo.getIpAddress;
 | |
|     except
 | |
|       if not HasAndroidPermission('android.permission.ACCESS_WIFI_STATE') then begin
 | |
|         IndyRaiseOuterException(EIdAccessWifiStatePermissionNeeded.CreateError(0, ''));
 | |
|       end;
 | |
|       raise;
 | |
|     end;
 | |
|     TIdStackLocalAddressIPv4.Create(AAddresses, Format('%d.%d.%d.%d', [ipAddress and $ff, (ipAddress shr 8) and $ff, (ipAddress shr 16) and $ff, (ipAddress shr 24) and $ff]), '');
 | |
|   end;
 | |
| 
 | |
|   This requires only ACCESS_WIFI_STATE permission.
 | |
|   }
 | |
| 
 | |
|   //IMPORTANT!!!
 | |
|   //
 | |
|   //The Hints structure must be zeroed out or you might get an AV.
 | |
|   //I've seen this in Mac OS X
 | |
|   FillChar(Hints, SizeOf(Hints), 0);
 | |
|   Hints.ai_family := PF_UNSPEC; // returns both IPv4 and IPv6 addresses
 | |
|   Hints.ai_socktype := SOCK_STREAM;
 | |
| 
 | |
|   LHostName := HostName;
 | |
| 
 | |
|   LRetVal := getaddrinfo(
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     M.AsAnsi(LHostName).ToPointer
 | |
|     {$ELSE}
 | |
|     PAnsiChar(
 | |
|       {$IFDEF STRING_IS_ANSI}
 | |
|       LHostName
 | |
|       {$ELSE}
 | |
|       AnsiString(LHostName) // explicit convert to Ansi
 | |
|       {$ENDIF}
 | |
|     )
 | |
|     {$ENDIF},
 | |
|     nil, Hints, LAddrList);
 | |
|   if LRetVal <> 0 then begin
 | |
|     if LRetVal = EAI_SYSTEM then begin
 | |
|       RaiseLastOSError;
 | |
|     end else begin
 | |
|       raise EIdReverseResolveError.CreateFmt(RSReverseResolveError, [LHostName, gai_strerror(LRetVal), LRetVal]);
 | |
|     end;
 | |
|   end;
 | |
|   try
 | |
|     AAddresses.BeginUpdate;
 | |
|     try
 | |
|       LAddrInfo := LAddrList;
 | |
|       repeat
 | |
|         case LAddrInfo^.ai_addr^.sa_family of
 | |
|         Id_PF_INET4 :
 | |
|           begin
 | |
|             TIdStackLocalAddressIPv4.Create(AAddresses, TranslateTInAddrToString( PSockAddr_In(LAddrInfo^.ai_addr)^.sin_addr, Id_IPv4), ''); // TODO: SubNet
 | |
|           end;
 | |
|         Id_PF_INET6 :
 | |
|           begin
 | |
|             TIdStackLocalAddressIPv6.Create(AAddresses, TranslateTInAddrToString( PSockAddr_In6(LAddrInfo^.ai_addr)^.sin6_addr, Id_IPv6));
 | |
|           end;
 | |
|         end;
 | |
|         LAddrInfo := LAddrInfo^.ai_next;
 | |
|       until LAddrInfo = nil;
 | |
|     finally
 | |
|       AAddresses.EndUpdate;
 | |
|     end;
 | |
|   finally
 | |
|     freeaddrinfo(LAddrList^);
 | |
|   end;
 | |
| 
 | |
|   {$ENDIF}
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.Bind(ASocket: TIdStackSocketHandle;
 | |
|   const AIP: string; const APort: TIdPort; const AIPVersion: TIdIPVersion);
 | |
| var
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| begin
 | |
|   case AIPVersion of
 | |
|     Id_IPv4: begin
 | |
|         InitSockAddr_In(LAddrIPv4);
 | |
|         if AIP <> '' then begin
 | |
|           TranslateStringToTInAddr(AIP, LAddrIPv4.sin_addr, Id_IPv4);
 | |
|         end;
 | |
|         LAddrIPv4.sin_port := htons(APort);
 | |
|         CheckForSocketError(Posix.SysSocket.bind(ASocket, LAddr, SizeOf(LAddrIPv4)));
 | |
|       end;
 | |
|     Id_IPv6: begin
 | |
|         InitSockAddr_in6(LAddrIPv6);
 | |
|         if AIP <> '' then begin
 | |
|           TranslateStringToTInAddr(AIP, LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|         end;
 | |
|         LAddrIPv6.sin6_port := htons(APort);
 | |
|         CheckForSocketError(Posix.SysSocket.bind(ASocket,LAddr, SizeOf(LAddrIPv6)));
 | |
|       end;
 | |
|     else begin
 | |
|       IPVersionUnsupported;
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.CheckIPVersionSupport(
 | |
|   const AIPVersion: TIdIPVersion): boolean;
 | |
| var
 | |
|   LTmpSocket: TIdStackSocketHandle;
 | |
| begin
 | |
|   // TODO: on nix systems (or maybe just Linux?), an alternative would be to
 | |
|   // check for the existance of the '/proc/net/if_inet6' kernel pseudo-file
 | |
|   LTmpSocket := WSSocket(IdIPFamily[AIPVersion], Id_SOCK_STREAM, Id_IPPROTO_IP );
 | |
|   Result := LTmpSocket <> Id_INVALID_SOCKET;
 | |
|   if Result then begin
 | |
|     WSCloseSocket(LTmpSocket);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.Connect(const ASocket: TIdStackSocketHandle;
 | |
|   const AIP: string; const APort: TIdPort; const AIPVersion: TIdIPVersion);
 | |
| var
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| begin
 | |
|   case AIPVersion of
 | |
|     Id_IPv4: begin
 | |
|       InitSockAddr_In(LAddrIPv4);
 | |
|       TranslateStringToTInAddr(AIP, LAddrIPv4.sin_addr, Id_IPv4);
 | |
|       LAddrIPv4.sin_port := htons(APort);
 | |
|       CheckForSocketError(Posix.SysSocket.connect(ASocket, LAddr, SizeOf(LAddrIPv4)));
 | |
|     end;
 | |
|     Id_IPv6: begin
 | |
|       InitSockAddr_in6(LAddrIPv6);
 | |
|       TranslateStringToTInAddr(AIP, LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|       LAddrIPv6.sin6_port := htons(APort);
 | |
|       CheckForSocketError(Posix.SysSocket.connect(ASocket, LAddr, SizeOf(LAddrIPv6)));
 | |
|     end;
 | |
|     else begin
 | |
|       IPVersionUnsupported;
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
| end;
 | |
| 
 | |
| constructor TIdStackVCLPosix.Create;
 | |
| begin
 | |
|   inherited Create;
 | |
| end;
 | |
| 
 | |
| destructor TIdStackVCLPosix.Destroy;
 | |
| begin
 | |
|   inherited Destroy;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.Disconnect(ASocket: TIdStackSocketHandle);
 | |
| begin
 | |
|   // Windows uses Id_SD_Send, Linux should use Id_SD_Both
 | |
|   WSShutdown(ASocket, Id_SD_Both);
 | |
|   // SO_LINGER is false - socket may take a little while to actually close after this
 | |
|   WSCloseSocket(ASocket);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.GetLastError: Integer;
 | |
| begin
 | |
|   Result := errno;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.GetPeerName(ASocket: TIdStackSocketHandle;
 | |
|   var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion);
 | |
| var
 | |
|   i: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| begin
 | |
|   i := SizeOf(LAddrStore);
 | |
|   CheckForSocketError(Posix.SysSocket.getpeername(ASocket, LAddr, i));
 | |
|   case LAddrStore.ss_family of
 | |
|     Id_PF_INET4: begin
 | |
|       VIP := TranslateTInAddrToString(LAddrIPv4.sin_addr, Id_IPv4);
 | |
|       VPort := ntohs(LAddrIPv4.sin_port);
 | |
|       VIPVersion := Id_IPV4;
 | |
|     end;
 | |
|     Id_PF_INET6: begin
 | |
|       VIP := TranslateTInAddrToString(LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|       VPort := ntohs(LAddrIPv6.sin6_port);
 | |
|       VIPVersion := Id_IPV6;
 | |
|     end;
 | |
|     else begin
 | |
|       IPVersionUnsupported;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.GetSocketName(ASocket: TIdStackSocketHandle;
 | |
|   var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion);
 | |
| var
 | |
|   LiSize: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| begin
 | |
|   LiSize := SizeOf(LAddrStore);
 | |
|   CheckForSocketError(getsockname(ASocket, LAddr, LiSize));
 | |
|   case LAddrStore.ss_family of
 | |
|     Id_PF_INET4: begin
 | |
|       VIP := TranslateTInAddrToString(LAddrIPv4.sin_addr, Id_IPv4);
 | |
|       VPort := ntohs(LAddrIPv4.sin_port);
 | |
|       VIPVersion := Id_IPV4;
 | |
|     end;
 | |
|     Id_PF_INET6: begin
 | |
|       VIP := TranslateTInAddrToString(LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|       VPort := ntohs(LAddrIPv6.sin6_port);
 | |
|       VIPVersion := Id_IPV6;
 | |
|     end;
 | |
|     else begin
 | |
|       IPVersionUnsupported;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.HostByAddress(const AAddress: string;
 | |
|   const AIPVersion: TIdIPVersion): string;
 | |
| var
 | |
|   LiSize: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
|   LHostName : array[0..NI_MAXHOST] of TIdAnsiChar;
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   LHostNamePtr: TPtrWrapper;
 | |
|   {$ENDIF}
 | |
|   LRet : Integer;
 | |
|   LHints : addrinfo;
 | |
|   LAddrInfo: pAddrInfo;
 | |
| begin
 | |
|   LiSize := 0;
 | |
|   case AIPVersion of
 | |
|     Id_IPv4 :
 | |
|     begin
 | |
|       InitSockAddr_In(LAddrIPv4);
 | |
|       TranslateStringToTInAddr(AAddress,LAddrIPv4.sin_addr,Id_IPv4);
 | |
|       LiSize := SizeOf(SockAddr_In);
 | |
|     end;
 | |
|     Id_IPv6 :
 | |
|     begin
 | |
|       InitSockAddr_In6(LAddrIPv6);
 | |
|       TranslateStringToTInAddr(AAddress,LAddrIPv6.sin6_addr,Id_IPv6);
 | |
|       LiSize := SizeOf(SockAddr_In6);
 | |
|     end
 | |
|   else
 | |
|     IPVersionUnsupported;
 | |
|   end;
 | |
|   FillChar(LHostName[0],Length(LHostName),0);
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   LHostNamePtr := TPtrWrapper.Create(@LHostName[0]);
 | |
|   {$ENDIF}
 | |
|   LRet := getnameinfo(LAddr,LiSize,
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     LHostNamePtr.ToPointer
 | |
|     {$ELSE}
 | |
|     LHostName
 | |
|     {$ENDIF},
 | |
|     NI_MAXHOST,nil,0,NI_NAMEREQD );
 | |
|   if LRet <> 0 then begin
 | |
|     if LRet = EAI_SYSTEM then begin
 | |
|       RaiseLastOSError;
 | |
|     end else begin
 | |
|       raise EIdReverseResolveError.CreateFmt(RSReverseResolveError, [AAddress, gai_strerror(LRet), LRet]);
 | |
|     end;
 | |
|   end;
 | |
| {
 | |
| IMPORTANT!!!
 | |
| 
 | |
| getnameinfo can return either results from a numeric to text conversion or
 | |
| results from a DNS reverse lookup.  Someone could make a malicous PTR record
 | |
| such as
 | |
| 
 | |
|    1.0.0.127.in-addr.arpa. IN PTR  10.1.1.1
 | |
| 
 | |
| and trick a caller into beleiving the socket address is 10.1.1.1 instead of
 | |
| 127.0.0.1.  If there is a numeric host in LAddr, than this is the case and
 | |
| we disregard the result and raise an exception.
 | |
| }
 | |
|   FillChar(LHints, SizeOf(LHints), 0);
 | |
|   LHints.ai_socktype := SOCK_DGRAM; //*dummy*/
 | |
|   LHints.ai_flags := AI_NUMERICHOST;
 | |
|   if getaddrinfo(
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     LHostNamePtr.ToPointer
 | |
|     {$ELSE}
 | |
|     LHostName
 | |
|     {$ENDIF},
 | |
|     '0', LHints, LAddrInfo) = 0 then
 | |
|   begin
 | |
|     freeaddrinfo(LAddrInfo^);
 | |
|     Result := '';
 | |
|     raise EIdMaliciousPtrRecord.Create(RSMaliciousPtrRecord);
 | |
|   end;
 | |
| 
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   Result := TMarshal.ReadStringAsAnsi(LHostNamePtr);
 | |
|   {$ELSE}
 | |
|   Result := String(LHostName);
 | |
|   {$ENDIF}
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.HostByName(const AHostName: string;
 | |
|   const AIPVersion: TIdIPVersion): string;
 | |
| var
 | |
|   LAddrInfo: pAddrInfo;
 | |
|   LHints: AddrInfo;
 | |
|   LRetVal: Integer;
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   M: TMarshaller;
 | |
|   {$ENDIF}
 | |
| begin
 | |
|   if not (AIPVersion in [Id_IPv4, Id_IPv6]) then begin
 | |
|     IPVersionUnsupported;
 | |
|   end;
 | |
|   //IMPORTANT!!!
 | |
|   //
 | |
|   //The Hints structure must be zeroed out or you might get an AV.
 | |
|   //I've seen this in Mac OS X
 | |
|   FillChar(LHints, SizeOf(LHints), 0);
 | |
|   LHints.ai_family := IdIPFamily[AIPVersion];
 | |
|   LHints.ai_socktype := SOCK_STREAM;
 | |
|   LAddrInfo := nil;
 | |
| 
 | |
|   LRetVal := getaddrinfo(
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     M.AsAnsi(AHostName).ToPointer
 | |
|     {$ELSE}
 | |
|     PAnsiChar(
 | |
|       {$IFDEF STRING_IS_ANSI}
 | |
|       AHostName
 | |
|       {$ELSE}
 | |
|       AnsiString(AHostName) // explicit convert to Ansi
 | |
|       {$ENDIF}
 | |
|     )
 | |
|     {$ENDIF},
 | |
|     nil, LHints, LAddrInfo);
 | |
|   if LRetVal <> 0 then begin
 | |
|     if LRetVal = EAI_SYSTEM then begin
 | |
|       RaiseLastOSError;
 | |
|     end else begin
 | |
|       raise EIdResolveError.CreateFmt(RSReverseResolveError, [AHostName, gai_strerror(LRetVal), LRetVal]);
 | |
|     end;
 | |
|   end;
 | |
|   try
 | |
|     if AIPVersion = Id_IPv4 then begin
 | |
|       Result := TranslateTInAddrToString( PSockAddr_In( LAddrInfo^.ai_addr)^.sin_addr, AIPVersion);
 | |
|     end else begin
 | |
|       Result := TranslateTInAddrToString( PSockAddr_In6( LAddrInfo^.ai_addr)^.sin6_addr, AIPVersion);
 | |
|     end;
 | |
|   finally
 | |
|     freeaddrinfo(LAddrInfo^);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.HostToNetwork(AValue: UInt32): UInt32;
 | |
| begin
 | |
|  Result := htonl(AValue);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.HostToNetwork(AValue: UInt16): UInt16;
 | |
| begin
 | |
|   Result := htons(AValue);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.HostToNetwork(AValue: TIdUInt64): TIdUInt64;
 | |
| var
 | |
|   LParts: TIdUInt64Parts;
 | |
|   L: UInt32;
 | |
| begin
 | |
|   LParts.QuadPart := AValue;
 | |
|   L := htonl(LParts.HighPart);
 | |
|   if (L <> LParts.HighPart) then begin
 | |
|     LParts.HighPart := htonl(LParts.LowPart);
 | |
|     LParts.LowPart := L;
 | |
|   end;
 | |
|   Result := LParts.QuadPart;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.IOControl(const s: TIdStackSocketHandle;
 | |
|   const cmd: UInt32; var arg: UInt32): Integer;
 | |
| begin
 | |
|   Result := ioctl(s, cmd, @arg);
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.Listen(ASocket: TIdStackSocketHandle;
 | |
|   ABackLog: Integer);
 | |
| begin
 | |
|   CheckForSocketError(Posix.SysSocket.listen(ASocket, ABacklog));
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.NetworkToHost(AValue: UInt32): UInt32;
 | |
| begin
 | |
|   Result := ntohl(AValue);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.NetworkToHost(AValue: TIdUInt64): TIdUInt64;
 | |
| var
 | |
|   LParts: TIdUInt64Parts;
 | |
|   L: UInt32;
 | |
| begin
 | |
|   LParts.QuadPart := AValue;
 | |
|   L := ntohl(LParts.HighPart);
 | |
|   if (L <> LParts.HighPart) then begin
 | |
|     LParts.HighPart := ntohl(LParts.LowPart);
 | |
|     LParts.LowPart := L;
 | |
|   end;
 | |
|   Result := LParts.QuadPart;
 | |
| 
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.NetworkToHost(AValue: UInt16): UInt16;
 | |
| begin
 | |
|    Result := ntohs(AValue);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.ReadHostName: string;
 | |
| const
 | |
|   sMaxHostSize = 250;
 | |
| var
 | |
|   LStr: array[0..sMaxHostSize] of TIdAnsiChar;
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   LStrPtr: TPtrWrapper;
 | |
|   {$ENDIF}
 | |
| begin
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   LStrPtr := TPtrWrapper.Create(@LStr[0]);
 | |
|   {$ENDIF}
 | |
|   gethostname(
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     LStrPtr.ToPointer
 | |
|     {$ELSE}
 | |
|     LStr
 | |
|     {$ENDIF}, sMaxHostSize);
 | |
|   LStr[sMaxHostSize] := TIdAnsiChar(0);
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   Result := TMarshal.ReadStringAsAnsi(LStrPtr);
 | |
|   {$ELSE}
 | |
|   Result := String(LStr);
 | |
|   {$ENDIF}
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.ReceiveMsg(ASocket: TIdStackSocketHandle;
 | |
|   var VBuffer: TIdBytes; APkt: TIdPacketInfo): UInt32;
 | |
| var
 | |
|   LSize: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
|   LMsg : msghdr;
 | |
|   LIOV : iovec;
 | |
|   LControl : TIdBytes;
 | |
|   LCurCmsg : Pcmsghdr;   //for iterating through the control buffer
 | |
|   LByte : PByte;
 | |
| 
 | |
| begin
 | |
|   //we call the macro twice because we specified two possible structures.
 | |
|   //Id_IPV6_HOPLIMIT and Id_IPV6_PKTINFO
 | |
|   LSize := CMSG_LEN(CMSG_LEN(Length(VBuffer)));
 | |
|   SetLength( LControl,LSize);
 | |
| 
 | |
|   LIOV.iov_len := Length(VBuffer); // Length(VMsgData);
 | |
|   LIOV.iov_base := @VBuffer[0]; // @VMsgData[0];
 | |
| 
 | |
|   FillChar(LMsg,SizeOf(LMsg),0);
 | |
| 
 | |
|   LMsg.msg_iov := @LIOV;//lpBuffers := @LMsgBuf;
 | |
|   LMsg.msg_iovlen := 1;
 | |
| 
 | |
|   LMsg.msg_controllen := LSize;
 | |
|   LMsg.msg_control := @LControl[0];
 | |
| 
 | |
|   LMsg.msg_name := @LAddr;
 | |
|   LMsg.msg_namelen := SizeOf(LAddrStore);
 | |
| 
 | |
|   Result := 0;
 | |
|   CheckForSocketError(RecvMsg(ASocket, LMsg, Result));
 | |
|   APkt.Reset;
 | |
| 
 | |
|   case LAddrStore.ss_family of
 | |
|     Id_PF_INET4: begin
 | |
|       APkt.SourceIP := TranslateTInAddrToString(LAddrIPv4.sin_addr, Id_IPv4);
 | |
|       APkt.SourcePort := ntohs(LAddrIPv4.sin_port);
 | |
|       APkt.SourceIPVersion := Id_IPv4;
 | |
|     end;
 | |
|     Id_PF_INET6: begin
 | |
|       APkt.SourceIP := TranslateTInAddrToString(LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|       APkt.SourcePort := ntohs(LAddrIPv6.sin6_port);
 | |
|       APkt.SourceIPVersion := Id_IPv6;
 | |
|     end;
 | |
|     else begin
 | |
|       Result := 0; // avoid warning
 | |
|       IPVersionUnsupported;
 | |
|     end;
 | |
|   end;
 | |
| 
 | |
|   LCurCmsg := nil;
 | |
|   repeat
 | |
|     LCurCmsg := CMSG_NXTHDR(@LMsg, LCurCmsg);
 | |
|     if LCurCmsg = nil then begin
 | |
|       break;
 | |
|     end;
 | |
|     case LCurCmsg^.cmsg_type of
 | |
|       IPV6_PKTINFO :     //done this way because IPV6_PKTINF and IP_PKTINFO are both 19
 | |
|       begin
 | |
|         case LAddrStore.ss_family of
 | |
|           Id_PF_INET4: begin
 | |
|             {$IFDEF IOS}
 | |
|             ToDo('PKTINFO not implemented for IPv4 under iOS yet');
 | |
|             {$ELSE}
 | |
|               {$IFNDEF DARWIN}
 | |
|               //This is not supported in OS X.
 | |
|               with Pin_pktinfo(CMSG_DATA(LCurCmsg))^ do begin
 | |
|                 APkt.DestIP := TranslateTInAddrToString(ipi_addr, Id_IPv4);
 | |
|                 APkt.DestIF := ipi_ifindex;
 | |
|               end;
 | |
|               APkt.DestIPVersion := Id_IPv4;
 | |
|               {$ENDIF}
 | |
|             {$ENDIF}
 | |
|           end;
 | |
|           Id_PF_INET6: begin
 | |
|             with pin6_pktinfo(CMSG_DATA(LCurCmsg))^ do begin
 | |
|               APkt.DestIP := TranslateTInAddrToString(ipi6_addr, Id_IPv6);
 | |
|               APkt.DestIF :=  ipi6_ifindex;
 | |
|             end;
 | |
|             APkt.DestIPVersion := Id_IPv6;
 | |
|           end;
 | |
|         end;
 | |
|       end;
 | |
|       Id_IPV6_HOPLIMIT :
 | |
|       begin
 | |
|         LByte :=  PByte(CMSG_DATA(LCurCmsg));
 | |
|         APkt.TTL := LByte^;
 | |
|       end;
 | |
|     end;
 | |
|   until False;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.RecvFrom(const ASocket: TIdStackSocketHandle;
 | |
|   var VBuffer; const ALength, AFlags: Integer; var VIP: string;
 | |
|   var VPort: TIdPort; var VIPVersion: TIdIPVersion): Integer;
 | |
| var
 | |
|   LiSize: socklen_t;
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
| 
 | |
| begin
 | |
|   LiSize := SizeOf(LAddrStore);
 | |
|   Result := Posix.SysSocket.recvfrom(ASocket,VBuffer, ALength, AFlags or Id_MSG_NOSIGNAL, LAddr, LiSize);
 | |
|   if Result >= 0 then
 | |
|   begin
 | |
|     case LAddrStore.ss_family of
 | |
|       Id_PF_INET4: begin
 | |
|         VIP := TranslateTInAddrToString(LAddrIPv4.sin_addr, Id_IPv4);
 | |
|         VPort := ntohs(LAddrIPv4.sin_port);
 | |
|         VIPVersion := Id_IPV4;
 | |
|       end;
 | |
|       Id_PF_INET6: begin
 | |
|         VIP := TranslateTInAddrToString(LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|         VPort := ntohs(LAddrIPv6.sin6_port);
 | |
|         VIPVersion := Id_IPV6;
 | |
|       end;
 | |
|       else begin
 | |
|         Result := 0;
 | |
|         IPVersionUnsupported;
 | |
|       end;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.SetBlocking(ASocket: TIdStackSocketHandle;
 | |
|   const ABlocking: Boolean);
 | |
| begin
 | |
|   if not ABlocking then begin
 | |
|     raise EIdNonBlockingNotSupported.Create(RSStackNonBlockingNotSupported);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.SetLastError(const AError: Integer);
 | |
| begin
 | |
|   __error^ := AError;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.{$IFDEF VCL_XE3_OR_ABOVE}GetSocketOption{$ELSE}WSGetSocketOption{$ENDIF}
 | |
|   (ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel; AOptName: TIdSocketOption;
 | |
|   var AOptVal; var AOptLen: Integer);
 | |
| var
 | |
|   LLen : socklen_t;
 | |
| begin
 | |
|   LLen := AOptLen;
 | |
|   CheckForSocketError(Posix.SysSocket.getsockopt(ASocket, ALevel, AOptName, AOptVal, LLen));
 | |
|   AOptLen := LLen;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.{$IFDEF VCL_XE3_OR_ABOVE}SetSocketOption{$ELSE}WSSetSocketOption{$ENDIF}
 | |
|   (ASocket: TIdStackSocketHandle; ALevel: TIdSocketOptionLevel; AOptName: TIdSocketOption;
 | |
|   const AOptVal; const AOptLen: Integer);
 | |
| begin
 | |
|   CheckForSocketError(Posix.SysSocket.setsockopt(ASocket, ALevel, AOptName, AOptVal, AOptLen));
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.SupportsIPv6: Boolean;
 | |
| begin
 | |
|   //In Windows, this does something else.  It checks the LSP's installed.
 | |
|   Result := CheckIPVersionSupport(Id_IPv6);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WouldBlock(const AResult: Integer): Boolean;
 | |
| begin
 | |
|   //non-blocking does not exist in Linux, always indicate things will block
 | |
|   Result := True;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.WriteChecksum(s: TIdStackSocketHandle;
 | |
|   var VBuffer: TIdBytes; const AOffset: Integer; const AIP: String;
 | |
|   const APort: TIdPort; const AIPVersion: TIdIPVersion);
 | |
| begin
 | |
|   case AIPVersion of
 | |
|     Id_IPv4 : CopyTIdUInt16(HostToLittleEndian(CalcCheckSum(VBuffer)), VBuffer, AOffset);
 | |
|     Id_IPv6 : WriteChecksumIPv6(s, VBuffer, AOffset, AIP, APort);
 | |
|   else
 | |
|     IPVersionUnsupported;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.WriteChecksumIPv6(s: TIdStackSocketHandle;
 | |
|   var VBuffer: TIdBytes; const AOffset: Integer; const AIP: String;
 | |
|   const APort: TIdPort);
 | |
| begin
 | |
| //we simply request that the kernal write the checksum when the data
 | |
| //is sent.  All of the parameters required are because Windows is bonked
 | |
| //because it doesn't have the IPV6CHECKSUM socket option meaning we have
 | |
| //to querry the network interface in TIdStackWindows -- yuck!!
 | |
|   SetSocketOption(s, Id_IPPROTO_IPV6, IPV6_CHECKSUM, AOffset);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSCloseSocket(ASocket: TIdStackSocketHandle): Integer;
 | |
| begin
 | |
|   Result := __close(ASocket);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSGetLastError: Integer;
 | |
| begin
 | |
|   //IdStackWindows just uses   result := WSAGetLastError;
 | |
|   Result := GetLastError; //System.GetLastOSError; - FPC doesn't define it in System
 | |
|   if Result = Id_WSAEPIPE then begin
 | |
|     Result := Id_WSAECONNRESET;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSGetServByName(const AServiceName: string): TIdPort;
 | |
| var
 | |
|   Lps: PServEnt;
 | |
|   {$IFDEF USE_MARSHALLED_PTRS}
 | |
|   M: TMarshaller;
 | |
|   {$ENDIF}
 | |
| begin
 | |
|   Lps := Posix.NetDB.getservbyname(
 | |
|     {$IFDEF USE_MARSHALLED_PTRS}
 | |
|     M.AsAnsi(AServiceName).ToPointer
 | |
|     {$ELSE}
 | |
|     PAnsiChar(
 | |
|       {$IFDEF STRING_IS_ANSI}
 | |
|       AServiceName
 | |
|       {$ELSE}
 | |
|       AnsiString(AServiceName) // explicit convert to Ansi
 | |
|       {$ENDIF}
 | |
|     )
 | |
|     {$ENDIF},
 | |
|     nil);
 | |
|   if Lps <> nil then begin
 | |
|     Result := ntohs(Lps^.s_port);
 | |
|   end else begin
 | |
|     try
 | |
|       Result := IndyStrToInt(AServiceName);
 | |
|     except
 | |
|       on EConvertError do begin
 | |
|         Result := 0;
 | |
|         IndyRaiseOuterException(EIdInvalidServiceName.CreateFmt(RSInvalidServiceName, [AServiceName]));
 | |
|       end;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.AddServByPortToList(const APortNumber: TIdPort; AAddresses: TStrings);
 | |
| //function TIdStackVCLPosix.WSGetServByPort(const APortNumber: TIdPort): TStrings;
 | |
| type
 | |
|   PPAnsiCharArray = ^TPAnsiCharArray;
 | |
|   TPAnsiCharArray = packed array[0..(MaxInt div SizeOf(PIdAnsiChar))-1] of PIdAnsiChar;
 | |
| var
 | |
|   Lps: PServEnt;
 | |
|   Li: Integer;
 | |
|   Lp: PPAnsiCharArray;
 | |
| begin
 | |
|   Lps := Posix.NetDB.getservbyport(htons(APortNumber), nil);
 | |
|   if Lps <> nil then begin
 | |
|     AAddresses.BeginUpdate;
 | |
|     try
 | |
|       AAddresses.Add(String(Lps^.s_name));
 | |
|       Li := 0;
 | |
|       Lp := Pointer(Lps^.s_aliases);
 | |
|       while Lp[Li] <> nil do begin
 | |
|         AAddresses.Add(String(Lp[Li]));
 | |
|         Inc(Li);
 | |
|       end;
 | |
|     finally
 | |
|       AAddresses.EndUpdate;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSRecv(ASocket: TIdStackSocketHandle; var ABuffer;
 | |
|   const ABufferLength, AFlags: Integer): Integer;
 | |
| begin
 | |
|   //IdStackWindows is just: Result := Recv(ASocket, ABuffer, ABufferLength, AFlags);
 | |
|   Result := Posix.SysSocket.Recv(ASocket, ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSSend(ASocket: TIdStackSocketHandle; const ABuffer;
 | |
|   const ABufferLength, AFlags: Integer): Integer;
 | |
| begin
 | |
|   //CC: Should Id_MSG_NOSIGNAL be included?
 | |
|   //  Result := Send(ASocket, ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL);
 | |
|   Result := CheckForSocketError(Posix.SysSocket.send(ASocket, ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL));
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.WSSendTo(ASocket: TIdStackSocketHandle;
 | |
|   const ABuffer; const ABufferLength, AFlags: Integer; const AIP: string;
 | |
|   const APort: TIdPort; AIPVersion: TIdIPVersion);
 | |
| var
 | |
|   LAddrStore: sockaddr_storage;
 | |
|   LAddrIPv4 : SockAddr_In absolute LAddrStore;
 | |
|   LAddrIPv6 : sockaddr_in6 absolute LAddrStore;
 | |
|   LAddr : sockaddr absolute LAddrStore;
 | |
|   LiSize: socklen_t;
 | |
|   LBytesSent: Integer;
 | |
| begin
 | |
|   case AIPVersion of
 | |
|     Id_IPv4: begin
 | |
|       InitSockAddr_In(LAddrIPv4);
 | |
|       TranslateStringToTInAddr(AIP, LAddrIPv4.sin_addr, Id_IPv4);
 | |
|       LAddrIPv4.sin_port := htons(APort);
 | |
|       LiSize := SizeOf(LAddrIPv4);
 | |
|     end;
 | |
|     Id_IPv6: begin
 | |
|       InitSockAddr_in6(LAddrIPv6);
 | |
|       TranslateStringToTInAddr(AIP, LAddrIPv6.sin6_addr, Id_IPv6);
 | |
|       LAddrIPv6.sin6_port := htons(APort);
 | |
|       LiSize := SizeOf(LAddrIPv6);
 | |
|     end;
 | |
|   else
 | |
|     LiSize := 0; // avoid warning
 | |
|     IPVersionUnsupported;
 | |
|   end;
 | |
|   LBytesSent := Posix.SysSocket.sendto(
 | |
|     ASocket, ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL, LAddr, LiSize);
 | |
|   if LBytesSent = Id_SOCKET_ERROR then begin
 | |
|     // TODO: move this into RaiseLastSocketError directly
 | |
|     if WSGetLastError() = Id_WSAEMSGSIZE then begin
 | |
|       raise EIdPackageSizeTooBig.Create(RSPackageSizeTooBig);
 | |
|     end else begin
 | |
|       RaiseLastSocketError;
 | |
|     end;
 | |
|   end
 | |
|   else if LBytesSent <> ABufferLength then begin
 | |
|     raise EIdNotAllBytesSent.Create(RSNotAllBytesSent);
 | |
|   end;
 | |
| 
 | |
| end;
 | |
| 
 | |
| procedure TIdStackVCLPosix.WSSetLastError(const AErr: Integer);
 | |
| begin
 | |
|   __error^ := AErr;
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSShutdown(ASocket: TIdStackSocketHandle;
 | |
|   AHow: Integer): Integer;
 | |
| begin
 | |
|   Result := Posix.SysSocket.shutdown(ASocket, AHow);
 | |
| end;
 | |
| 
 | |
| function TIdStackVCLPosix.WSSocket(AFamily : Integer; AStruct : TIdSocketType; AProtocol: Integer;
 | |
|       const AOverlapped: Boolean = False): TIdStackSocketHandle;
 | |
| begin
 | |
|   Result := Posix.SysSocket.socket(AFamily, AStruct, AProtocol);
 | |
|   {$IFDEF HAS_SOCKET_NOSIGPIPE}
 | |
|   if Result <> INVALID_SOCKET then begin
 | |
|     SetSocketOption(Result, SOL_SOCKET, SO_NOSIGPIPE, 1);
 | |
|   end;
 | |
|   {$ENDIF}
 | |
| end;
 | |
| 
 | |
| {$I IdUnitPlatformOn.inc}
 | |
| {$I IdSymbolPlatformOn.inc}
 | |
| initialization
 | |
|   GSocketListClass := TIdSocketListVCLPosix;
 | |
| end.
 |