{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.


  $Log$


   Rev 1.7    10/26/2004 8:20:04 PM  JPMugaas
 Fixed some oversights with conversion.  OOPS!!!


   Rev 1.6    10/26/2004 8:12:32 PM  JPMugaas
 Now uses TIdStrings and TIdStringList for portability.


   Rev 1.5    12/06/2004 15:17:20  CCostelloe
 Restructured to correspond with IdStackWindows, now works.


   Rev 1.4    07/06/2004 21:31:02  CCostelloe
 Kylix 3 changes


   Rev 1.3    4/18/04 10:43:22 PM  RLebeau
 Fixed syntax error


   Rev 1.2    4/18/04 10:29:46 PM  RLebeau
 Renamed Int64Parts structure to TIdInt64Parts


   Rev 1.1    4/18/04 2:47:28 PM  RLebeau
 Conversion support for Int64 values

 Removed WSHToNs(), WSNToHs(), WSHToNL(), and WSNToHL() methods, obsolete


   Rev 1.0    2004.02.03 3:14:48 PM  czhower
 Move and updates


   Rev 1.3    10/19/2003 5:35:14 PM  BGooijen
 SetSocketOption


   Rev 1.2    2003.10.01 9:11:24 PM  czhower
 .Net


   Rev 1.1    7/5/2003 07:25:50 PM  JPMugaas
 Added functions to the Linux stack which use the new TIdIPAddress record type
 for IP address parameters.  I also fixed a compile bug.


   Rev 1.0    11/13/2002 08:59:24 AM  JPMugaas
}
unit IdStackUnix;
interface

{$i IdCompilerDefines.inc}

{$IFNDEF FPC}
  {$Message Fatal 'IdStackUnix is only for FreePascal.'}
{$ENDIF}
uses
  Classes,
  sockets,
  baseunix,
  IdStack,
  IdStackConsts,
  IdGlobal,
  IdStackBSDBase;

{$IFDEF FREEBSD}
  {$DEFINE SOCK_HAS_SINLEN}
{$ENDIF}
{$IFDEF DARWIN}
  {$DEFINE SOCK_HAS_SINLEN}
{$ENDIF}

type
  {$IFNDEF NO_REDECLARE}
  Psockaddr = ^sockaddr;
  {$ENDIF}

  TIdSocketListUnix = class (TIdSocketList)
  protected
    FCount: Integer;
    FFDSet: TFDSet;
    //
    class function FDSelect(AReadSet: PFDSet; AWriteSet: PFDSet; AExceptSet: PFDSet;
      const ATimeout: Integer = IdTimeoutInfinite): 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: TFDSet);
    procedure SetFDSet(var VSet: TFDSet);
    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;

  TIdStackUnix = 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 WSTranslateSocketErrorMsg(const AErr: Integer): string; 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, AStruct, 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}
    procedure SetKeepAliveValues(ASocket: TIdStackSocketHandle;
      const AEnabled: Boolean; const ATimeMS, AInterval: Integer); override;
    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;

  {$IFNDEF NO_REDECLARE}
  TLinger = record
    l_onoff: UInt16;
    l_linger: UInt16;
  end;
  {$ENDIF}
  TIdLinger = TLinger;

implementation

uses
  netdb,
  unix,
  IdResourceStrings,
  IdResourceStringsUnix,
  IdException,
  SysUtils;


//from: netdbh.inc, we can not include it with the I derrective and netdb.pas
//does not expose it.
{const
  EAI_SYSTEM = -(11);}

const
  FD_SETSIZE = FD_MAXFDSET;
  __FD_SETSIZE = FD_MAXFDSET;
  {$IFDEF DARWIN}
  { MSG_NOSIGNAL does not exist in OS X. Instead we have SO_NOSIGPIPE, which we set in Connect. }
  Id_MSG_NOSIGNAL = 0;
  {$ELSE}
  Id_MSG_NOSIGNAL = MSG_NOSIGNAL;
  {$ENDIF}
  ESysEPIPE = ESysEPIPE;

