Bind to user-specified address (BindAddress6)
[mgsmtp.git] / Network.pas
index ec602d1a8995022c6c12ec3a21766d988d49f037..a28ff634077b64e0a77e8c36be148ead9d515017 100644 (file)
@@ -37,7 +37,7 @@
 unit Network;
 
 interface
-uses Classes, Sockets, SocketUtils, DNSResolve, NetRFC, Common;
+uses Classes, Sockets, SocketUtils, ctypes, DNSResolve, NetRFC, Common;
 
 const
 
@@ -61,15 +61,21 @@ type
    TTCPConnection = class
       constructor Create; overload;
       constructor Create(const HostName: string; Port: word); overload;
-      constructor Create(Socket: socket; const Addr: TSockAddr6); overload;
+      constructor Create(Socket: socket); overload;
       destructor Destroy; override;
    private
       FConnected: boolean;
       FSocket: socket;
       FHostIP: TIPNamePair;
       FSockTimeOut: DWord;
-      SockAddr: TSockAddr6;
+      SrcSockAddr: TSockAddr;
+      SrcSockAddr6: TSockAddr6;
+      DstSockAddr: TSockAddr6;
+   protected
+      function IsNullAddress(SockAddr: PSockAddr): boolean;
+      function BindSrcAddr(Socket: socket; Family: word): cint;
    public
+      function SetBindAddress(Family: word; const HostName: string): boolean;
       function Connect(const HostName: string; Port: word): boolean;
       procedure Disconnect;
       procedure ReverseDNSLookup;
@@ -136,6 +142,11 @@ begin
    FConnected:= false;
    FSocket:= -1;
    FSockTimeOut:= DEF_SOCK_TIMEOUT;
+   FillChar(SrcSockAddr, SizeOf(SrcSockAddr), 0);
+   FillChar(SrcSockAddr6, SizeOf(SrcSockAddr6), 0);
+   FillChar(DstSockAddr, SizeOf(DstSockAddr), 0);
+   SrcSockAddr.sin_family:= AF_INET;
+   SrcSockAddr6.sin6_family:= AF_INET6;
 end;
 
 constructor TTCPConnection.Create(const HostName: string; Port: word);
@@ -145,13 +156,17 @@ begin
    Connect(HostName, Port);
 end;
 
-constructor TTCPConnection.Create(Socket: socket; const Addr: TSockAddr6);
+constructor TTCPConnection.Create(Socket: socket);
 { Use an already connected socket. }
+var ssocklen, dsocklen: TSockLen;
 begin
    inherited Create;
    FSocket:= Socket;
-   SockAddr:= Addr;
-   FHostIP:= TIPNamePair.Create('', IPToStr(@Addr));
+   ssocklen:= SizeOf(SrcSockAddr);
+   dsocklen:= SizeOf(DstSockAddr);
+   fpgetsockname(FSocket, @SrcSockAddr, @ssocklen);
+   fpgetpeername(FSocket, @DstSockAddr, @dsocklen);
+   FHostIP:= TIPNamePair.Create('', IPToStr(@DstSockAddr));
    FConnected:= true;
 end;
 
@@ -184,26 +199,84 @@ begin
 end;
 
 
