{ Summary: Wrappers for IPv4 and IPv6 socket operations ## $Id: socket.pas,v 1.9 2004/03/30 12:22:42 druid Exp $ } unit socket; interface uses {$IFDEF WIN32} Winsock2; {$ENDIF} {$IFDEF LINUX} KernelIoctl, Libc; {$ENDIF} type {$IFDEF LINUX} TSockAddr = sockaddr; TSockAddr6 = sockaddr_in6; TSockAddr_Storage = sockaddr_storage; {$ENDIF} GSocketTypes = (SOCKTYPE_IPV4, SOCKTYPE_IPV6); GSocket = class private af : integer; fd : TSocket; addr : TSockAddr_Storage; rw_set, ex_set : TFDSet; time : TTimeVal; ip_string : string; host_string : string; public procedure resolve(lookup_hosts : boolean); procedure disconnect(); procedure openPort(port : integer); virtual; procedure setNonBlocking(); function isValid() : boolean; function canRead() : boolean; function canWrite() : boolean; function read(var Buf; len : integer) : integer; function send(s : string) : integer; overload; function send(var s; len : integer) : integer; overload; function acceptConnection(lookup_hosts : boolean) : GSocket; function connect(const remoteName : string; port : integer) : boolean; constructor Create(_af : integer; _fd : TSocket = -1); destructor Destroy; override; property getDescriptor : TSocket read fd; property getAddressFamily : integer read af; property socketAddress : TSockAddr_Storage read addr write addr; property hostString : string read host_string; property ipString : string read ip_string; end; GSocket4 = class(GSocket) private addrv4 : TSockAddrIn; public constructor Create(); overload; constructor Create(fd : TSocket); overload; procedure openPort(port : integer); override; end; GSocket6 = class(GSocket) private addrv6 : TSockAddr6; ssv6 : TSockAddr_Storage; addrv6p : PSockAddr; public constructor Create(); overload; constructor Create(fd : TSocket); overload; procedure openPort(port : integer); override; end; function isSupported(socketType : GSocketTypes) : boolean; overload; function isSupported(af : integer) : boolean; overload; function createSocket(socketType : GSocketTypes; fd : TSocket = -1) : GSocket; overload; function createSocket(af : integer; fd : TSocket = -1) : GSocket; overload; implementation uses SysUtils; {$IFDEF WIN32} var hWSAData : TWSAData; ver : integer; {$ENDIF} { Convert a symbolic socketType to address family } function socketTypeToFamily(socketType : GSocketTypes) : integer; begin case socketType of SOCKTYPE_IPV4: Result := AF_INET; SOCKTYPE_IPV6: Result := AF_INET6; end; end; { Check wether socketType is a supported address family } function isSupported(socketType : GSocketTypes) : boolean; begin Result := isSupported(socketTypeToFamily(socketType)); end; { Check wether af is a supported address family } function isSupported(af : integer) : boolean; {$IFDEF WIN32} var x, ret, size : integer; lpprot : array[0..1] of integer; lpinfo, pinfo : LPWSAProtocol_Info; begin Result := false; size := SizeOf(TWSAProtocol_Info) * 10; GetMem(lpinfo, size); lpprot[0] := IPPROTO_TCP; lpprot[1] := 0; x := size; ret := WSAEnumProtocols(@lpprot, lpinfo, x); if (ret = SOCKET_ERROR) then begin raise Exception.Create('WSAEnumProtocols failed: ' + IntToStr(WSAGetLastError())); end else begin for x := 0 to ret - 1 do begin pinfo := pointer(integer(lpinfo) + (x * sizeof(TWSAProtocol_Info))); if (pinfo^.iAddressFamily = af) then Result := true; end; end; FreeMem(lpinfo, size); end; {$ELSE} var fd : TSocket; begin fd := Libc.socket(af, SOCK_STREAM, IPPROTO_TCP); if (fd = INVALID_SOCKET) then Result := false else Result := true; end; {$ENDIF} { Create a socket of type socketType } function createSocket(socketType : GSocketTypes; fd : TSocket = -1) : GSocket; begin Result := createSocket(socketTypeToFamily(socketType), fd); end; { Create a socket of type af } function createSocket(af : integer; fd : TSocket = -1) : GSocket; begin if (af = AF_INET) then Result := GSocket4.Create(fd) else if (af = AF_INET6) then Result := GSocket6.Create(fd) else raise Exception.Create('Unsupported address family'); end; // GSocket constructor GSocket.Create(_af : integer; _fd : TSocket = -1); begin inherited Create(); af := _af; if (_fd = -1) then begin {$IFDEF WIN32} fd := Winsock2.socket(af, SOCK_STREAM, IPPROTO_TCP); {$ENDIF} {$IFDEF LINUX} fd := Libc.socket(af, SOCK_STREAM, IPPROTO_TCP); {$ENDIF} if (fd = INVALID_SOCKET) then raise Exception.Create('Could not create socket.'); end else fd := _fd; end; destructor GSocket.Destroy; begin disconnect(); inherited Destroy(); end; procedure GSocket.disconnect(); begin {$IFDEF LINUX} __close(fd); {$ENDIF} {$IFDEF WIN32} closesocket(fd); {$ENDIF} end; procedure GSocket.resolve(lookup_hosts : boolean); var h : PHostEnt; l, p : integer; v6 : TSockAddr6; v4 : TSockAddr; begin {$IFDEF LINUX} if (addr.__ss__family = AF_INET) then {$ENDIF} {$IFDEF WIN32} if (addr.ss_family = AF_INET) then {$ENDIF} begin move(addr, v4, sizeof(v4)); ip_string := inet_ntoa(v4.sin_addr); if (lookup_hosts) then begin h := gethostbyaddr(@v4.sin_addr.s_addr, 4, AF_INET); if (h <> nil) then host_string := h.h_name else host_string := ip_string; end else host_string := ip_string; end else {$IFDEF LINUX} if (addr.__ss__family = AF_INET6) then {$ENDIF} {$IFDEF WIN32} if (addr.ss_family = AF_INET6) then {$ENDIF} begin move(addr, v6, sizeof(v6)); l := 0; while (l < 16) do begin p := (byte(v6.sin6_addr.s6_addr[l]) shl 8) + byte(v6.sin6_addr.s6_addr[l + 1]); if (p = 0) then begin ip_string := ip_string + ':'; while (p = 0) do begin p := (byte(v6.sin6_addr.s6_addr[l]) shl 8) + byte(v6.sin6_addr.s6_addr[l + 1]); inc(l, 2); end; end else inc(l, 2); if (ip_string <> '') then ip_string := ip_string + ':'; ip_string := ip_string + lowercase(inttohex(p, 1)); end; host_string := ip_string; end; end; function GSocket.isValid() : boolean; begin Result := (fd <> INVALID_SOCKET); end; function GSocket.canRead() : boolean; begin Result := false; if (not isValid) then exit; FD_ZERO(rw_set); FD_SET(fd, rw_set); FD_ZERO(ex_set); FD_SET(fd, ex_set); time.tv_sec := 0; time.tv_usec := 0; if (select(fd + 1, @rw_set, nil, @ex_set, @time) = SOCKET_ERROR) or (FD_ISSET(fd, ex_set)) then begin fd := INVALID_SOCKET; raise Exception.Create('Connection reset by peer'); end; if (FD_ISSET(fd, rw_set)) then Result := true; end; function GSocket.canWrite() : boolean; begin Result := false; if (not isValid) then exit; FD_ZERO(rw_set); FD_SET(fd, rw_set); FD_ZERO(ex_set); FD_SET(fd, ex_set); time.tv_sec := 0; time.tv_usec := 0; if (select(fd + 1, nil, @rw_set, @ex_set, @time) = SOCKET_ERROR) or (FD_ISSET(fd, ex_set)) then begin fd := INVALID_SOCKET; raise Exception.Create('Connection reset by peer'); end; if (FD_ISSET(fd, rw_set)) then Result := true; end; function GSocket.send(s : string) : integer; begin if (not isValid) then begin Result := -1; exit; end; Result := send(s[1], length(s)); end; function GSocket.send(var s; len : integer) : integer; var res : integer; begin if (not isValid) then begin Result := -1; exit; end; res := 0; if (len > 0) then {$IFDEF WIN32} res := Winsock2.send(fd, s, len, 0); {$ENDIF} {$IFDEF LINUX} res := Libc.send(fd, s, len, 0); {$ENDIF} if (res = SOCKET_ERROR) then begin fd := INVALID_SOCKET; raise Exception.Create('Connection reset by peer'); end; Result := res; end; function GSocket.read(var buf; len : integer) : integer; var res : integer; begin res := recv(fd, buf, len, 0); if (res = SOCKET_ERROR) then begin fd := INVALID_SOCKET; raise Exception.Create('Connection reset by peer'); end; Result := res; end; procedure GSocket.setNonBlocking(); var x : integer; begin if (not isValid) then exit; {$IFDEF WIN32} x := 1; ioctlsocket(fd, FIONBIO, x); {$ENDIF} {$IFDEF LINUX} x := fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, x or O_NONBLOCK); {$ENDIF} end; function GSocket.acceptConnection(lookup_hosts : boolean) : GSocket; var ac_fd : TSocket; client_addr : TSockAddr_Storage; len : integer; sk : GSocket; begin len := 128; {$IFDEF LINUX} ac_fd := accept(fd, PSockAddr(@client_addr), @len); {$ELSE} ac_fd := accept(fd, PSockAddr(@client_addr)^, len); {$ENDIF} sk := createSocket(af, ac_fd); sk.addr := client_addr; sk.resolve(lookup_hosts); Result := sk; end; function GSocket.connect(const remoteName : string; port : integer) : boolean; var addrLength : integer; addrPointer : PChar; sockAddr : TSockAddr; hostent : PHostEnt; begin hostent := gethostbyname(PChar(remoteName)); if (hostent = nil) then raise Exception.Create('Could not resolve hostname ' + remoteName); addrLength := hostent^.h_length; addrPointer := hostent^.h_addr_list^; sockAddr.sin_family := af; sockAddr.sin_port := htons(port); StrMove (PChar(@sockAddr.sin_addr.s_addr), addrPointer, addrLength); {$IFDEF WIN32} Result := WinSock2.connect(fd, sockAddr, sizeof(sockAddr)) = 0; {$ENDIF} {$IFDEF LINUX} Result := Libc.connect(fd, sockAddr, sizeof(sockAddr)) = 0; {$ENDIF} end; procedure GSocket.openPort(port : integer); begin raise Exception.Create('Operation not supported'); end; // GSocket4 constructor GSocket4.Create; begin inherited Create(AF_INET); end; constructor GSocket4.Create(fd : TSocket); begin inherited Create(AF_INET, fd); end; procedure GSocket4.openPort(port : integer); var rc : integer; begin rc := 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @rc, sizeof(rc)) < 0) then raise Exception.Create('Could not set option on IPv4 socket.'); addrv4.sin_family := AF_INET; addrv4.sin_port := htons(port); // addrv4.sin_addr.s_addr := system_info.bind_ip; addrv4.sin_addr.s_addr := INADDR_ANY; if (bind(fd, TSockaddr(addrv4), sizeof(addrv4)) = -1) then begin {$IFDEF LINUX} __close(fd); {$ELSE} closesocket(fd); {$ENDIF} raise Exception.Create('Could not bind on IPv4 port ' + inttostr(port)); end; rc := listen(fd, 15); if (rc > 0) then raise Exception.Create('Could not listen on IPv4 socket'); end; // GSocket6 constructor GSocket6.Create; begin inherited Create(AF_INET6); end; constructor GSocket6.Create(fd : TSocket); begin inherited Create(AF_INET6, fd); end; procedure GSocket6.openPort(port : integer); var rc : integer; begin rc := 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @rc, sizeof(rc)) < 0) then raise Exception.Create('Could not set option on IPv6 socket.'); addrv6.sin6_family := AF_INET6; addrv6.sin6_port := htons(port); move(addrv6, ssv6, sizeof(addrv6)); addrv6p := @ssv6; if (bind(fd, addrv6p^, 128) = -1) then begin {$IFDEF LINUX} __close(fd); {$ELSE} closesocket(fd); {$ENDIF} raise Exception.Create('Could not bind on IPv6 port ' + inttostr(port)); end; rc := listen(fd, 15); if (rc > 0) then raise Exception.Create('Could not listen on IPv6 socket'); end; initialization {$IFDEF WIN32} ver := WINSOCK_VERSION; if (WSAStartup(ver, hWSAData) <> 0) then raise Exception.Create('Could not perform WSAStartup'); {$ENDIF} finalization {$IFDEF WIN32} WSACleanup(); {$ENDIF} end.