//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(var VSock : Sockaddr);
{$IFDEF USE_INLINE} inline; {$ENDIF}
begin
  FillChar(VSock, SizeOf(Sockaddr), 0);
  VSock.sin_family := PF_INET;
  {$IFDEF SOCK_HAS_SINLEN}
  VSock.sa_len := SizeOf(Sockaddr);
  {$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;
//
constructor TIdStackUnix.Create;
begin
  inherited Create;
end;

destructor TIdStackUnix.Destroy;
begin
  inherited Destroy;
end;

function TIdStackUnix.GetLastError : Integer;
begin
  Result := SocketError;
end;

procedure TIdStackUnix.SetLastError(Const AError : Integer);
begin
  errno := AError;
end;

function TIdStackUnix.Accept(ASocket: TIdStackSocketHandle;
  var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion): TIdStackSocketHandle;
var
  LA : socklen_t;
  LAddr: sockaddr_in6;
begin
  LA := SizeOf(LAddr);
  Result := fpaccept(ASocket, @LAddr, @LA);
  //calls prefixed by fp to avoid clashing with libc

  if Result <> ID_SOCKET_ERROR then begin
    case LAddr.sin6_family of
      PF_INET : begin
        with Psockaddr(@LAddr)^ do
        begin
          VIP := NetAddrToStr(sin_addr);
          VPort := ntohs(sin_port);
        end;
        VIPVersion := Id_IPv4;
      end;
      PF_INET6: begin
        with LAddr do
        begin
          VIP := NetAddrToStr6(sin6_addr);
          VPort := Ntohs(sin6_port);
        end;
        VIPVersion := Id_IPv6;
      end;
      else begin
        fpclose(Result);
        Result := Id_INVALID_SOCKET;
        IPVersionUnsupported;
      end;
    end;
  end else begin
    if GetLastError = ESysEBADF then begin
      SetLastError(ESysEINTR);
    end;
  end;
end;

procedure TIdStackUnix.Bind(ASocket: TIdStackSocketHandle; const AIP: string;
  const APort: TIdPort; const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION);
var
  LAddr: sockaddr_in6;
begin
  case AIPVersion of
    Id_IPv4: begin
        InitSockAddr(Psockaddr(@LAddr)^);
        with Psockaddr(@LAddr)^ do
        begin
          if AIP <> '' then begin
            sin_addr := StrToNetAddr(AIP);
            //TranslateStringToTInAddr(AIP, sin_addr, Id_IPv4);
          end;
          sin_port := htons(APort);
        end;
        CheckForSocketError(fpBind(ASocket, Psockaddr(@LAddr), SizeOf(sockaddr)));
      end;
    Id_IPv6: begin
        InitSockAddr_in6(LAddr);
        with LAddr do
        begin
          if AIP <> '' then begin
            sin6_addr := StrToNetAddr6(AIP);
            //TranslateStringToTInAddr(AIP, sin6_addr, Id_IPv6);
          end;
          sin6_port := htons(APort);
        end;
        CheckForSocketError(fpBind(ASocket, Psockaddr(@LAddr), SizeOf(Sockaddr_in6)));
      end;
    else begin
      IPVersionUnsupported;
    end;
  end;
end;

function TIdStackUnix.WSCloseSocket(ASocket: TIdStackSocketHandle): Integer;
begin
  Result := fpclose(ASocket);
end;

procedure TIdStackUnix.Connect(const ASocket: TIdStackSocketHandle;
  const AIP: string; const APort: TIdPort;
  const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION);
var
  LAddr: sockaddr_in6;
begin
  case AIPVersion of
    Id_IPv4: begin
      InitSockAddr(Psockaddr(@LAddr)^);
      with Psockaddr(@LAddr)^ do
      begin
        sin_addr := StrToNetAddr(AIP);
        //TranslateStringToTInAddr(AIP, sin_addr, Id_IPv4);
        sin_port := htons(APort);
      end;
      CheckForSocketError(fpConnect(ASocket, Psockaddr(@LAddr), SizeOf(sockaddr)));
    end;
    Id_IPv6: begin
      InitSockAddr_in6(LAddr);
      with LAddr do
      begin
        sin6_addr := StrToNetAddr6(AIP);
        //TranslateStringToTInAddr(AIP, LAddr6.sin6_addr, Id_IPv6);
        sin6_port := htons(APort);
      end;
      CheckForSocketError(fpConnect(ASocket, Psockaddr(@LAddr), SizeOf(sockaddr_in6)));
    end;
    else begin
      IPVersionUnsupported;
    end;
  end;
  {$IFDEF DARWIN}
  SetSocketOption(ASocket, Id_SOL_SOCKET, SO_NOSIGPIPE, 1);
  {$ENDIF}
end;

function TIdStackUnix.HostByName(const AHostName: string;
  const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION): string;
var
  LI4 : array of THostAddr;
  LI6 : array of THostAddr6;
  LH4 : THostEntry;
  LRetVal : Integer;
begin
  case AIPVersion of
    Id_IPv4 :
    begin
      if GetHostByName(AHostName, LH4) then
      begin
        Result := HostAddrToStr(LH4.Addr);
        Exit;
      end;
      SetLength(LI4, 10);
      LRetVal := ResolveName(AHostName, LI4);
      if LRetVal < 1 then begin
        raise EIdResolveError.CreateFmt(RSResolveError, [AHostName, 'Error', LRetVal]); {do not localize}
      end;
      Result := NetAddrToStr(LI4[0]);
    end;
    Id_IPv6 :
    begin
      SetLength(LI6, 10);
      LRetVal :=  ResolveName6(AHostName, LI6);
      if LRetVal < 1 then begin
        raise EIdResolveError.CreateFmt(RSResolveError, [AHostName, LRetVal]);
      end;
      Result := NetAddrToStr6(LI6[0]);
    end;
  end;
end;

function TIdStackUnix.ReadHostName: string;
begin
  Result := GetHostName;
end;

procedure TIdStackUnix.Listen(ASocket: TIdStackSocketHandle; ABackLog: Integer);
begin
  CheckForSocketError(fpListen(ASocket, ABacklog));
end;

function TIdStackUnix.WSRecv(ASocket: TIdStackSocketHandle; var ABuffer;
  const ABufferLength, AFlags: Integer): Integer;
begin
  //IdStackWindows is just: Result := Recv(ASocket, ABuffer, ABufferLength, AFlags);
  Result := fpRecv(ASocket, @ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL);
end;

function TIdStackUnix.RecvFrom(const ASocket: TIdStackSocketHandle; var VBuffer;
      const ALength, AFlags: Integer; var VIP: string; var VPort: TIdPort;
      var VIPVersion: TIdIPVersion): Integer;
var
  LiSize: tsocklen;
  LAddr: sockaddr_in6;
begin
  LiSize := SizeOf(sockaddr_in6);
  Result := fpRecvFrom(ASocket, @VBuffer, ALength, AFlags or Id_MSG_NOSIGNAL, Psockaddr(@LAddr), @LiSize);
  if Result >= 0 then
  begin
    case LAddr.sin6_family of
    Id_PF_INET4 :
      begin
        with Psockaddr(@LAddr)^ do
        begin
          VIP := NetAddrToStr(sin_addr);
          VPort := ntohs(sin_port);
        end;
      end;
    Id_PF_INET6:
      begin
        with LAddr do
        begin
          VIP := NetAddrToStr6(sin6_addr);
          VPort := ntohs(sin6_port);
        end;
        VIPVersion := Id_IPV6;
      end;
    end;
  end;
end;

function TIdStackUnix.ReceiveMsg(ASocket: TIdStackSocketHandle;
  var VBuffer: TIdBytes; APkt: TIdPacketInfo): UInt32;
var
  LIP : String;
  LPort : TIdPort;
  LIPVersion : TIdIPVersion;
begin
  Result := RecvFrom(ASocket, VBuffer, Length(VBuffer), 0, LIP, LPort, LIPVersion);
  APkt.Reset;
  APkt.SourceIP := LIP;
  APkt.SourcePort := LPort;
  APkt.SourceIPVersion := LIPVersion;
  APkt.DestIPVersion := LIPVersion;

  SetLength(VBuffer, Result);
end;
{The stuff below is commented out until I figure out what to do}
{var
  LIP : String;
  LPort : TIdPort;
  LSize: UInt32;
  LAddr: SockAddr_In6;
  LMsg : msghdr;
  LMsgBuf : BUF;
  LControl : TIdBytes;
  LCurCmsg : CMSGHDR;   //for iterating through the control buffer
  LCurPt : Pin_pktinfo;
  LCurPt6 : Pin6_pktinfo;
  LByte : PByte;
  LDummy, LDummy2 : UInt32;
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);

  LMsgBuf.len := Length(VBuffer); // Length(VMsgData);
  LMsgBuf.buf := @VBuffer[0]; // @VMsgData[0];

  FillChar(LMsg,SizeOf(LMsg),0);

  LMsg.lpBuffers := @LMsgBuf;
  LMsg.dwBufferCount := 1;

  LMsg.Control.Len := LSize;
  LMsg.Control.buf := @LControl[0];

  LMsg.name := PSOCKADDR(@LAddr);
  LMsg.namelen := SizeOf(LAddr);
  CheckForSocketError( RecvMsg(ASocket,@LMsg,Result,@LDummy,LPwsaoverlapped_COMPLETION_ROUTINE(@LDummy2)));

  case LAddr.sin6_family of
    Id_PF_INET4: begin
      with PSOCKADDR(@LAddr)^do
      begin
        APkt.SourceIP :=  TranslateTInAddrToString(sin_addr,Id_IPv4);
        APkt.SourcePort := NToHs(sin_port);
      end;
      APkt.SourceIPVersion := Id_IPv4;
    end;
    Id_PF_INET6: begin
      with LAddr do
      begin
        APkt.SourceIP := TranslateTInAddrToString(sin6_addr, Id_IPv6);
        APkt.SourcePort := NToHs(sin6_port);
      end;
      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
      IP_PKTINFO :     //done this way because IPV6_PKTINF and  IP_PKTINFO
      //are both 19
      begin
        case LAddr.sin6_family of
          Id_PF_INET4:
          begin
            LCurPt := WSA_CMSG_DATA(LCurCmsg);
            APkt.DestIP := GWindowsStack.TranslateTInAddrToString(LCurPt^.ipi_addr,Id_IPv4);
            APkt.DestIF := LCurPt^.ipi_ifindex;
            APkt.DestIPVersion := Id_IPv4;
          end;
          Id_PF_INET6:
          begin
            LCurPt6 := WSA_CMSG_DATA(LCurCmsg);
            APkt.DestIP := GWindowsStack.TranslateTInAddrToString(LCurPt6^.ipi6_addr,Id_IPv6);
            APkt.DestIF := LCurPt6^.ipi6_ifindex;
            APkt.DestIPVersion := Id_IPv6;
          end;
        end;
      end;
      Id_IPV6_HOPLIMIT :
      begin
        LByte :=  PByte(WSA_CMSG_DATA(LCurCmsg));
        APkt.TTL := LByte^;
      end;
    end;
  until False; }