+function TTCPConnection.IsNullAddress(SockAddr: PSockAddr): boolean;
+begin
+   if SockAddr^.sin_family = AF_INET then
+      Result:= SockAddr^.sin_addr.s_addr = 0
+   else if SockAddr^.sin_family = AF_INET6 then
+      Result:= (PSockAddr6(SockAddr)^.sin6_addr.u6_addr32[0] = 0)
+         and (PSockAddr6(SockAddr)^.sin6_addr.u6_addr32[1] = 0)
+         and (PSockAddr6(SockAddr)^.sin6_addr.u6_addr32[2] = 0)
+         and (PSockAddr6(SockAddr)^.sin6_addr.u6_addr32[3] = 0)
+   else
+      Result:= true;
+end;
+
+function TTCPConnection.BindSrcAddr(Socket: socket; Family: word): cint;
+var SockAddr: PSockAddr; addrlen: size_t;
+begin
+   case Family of
+      AF_INET:
+         begin
+            SockAddr:= @SrcSockAddr;
+            addrlen:= SizeOf(SrcSockAddr);
+         end;
+      AF_INET6:
+         begin
+            SockAddr:= @SrcSockAddr6;
+            addrlen:= SizeOf(SrcSockAddr6);
+         end;
+   end;
+
+   if not IsNullAddress(SockAddr) then
+      Result:= fpBind(Socket, SockAddr, addrlen)
+   else
+      Result:= 0;
+end;
+
+function TTCPConnection.SetBindAddress(Family: word; const HostName: string): boolean;
+var GAIResult: TGAIResult; SockAddr: PSockAddr;
+begin
+   GAIResult:= ResolveHost(HostName, Family);
+   if GAIResult.GAIError = 0 then begin
+      case GAIResult.AddrInfo^.ai_family of
+         AF_INET:    SockAddr:= @SrcSockAddr;
+         AF_INET6:   SockAddr:= @SrcSockAddr6;
+      end;
+      Move(GAIResult.AddrInfo^.ai_addr^, SockAddr^, GAIResult.AddrInfo^.ai_addrlen);
+      FreeHost(GAIResult);
+      Result:= true;
+   end
+   else
+      Result:= false;
+end;
+
 function TTCPConnection.Connect(const HostName: string; Port: word): boolean;
 { Resolves the given hostname, and tries to connect it on the given port. }
 var GAIResult: TGAIResult;
 begin
    GAIResult:= ResolveHost(HostName, AF_UNSPEC);
    if GAIResult.GAIError = 0 then begin
-      Move(GAIResult.AddrInfo^.ai_addr^, SockAddr, GAIResult.AddrInfo^.ai_addrlen);
-      SockAddr.sin6_port:= htons(Port);
+      Move(GAIResult.AddrInfo^.ai_addr^, DstSockAddr, GAIResult.AddrInfo^.ai_addrlen);
+      DstSockAddr.sin6_port:= htons(Port);
 
       { Create socket. }
       FSocket:= fpSocket(GAIResult.AddrInfo^.ai_family, SOCK_STREAM, 0);
 
       if (FSocket <> -1) then begin
 
-         { Try to initiate connection. }
-         FConnected:= fpConnect(FSocket, @SockAddr, GAIResult.AddrInfo^.ai_addrlen) <> -1;
+         if BindSrcAddr(FSocket, GAIResult.AddrInfo^.ai_family) = 0 then begin
+
+            { Try to initiate connection. }
+            FConnected:= fpConnect(FSocket, @DstSockAddr, GAIResult.AddrInfo^.ai_addrlen) <> -1;
+
+            if FConnected then begin
+               FHostIP:= TIPNamePair.Create(HostName, IPToStr(@DstSockAddr));
+               SetSockTimeOut(FSockTimeOut);
+            end
+            else
+               CloseSocket(FSocket);
 
-         if FConnected then begin
-            FHostIP:= TIPNamePair.Create(HostName, IPToStr(@SockAddr));
-            SetSockTimeOut(FSockTimeOut);
          end
          else
             CloseSocket(FSocket);
@@ -229,7 +302,7 @@ procedure TTCPConnection.ReverseDNSLookup;
 var NHostIP: TIPNamePair;
 begin
    if FConnected then begin
-      NHostIP:= TIPNamePair.Create(ResolveIP(PSockAddr(@SockAddr)), FHostIP.IP);
+      NHostIP:= TIPNamePair.Create(ResolveIP(PSockAddr(@DstSockAddr)), FHostIP.IP);
       FHostIP.Free;
       FHostIP:= NHostIP;
    end;
@@ -362,9 +435,9 @@ begin
            connection. }
          case FFeatureRequest of
          NET_TCP_BASIC:
-            TCPConnection:= TTCPConnection.Create(ClientSocket, SockAddr);
+            TCPConnection:= TTCPConnection.Create(ClientSocket);
          NET_TCP_RFCSUPPORT:
-            TCPConnection:= TTCPRFCConnection.Create(ClientSocket, SockAddr);
+            TCPConnection:= TTCPRFCConnection.Create(ClientSocket);
          end;
 
          { Then start a new thread with the connection handler. }