2 Basic object-oriented network functions
3 Copyright (C) 2010-2018 MegaBrutal
5 This unit is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This unit is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 This unit provides an object-oriented interface to manage TCP/IPv4
24 TTCPConnection - provides methods for sending/receiving buffers through
25 the connection, and some support for text-based communication.
27 TTCPRFCConnection - in addition to TTCPConnection, it provides methods
28 to send/receive RFC-style commands and responses.
30 TTCPListener - opens a port to listen on, and accepts incoming connections
31 through it. Override its "HandleClient" method to serve connected
40 uses Classes
, Sockets
, SocketUtils
, ctypes
, DNSResolve
, NetRFC
, Common
;
45 { These are here so users of this unit don't necessarily have to
46 use Sockets as well. }
47 AF_UNSPEC
= Sockets
.AF_UNSPEC
;
48 AF_INET
= Sockets
.AF_INET
;
49 AF_INET6
= Sockets
.AF_INET6
;
51 { Connection feature requests: }
53 NET_TCP_RFCSUPPORT
= 1;
55 { Default socket timeout: }
56 DEF_SOCK_TIMEOUT
= 5 * 60000; { 5 minutes. }
61 TTCPConnection
= class
62 constructor Create
; overload
;
63 constructor Create(const HostName
: string; Port
: word); overload
;
64 constructor Create(Socket
: socket
); overload
;
65 destructor Destroy
; override;
71 SrcSockAddr
: TSockAddr
;
72 SrcSockAddr6
: TSockAddr6
;
73 DstSockAddr
: TSockAddr6
;
75 function IsNullAddress(SockAddr
: PSockAddr
): boolean;
76 function BindSrcAddr(Socket
: socket
; Family
: word): cint
;
78 function SetBindAddress(Family
: word; const HostName
: string): boolean;
79 function Connect(const HostName
: string; Port
: word): boolean;
81 procedure ReverseDNSLookup
;
82 function VerifyFCrDNS
: boolean;
83 procedure SetSockTimeOut(TimeOut
: DWord
);
84 function ReadBuffer(PtrBuffer
: pointer; Len
: size_t
): ssize_t
;
85 function WriteBuffer(PtrBuffer
: pointer; Len
: size_t
): ssize_t
;
86 function ReadLn(var Line
: string): boolean;
87 function WriteLn(const Line
: string): boolean;
88 property Connected
: boolean read FConnected
;
89 property Socket
: socket read FSocket
;
90 property HostIP
: TIPNamePair read FHostIP
;
91 property SockTimeOut
: DWord read FSockTimeOut write SetSockTimeOut
;
94 TTCPRFCConnection
= class(TTCPConnection
)
96 function ReadCommand(var Command
: shortstring
; var Prms
: string): boolean;
97 function ReadResponse(Response
: TRFCReply
): boolean;
98 function SendCommand(Command
: shortstring
): boolean; overload
;
99 function SendCommand(Command
: shortstring
; Prms
: string): boolean; overload
;
100 function SendResponse(Response
: TRFCReply
): boolean;
104 TTCPAcceptHandler
= procedure(Connection
: TTCPConnection
) of object;
106 TTCPAcceptor
= class(TThread
)
107 constructor Create(Handler
: TTCPAcceptHandler
; TCPConnection
: TTCPConnection
);
109 FHandler
: TTCPAcceptHandler
;
110 FTCPConnection
: TTCPConnection
;
111 procedure Execute
; override;
114 TTCPListener
= class(TThread
)
115 constructor Create(const Address
: string; Port
: word; Family
: word; FeatureRequest
: word);
116 {destructor Destroy; override;}
118 FFeatureRequest
: word;
120 FListenAddress
: string;
122 FListenSocket
: socket
;
123 SockAddr
: TSockAddr6
;
125 procedure HandleClient(Connection
: TTCPConnection
); virtual; abstract;
126 procedure Execute
; override;
128 property ListenPort
: word read FListenPort
;
129 function StartListen
: boolean;
130 procedure StopListen
;
138 constructor TTCPConnection
.Create
;
139 { Create an instance, but don't connect to anywhere yet. }
144 FSockTimeOut
:= DEF_SOCK_TIMEOUT
;
145 FillChar(SrcSockAddr
, SizeOf(SrcSockAddr
), 0);
146 FillChar(SrcSockAddr6
, SizeOf(SrcSockAddr6
), 0);
147 FillChar(DstSockAddr
, SizeOf(DstSockAddr
), 0);
148 SrcSockAddr
.sin_family
:= AF_INET
;
149 SrcSockAddr6
.sin6_family
:= AF_INET6
;
152 constructor TTCPConnection
.Create(const HostName
: string; Port
: word);
153 { Connect to the given port on the given hostname. }
156 Connect(HostName
, Port
);
159 constructor TTCPConnection
.Create(Socket
: socket
);
160 { Use an already connected socket. }
161 var ssocklen
, dsocklen
: TSockLen
;
165 ssocklen
:= SizeOf(SrcSockAddr
);
166 dsocklen
:= SizeOf(DstSockAddr
);
167 fpgetsockname(FSocket
, @SrcSockAddr
, @ssocklen
);
168 fpgetpeername(FSocket
, @DstSockAddr
, @dsocklen
);
169 FHostIP
:= TIPNamePair
.Create('', IPToStr(@DstSockAddr
));
173 destructor TTCPConnection
.Destroy
;
175 if FConnected
then Disconnect
;
180 constructor TTCPAcceptor
.Create(Handler
: TTCPAcceptHandler
; TCPConnection
: TTCPConnection
);
181 { Start a connection handler on a distinct thread. }
184 FTCPConnection
:= TCPConnection
;
185 FreeOnTerminate
:= true;
186 inherited Create(false);
190 constructor TTCPListener
.Create(const Address
: string; Port
: word; Family
: word; FeatureRequest
: word);
193 FListenAddress
:= Address
;
195 FFeatureRequest
:= FeatureRequest
;
196 FreeOnTerminate
:= false;
197 FillChar(SockAddr
, SizeOf(SockAddr
), 0);
198 inherited Create(true);
202 function TTCPConnection
.IsNullAddress(SockAddr
: PSockAddr
): boolean;
204 if SockAddr
^.sin_family
= AF_INET
then
205 Result
:= SockAddr
^.sin_addr
.s_addr
= 0
206 else if SockAddr
^.sin_family
= AF_INET6
then
207 Result
:= (PSockAddr6(SockAddr
)^.sin6_addr
.u6_addr32
[0] = 0)
208 and (PSockAddr6(SockAddr
)^.sin6_addr
.u6_addr32
[1] = 0)
209 and (PSockAddr6(SockAddr
)^.sin6_addr
.u6_addr32
[2] = 0)
210 and (PSockAddr6(SockAddr
)^.sin6_addr
.u6_addr32
[3] = 0)
215 function TTCPConnection
.BindSrcAddr(Socket
: socket
; Family
: word): cint
;
216 var SockAddr
: PSockAddr
; addrlen
: size_t
;
221 SockAddr
:= @SrcSockAddr
;
222 addrlen
:= SizeOf(SrcSockAddr
);
226 SockAddr
:= @SrcSockAddr6
;
227 addrlen
:= SizeOf(SrcSockAddr6
);
231 if not IsNullAddress(SockAddr
) then
232 Result
:= fpBind(Socket
, SockAddr
, addrlen
)
237 function TTCPConnection
.SetBindAddress(Family
: word; const HostName
: string): boolean;
238 var GAIResult
: TGAIResult
; SockAddr
: PSockAddr
;
240 GAIResult
:= ResolveHost(HostName
, Family
);
241 if GAIResult
.GAIError
= 0 then begin
242 case GAIResult
.AddrInfo
^.ai_family
of
243 AF_INET
: SockAddr
:= @SrcSockAddr
;
244 AF_INET6
: SockAddr
:= @SrcSockAddr6
;
246 Move(GAIResult
.AddrInfo
^.ai_addr
^, SockAddr
^, GAIResult
.AddrInfo
^.ai_addrlen
);
254 function TTCPConnection
.Connect(const HostName
: string; Port
: word): boolean;
255 { Resolves the given hostname, and tries to connect it on the given port. }
256 var GAIResult
: TGAIResult
;
258 GAIResult
:= ResolveHost(HostName
, AF_UNSPEC
);
259 if GAIResult
.GAIError
= 0 then begin
260 Move(GAIResult
.AddrInfo
^.ai_addr
^, DstSockAddr
, GAIResult
.AddrInfo
^.ai_addrlen
);
261 DstSockAddr
.sin6_port
:= htons(Port
);
264 FSocket
:= fpSocket(GAIResult
.AddrInfo
^.ai_family
, SOCK_STREAM
, 0);
266 if (FSocket
<> -1) then begin
268 if BindSrcAddr(FSocket
, GAIResult
.AddrInfo
^.ai_family
) = 0 then begin
270 { Try to initiate connection. }
271 FConnected
:= fpConnect(FSocket
, @DstSockAddr
, GAIResult
.AddrInfo
^.ai_addrlen
) <> -1;
273 if FConnected
then begin
274 FHostIP
:= TIPNamePair
.Create(HostName
, IPToStr(@DstSockAddr
));
275 SetSockTimeOut(FSockTimeOut
);
278 CloseSocket(FSocket
);
282 CloseSocket(FSocket
);
291 procedure TTCPConnection
.Disconnect
;
293 fpShutdown(FSocket
, 2);
294 CloseSocket(FSocket
);
300 procedure TTCPConnection
.ReverseDNSLookup
;
301 { Performs a reverse DNS lookup, and updates the HostIP structure. }
302 var NHostIP
: TIPNamePair
;
304 if FConnected
then begin
305 NHostIP
:= TIPNamePair
.Create(ResolveIP(PSockAddr(@DstSockAddr
)), FHostIP
.IP
);
311 function TTCPConnection
.VerifyFCrDNS
: boolean;
312 var GAIResult
: TGAIResult
; ai
: PAddrInfo
;
315 GAIResult
:= ResolveHost(HostIP
.Name
, AF_UNSPEC
);
316 if GAIResult
.GAIError
= 0 then begin
317 ai
:= GAIResult
.AddrInfo
;
318 { One of the addresses must match. }
319 while (ai
<> nil) and not Result
do begin
320 Result
:= IPToStr(ai
^.ai_addr
) = HostIP
.IP
;
326 procedure TTCPConnection
.SetSockTimeOut(TimeOut
: DWord
);
328 FSockTimeOut
:= TimeOut
;
329 if Connected
then begin
330 fpSetSockOpt(FSocket
, SOL_SOCKET
, SO_RCVTIMEO
, @FSockTimeOut
, SizeOf(FSockTimeOut
));
331 fpSetSockOpt(FSocket
, SOL_SOCKET
, SO_SNDTIMEO
, @FSockTimeOut
, SizeOf(FSockTimeOut
));
335 function TTCPConnection
.ReadBuffer(PtrBuffer
: pointer; Len
: size_t
): ssize_t
;
337 Result
:= fpRecv(FSocket
, PtrBuffer
, Len
, 0);
340 function TTCPConnection
.WriteBuffer(PtrBuffer
: pointer; Len
: size_t
): ssize_t
;
342 Result
:= fpSend(FSocket
, PtrBuffer
, Len
, 0);
345 function TTCPConnection
.ReadLn(var Line
: string): boolean;
347 Result
:= SockReadLn(FSocket
, Line
);
350 function TTCPConnection
.WriteLn(const Line
: string): boolean;
352 Result
:= SockWriteLn(FSocket
, Line
);
356 function TTCPRFCConnection
.ReadCommand(var Command
: shortstring
; var Prms
: string): boolean;
358 Result
:= NetRFC
.ReadCommand(FSocket
, Command
, Prms
);
361 function TTCPRFCConnection
.ReadResponse(Response
: TRFCReply
): boolean;
363 Result
:= NetRFC
.ReadResponse(FSocket
, Response
);
366 function TTCPRFCConnection
.SendCommand(Command
: shortstring
): boolean;
368 Result
:= NetRFC
.SendCommand(FSocket
, Command
);
371 function TTCPRFCConnection
.SendCommand(Command
: shortstring
; Prms
: string): boolean;
373 Result
:= NetRFC
.SendCommand(FSocket
, Command
, Prms
);
376 function TTCPRFCConnection
.SendResponse(Response
: TRFCReply
): boolean;
378 Result
:= NetRFC
.SendResponse(FSocket
, Response
);
382 procedure TTCPAcceptor
.Execute
;
384 FHandler(FTCPConnection
);
388 function TTCPListener
.StartListen
: boolean;
389 var GAIResult
: TGAIResult
;
391 FListenSocket
:= fpSocket(FFamily
, SOCK_STREAM
, 0);
392 if FListenSocket
<> -1 then begin
393 GAIResult
:= ResolveHost(FListenAddress
, FFamily
);
394 if GAIResult
.GAIError
= 0 then begin
395 Move(GAIResult
.AddrInfo
^.ai_addr
^, SockAddr
, GAIResult
.AddrInfo
^.ai_addrlen
);
396 SockAddr
.sin6_port
:= htons(FListenPort
);
398 if fpBind(FListenSocket
, @SockAddr
, GAIResult
.AddrInfo
^.ai_addrlen
) <> -1 then begin
399 { It seems the maximum connection value isn't enforced by the
400 Free Pascal library, so this 512 is a constant, dummy value. }
401 if fpListen(FListenSocket
, 512) <> -1 then begin
416 procedure TTCPListener
.StopListen
;
422 procedure TTCPListener
.Execute
;
423 var ClientSocket
: socket
; AcceptFailCount
: word; Len
: longint;
424 TCPConnection
: TTCPConnection
;
426 { Now, accept connections. }
428 while not Terminated
do begin
429 Len
:= SizeOf(SockAddr
);
430 ClientSocket
:= fpAccept(FListenSocket
, @SockAddr
, @Len
);
431 if ClientSocket
<> -1 then begin
434 { Creates the requested TTCPConnection object for the accepted
436 case FFeatureRequest
of
438 TCPConnection
:= TTCPConnection
.Create(ClientSocket
);
440 TCPConnection
:= TTCPRFCConnection
.Create(ClientSocket
);
443 { Then start a new thread with the connection handler. }
444 TTCPAcceptor
.Create(HandleClient
, TCPConnection
);
447 Inc(AcceptFailCount
);
448 if AcceptFailCount
>= 512 then Terminate
;