function TIdStackUnix.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(fpsend(ASocket, @ABuffer, ABufferLength, AFlags));
end;

procedure TIdStackUnix.WSSendTo(ASocket: TIdStackSocketHandle;
  const ABuffer; const ABufferLength, AFlags: Integer; const AIP: string;
  const APort: TIdPort; AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION);
var
  LAddr : sockaddr_in6;
  LBytesOut: Integer;
begin
  case AIPVersion of
    Id_IPv4 :
      begin
        InitSockAddr(Psockaddr(@LAddr)^);
        with Psockaddr(@LAddr)^ do
        begin
          sin_addr := StrToNetAddr(AIP);
          sin_port := htons(APort);
        end;
        LBytesOut := fpSendTo(ASocket, @ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL, Psockaddr(@LAddr), SizeOf(sockaddr));
      end;
    Id_IPv6:
      begin
        InitSockAddr_in6(LAddr);
        with LAddr do
        begin
          sin6_addr := StrToHostAddr6(AIP);
          //TranslateStringToTInAddr(AIP, sin6_addr, AIPVersion);
          sin6_port := htons(APort);
        end;
        LBytesOut := fpSendTo(ASocket, @ABuffer, ABufferLength, AFlags or Id_MSG_NOSIGNAL, Psockaddr(@LAddr), SizeOf(sockaddr_in6));
      end;
    else begin
      LBytesOut := 0; // avoid warning
      IPVersionUnsupported;
    end;
  end;
  if LBytesOut = -1 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 LBytesOut <> ABufferLength then begin
    raise EIdNotAllBytesSent.Create(RSNotAllBytesSent);
  end;
