{ $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.