end;

procedure TIdStackUnix.{$IFDEF VCL_XE3_OR_ABOVE}GetSocketOption{$ELSE}WSGetSocketOption{$ENDIF}
  (ASocket: TIdStackSocketHandle; ALevel: TIdSocketProtocol; AOptName: TIdSocketOption;
  var AOptVal; var AOptLen: Integer);
var
  LLen : TSockLen;
begin
  LLen := AOptLen;
  CheckForSocketError(fpGetSockOpt(ASocket, ALevel, AOptName, PAnsiChar(@AOptVal), @LLen));
  AOptLen := LLen;
end;

procedure TIdStackUnix.{$IFDEF VCL_XE3_OR_ABOVE}SetSocketOption{$ELSE}WSSetSocketOption{$ENDIF}
  (ASocket: TIdStackSocketHandle; ALevel: TIdSocketProtocol; AOptName: TIdSocketOption;
  const AOptVal; const AOptLen: Integer);
begin
  CheckForSocketError(fpSetSockOpt(ASocket, ALevel, AOptName, PAnsiChar(@AOptVal), AOptLen));
end;

function TIdStackUnix.WSGetLastError: Integer;
begin
  //IdStackWindows just uses   result := WSAGetLastError;
  Result := GetLastError; //System.GetLastOSError; - FPC doesn't define it in System
  if Result = ESysEPIPE then begin
    Result := Id_WSAECONNRESET;
  end;
end;

procedure TIdStackUnix.WSSetLastError(const AErr : Integer);
begin
  SetLastError(AErr);
end;

function TIdStackUnix.WSSocket(AFamily, AStruct, AProtocol: Integer;
  const AOverlapped: Boolean = False): TIdStackSocketHandle; 
begin
  Result := fpsocket(AFamily, AStruct, AProtocol);
end;

function TIdStackUnix.WSGetServByName(const AServiceName: string): TIdPort;
var
  LS : TServiceEntry;
begin
  if GetServiceByName(AServiceName, '', LS) then begin
    Result := LS.Port;
  end else begin
    raise EIdInvalidServiceName.CreateFmt(RSInvalidServiceName, [AServiceName]);
  end;
end;

function TIdStackUnix.HostToNetwork(AValue: UInt16): UInt16;
begin
  Result := htons(AValue);
end;

function TIdStackUnix.NetworkToHost(AValue: UInt16): UInt16;
begin
  Result := ntohs(AValue);
end;

function TIdStackUnix.HostToNetwork(AValue: UInt32): UInt32;
begin
  {$IFOPT R+} // detect range checking
    {$DEFINE _RPlusWasEnabled}
    {$R-}
  {$ENDIF}
  Result := htonl(AValue);
  // Restore range checking
  {$IFDEF _RPlusWasEnabled} // detect previous setting
    {$UNDEF _RPlusWasEnabled}
    {$R+}
  {$ENDIF}
end;

function TIdStackUnix.NetworkToHost(AValue: UInt32): UInt32;
begin
  {$IFOPT R+} // detect range checking
    {$DEFINE _RPlusWasEnabled}
    {$R-}
  {$ENDIF}
  Result := ntohl(AValue);
  // Restore range checking
  {$IFDEF _RPlusWasEnabled} // detect previous setting
    {$UNDEF _RPlusWasEnabled}
    {$R+}
  {$ENDIF}
end;

{ RP - I'm not sure what endian Linux natively uses, thus the
check to see if the bytes need swapping or not ... }
function TIdStackUnix.HostToNetwork(AValue: TIdUInt64): TIdUInt64;
var
  LParts: TIdUInt64Parts;
  L: UInt32;
begin
  LParts.QuadPart := AValue{$IFDEF TIdUInt64_IS_NOT_NATIVE}.QuadPart{$ENDIF};
  L := htonl(LParts.HighPart);
  if (L <> LParts.HighPart) then begin
    LParts.HighPart := htonl(LParts.LowPart);
    LParts.LowPart := L;
  end;
  Result{$IFDEF TIdUInt64_IS_NOT_NATIVE}.QuadPart{$ENDIF} := LParts.QuadPart;
end;

function TIdStackUnix.NetworkToHost(AValue: TIdUInt64): TIdUInt64;
var
  LParts: TIdUInt64Parts;
  L: UInt32;
begin
  LParts.QuadPart := AValue{$IFDEF TIdUInt64_IS_NOT_NATIVE}.QuadPart{$ENDIF};
  L := ntohl(LParts.HighPart);
  if (L <> LParts.HighPart) then begin
    LParts.HighPart := ntohl(LParts.LowPart);
    LParts.LowPart := L;
  end;
  Result{$IFDEF TIdUInt64_IS_NOT_NATIVE}.QuadPart{$ENDIF} := LParts.QuadPart;
end;

procedure TIdStackUnix.GetLocalAddressList(AAddresses: TIdStackLocalAddressList);
var
  LI4 : array of THostAddr;
  LI6 : array of THostAddr6;
  i : Integer;
  LHostName : String;
begin
  LHostName := GetHostName;
  if LHostName = '' then begin
    RaiseLastSocketError;
  end;
  AAddresses.BeginUpdate;
  try
    if ResolveName(LHostName, LI4) = 0 then
    begin
      for i := Low(LI4) to High(LI4) do
      begin
        TIdStackLocalAddressIPv4.Create(AAddresses, NetAddrToStr(LI4[i]), ''); // TODO: SubNet
      end;
    end;
    if ResolveName6(LHostName, LI6) = 0 then
    begin
      for i := Low(LI6) to High(LI6) do
      begin
        TIdStackLocalAddressIPv6.Create(AAddresses, NetAddrToStr6(LI6[i]));
      end;
    end;
  finally
    AAddresses.EndUpdate;
  end;
end;

function TIdStackUnix.HostByAddress(const AAddress: string;
  const AIPVersion: TIdIPVersion = ID_DEFAULT_IP_VERSION): string;
var
  LI : Array of string;
  LAddr4: THostAddr;
  LAddr6: THostAddr6;
begin
  Result := '';
  case AIPVersion of
    Id_IPv4 :
    begin
      LAddr4 := StrToNetAddr(AAddress);
      if ResolveAddress(LAddr4, LI) = 0 then begin
        Result := LI[0];
      end;
    end;
    Id_IPv6 :
    begin
      LAddr6 := StrToNetAddr6(AAddress);
      if ResolveAddress6(LAddr6, LI) = 0 then begin
        Result := LI[0];
      end;
    end;
  end;
end;

function TIdStackUnix.WSShutdown(ASocket: TIdStackSocketHandle; AHow: Integer): Integer;
begin
  Result := fpShutdown(ASocket, AHow);
end;

procedure TIdStackUnix.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;

procedure TIdStackUnix.GetPeerName(ASocket: TIdStackSocketHandle; var VIP: string;
  var VPort: TIdPort; var VIPVersion: TIdIPVersion);
var
  i: tsocklen;
  LAddr: sockaddr_in6;
begin
  i := SizeOf(LAddr);
  CheckForSocketError(fpGetPeerName(ASocket, @LAddr, @i));
  case LAddr.sin6_family of
    PF_INET: begin
      with Psockaddr(@LAddr)^ do
      begin
        VIP := NetAddrToStr(sin_addr);
        VPort := ntohs(sin_port);
      end;
      VIPVersion := Id_IPv4;
    end;
    PF_INET6: begin
      with LAddr do
      begin
        VIP := NetAddrToStr6(sin6_addr);
        VPort := ntohs(sin6_port);
      end;
      VIPVersion := Id_IPv6;
    end;
    else begin
      IPVersionUnsupported;
    end;
  end;
end;

procedure TIdStackUnix.GetSocketName(ASocket: TIdStackSocketHandle;
  var VIP: string; var VPort: TIdPort; var VIPVersion: TIdIPVersion);
var
  i: tsocklen;
  LAddr: sockaddr_in6;
begin
  i := SizeOf(LAddr);
  CheckForSocketError(fpGetSockName(ASocket, @LAddr, @i));
  case LAddr.sin6_family of
    PF_INET : begin
      with Psockaddr(@LAddr)^ do
      begin
        VIP := NetAddrToStr(sin_addr);
        VPort := ntohs(sin_port);
      end;
      VIPVersion := Id_IPV4;
    end;
    PF_INET6: begin
      with LAddr do
      begin
        VIP := NetAddrToStr6(sin6_addr);
        VPort := ntohs(sin6_port);
      end;
      VIPVersion := Id_IPv6;
    end;
    else begin
      IPVersionUnsupported;
    end;
  end;
end;

procedure TIdStackUnix.AddServByPortToList(const APortNumber: TIdPort; AAddresses: TStrings);
var
  LS : TServiceEntry;
begin
  if GetServiceByPort(APortNumber, '', LS) then begin
    AAddresses.Add(LS.Name);
  end;
end;

function TIdStackUnix.WSTranslateSocketErrorMsg(const AErr: Integer): string;
begin
  //we override this function for the herr constants that
  //are returned by the DNS functions
  //note that this is not really applicable because we are using some
  //FPC functions that do direct DNS lookups without the standard Unix
  //DNS functions.  It sounds odd but I think there's a good reason for it.
  Result := inherited WSTranslateSocketErrorMsg(AErr);
end;

procedure TIdStackUnix.SetBlocking(ASocket: TIdStackSocketHandle;
  const ABlocking: Boolean);
begin
  if not ABlocking then begin
    raise EIdNonBlockingNotSupported.Create(RSStackNonBlockingNotSupported);
  end;
end;

function TIdStackUnix.WouldBlock(const AResult: Integer): Boolean;
begin
  //non-blocking does not exist in Linux, always indicate things will block
  Result := True;
end;

function TIdStackUnix.SupportsIPv6: Boolean;
//In Windows, this does something else.  It checks the LSP's installed.
begin
  Result := CheckIPVersionSupport(Id_IPv6);
end;

function TIdStackUnix.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], Integer(Id_SOCK_STREAM), Id_IPPROTO_IP);
  Result := LTmpSocket <> Id_INVALID_SOCKET;
  if Result then begin
    WSCloseSocket(LTmpSocket);
  end;
end;

procedure TIdStackUnix.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 TIdStackUnix.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 TIdStackUnix.IOControl(const s: TIdStackSocketHandle; const cmd: UInt32;
  var arg: UInt32): Integer;
begin
  Result := fpioctl(s, cmd, @arg);
end;
(*
Why did I remove this again?

 1) it sends SIGPIPE even if the socket is created with the no-sigpipe bit set
    that could be solved by blocking sigpipe within this thread
    This is probably a bug in the Linux kernel, but we could work around it
    by blocking that signal for the time of sending the file (just get the
    sigprocmask, see if pipe bit is set, if not set it and remove again after
    sending the file)

But the more serious reason is another one, which exists in Windows too:
 2) I think that ServeFile is misdesigned:
    ServeFile does not raise an exception if it didn't send all the bytes.
    Now what happens if I have OnExecute assigned like this
      AThread.Connection.ServeFile('...', True); // <-- true to send via kernel
    is that it will return 0, but notice that in this case I didn't ask for the
    result. Net effect is that the thread will loop in OnExecute even if the
    socket is long gone. This doesn't fit Indy semantics at all, exceptions are
    always raised if the remote end disconnects. Even if I would do
      AThread.Connection.ServeFile('...', False);
    then it would raise an exception.
    I think this is a big flaw in the design of the ServeFile function.
    Maybe GServeFile should only return the bytes sent, but then
    TCPConnection.ServeFile() should raise an exception if GServeFile didn't
    send all the bytes.

JM Berg, 2002-09-09

function ServeFile(ASocket: TIdStackSocketHandle; AFileName: string): UInt32;
var
  LFileHandle: integer;
  offset: integer;
  stat: _stat;
begin
  LFileHandle := open(PChar(AFileName), O_RDONLY);
  try
    offset := 0;
    fstat(LFileHandle, stat);
    Result := sendfile(ASocket, LFileHandle, offset, stat.st_size);
//**    if Result = UInt32(-1) then RaiseLastOSError;
  finally libc.__close(LFileHandle); end;
end;
*)

procedure TIdStackUnix.SetKeepAliveValues(ASocket: TIdStackSocketHandle;
  const AEnabled: Boolean; const ATimeMS, AInterval: Integer);
begin
  if AEnabled then begin
    {$IFDEF HAS_TCP_KEEPIDLE}
    SetSocketOption(ASocket, Id_SOL_TCP, Id_TCP_KEEPIDLE, ATimeMS div MSecsPerSec);
    {$ENDIF}
    {$IFDEF HAS_TCP_KEEPINTVL}
    SetSocketOption(ASocket, Id_SOL_TCP, Id_TCP_KEEPINTVL, AInterval div MSecsPerSec);
    {$ENDIF}
  end;
  inherited;
end;

{ TIdSocketListUnix }

procedure TIdSocketListUnix.Add(AHandle: TIdStackSocketHandle);
begin
  Lock;
  try
    if fpFD_ISSET(AHandle, FFDSet) = 0 then begin
      if Count >= FD_SETSIZE then begin
        raise EIdStackSetSizeExceeded.Create(RSSetSizeExceeded);
      end;
      fpFD_SET(AHandle, FFDSet);
      Inc(FCount);
    end;
  finally
    Unlock;
  end;
end;//

procedure TIdSocketListUnix.Clear;
begin
  Lock;
  try
    fpFD_ZERO(FFDSet);
    FCount := 0;
  finally
    Unlock;
  end;
end;

function TIdSocketListUnix.ContainsSocket(AHandle: TIdStackSocketHandle): Boolean;
begin
  Lock;
  try
    Result := fpFD_ISSET(AHandle, FFDSet) > 0;
  finally
    Unlock;
  end;
end;

function TIdSocketListUnix.Count: Integer;
begin
  Lock;
  try
    Result := FCount;
  finally
    Unlock;
  end;
end;//

class function TIdSocketListUnix.FDSelect(AReadSet, AWriteSet, AExceptSet: PFDSet;
  const ATimeout: Integer): Integer;
var
  LTime: TTimeVal;
  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 := fpSelect(FD_SETSIZE, AReadSet, AWriteSet, AExceptSet, LTimePtr);
end;

procedure TIdSocketListUnix.GetFDSet(var VSet: TFDSet);
begin
  Lock;
  try
    VSet := FFDSet;
  finally
    Unlock;
  end;
end;

function TIdSocketListUnix.GetItem(AIndex: Integer): TIdStackSocketHandle;
var
  LIndex, i: Integer;
begin
  Result := 0;
  LIndex := 0;
  //? use FMaxHandle div x
  for i:= 0 to __FD_SETSIZE - 1 do begin
    if fpFD_ISSET(i, FFDSet) = 1 then begin
      if LIndex = AIndex then begin
        Result := i;
        Break;
      end;
      Inc(LIndex);
    end;
  end;
end;

procedure TIdSocketListUnix.Remove(AHandle: TIdStackSocketHandle);
begin
  Lock;
  try
    if fpFD_ISSET(AHandle, FFDSet) = 1 then
    begin
      Dec(FCount);
      fpFD_CLR(AHandle, FFDSet);
    end;
  finally
    Unlock;
  end;
end;//

procedure TIdSocketListUnix.SetFDSet(var VSet: TFDSet);
begin
  Lock;
  try
    FFDSet := VSet;
  finally
    Unlock;
  end;
end;

class function TIdSocketListUnix.Select(AReadList: TIdSocketList; AWriteList: TIdSocketList;
  AExceptList: TIdSocketList; const ATimeout: Integer = IdTimeoutInfinite): Boolean;
var
  LReadSet: TFDSet;
  LWriteSet: TFDSet;
  LExceptSet: TFDSet;
  LPReadSet: PFDSet;
  LPWriteSet: PFDSet;
  LPExceptSet: PFDSet;

  procedure ReadSet(AList: TIdSocketList; var ASet: TFDSet; var APSet: PFDSet);
  begin
    if AList <> nil then begin
      TIdSocketListUnix(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
    TIdSocketListUnix(AReadList).SetFDSet(LReadSet);
  end;
  if AWriteList <> nil then begin
    TIdSocketListUnix(AWriteList).SetFDSet(LWriteSet);
  end;
  if AExceptList <> nil then begin
    TIdSocketListUnix(AExceptList).SetFDSet(LExceptSet);
  end;
end;

function TIdSocketListUnix.SelectRead(const ATimeout: Integer): Boolean;
var
  LSet: TFDSet;
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 TIdSocketListUnix.SelectReadList(var VSocketList: TIdSocketList;
  const ATimeout: Integer = IdTimeoutInfinite): Boolean;
var
  LSet: TFDSet;
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;
    TIdSocketListUnix(VSocketList).SetFDSet(LSet);
  end;
end;

function TIdSocketListUnix.Clone: TIdSocketList;
begin
  Result := TIdSocketListUnix.Create;
  try
    Lock;
    try
      TIdSocketListUnix(Result).SetFDSet(FFDSet);
    finally
      Unlock;
    end;
  except
    FreeAndNil(Result);
    raise;
  end;
end;

initialization
  GSocketListClass := TIdSocketListUnix;

end.

