changeset 2483:0e85af1ee86d draft

Separate listening sockets, -bind=<addr>
author Pieter Wuille <pieter.wuille@gmail.com>
date Fri, 11 May 2012 15:28:59 +0200
parents 6499c25a47e7
children 8a3291f0342a
files src/init.cpp src/net.cpp src/net.h src/netbase.cpp src/netbase.h
diffstat 5 files changed, 141 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -119,6 +119,18 @@
     return fRet;
 }
 
+bool static Bind(const CService &addr) {
+    if (IsLimited(addr))
+        return false;
+    std::string strError;
+    if (!BindListenPort(addr, strError))
+    {
+        ThreadSafeMessageBox(strError, _("Bitcoin"), wxOK | wxMODAL);
+        return false;
+    }
+    return true;
+}
+
 bool AppInit2(int argc, char* argv[])
 {
 #ifdef _MSC_VER
@@ -193,6 +205,7 @@
             "  -discover        \t  "   + _("Try to discover public IP address (default: 1)") + "\n" +
             "  -irc             \t  "   + _("Find peers using internet relay chat (default: 0)") + "\n" +
             "  -listen          \t  "   + _("Accept connections from outside (default: 1)") + "\n" +
+            "  -bind=<addr>     \t  "   + _("Bind to given address. Use [host]:port notation for IPv6") + "\n" +
 #ifdef QT_GUI
             "  -lang=<lang>     \t\t  " + _("Set language, for example \"de_DE\" (default: system locale)") + "\n" +
 #endif
@@ -548,7 +561,11 @@
 
     if (mapArgs.count("-connect"))
         SoftSetBoolArg("-dnsseed", false);
- 
+
+    // even in Tor mode, if -bind is specified, you really want -listen
+    if (mapArgs.count("-bind"))
+        SoftSetBoolArg("-listen", true);
+
     bool fTor = (fUseProxy && addrProxy.GetPort() == 9050);
     if (fTor)
     {
@@ -588,14 +605,23 @@
     const char* pszP2SH = "/P2SH/";
     COINBASE_FLAGS << std::vector<unsigned char>(pszP2SH, pszP2SH+strlen(pszP2SH));
 
+    bool fBound = false;
     if (!fNoListen)
     {
         std::string strError;
-        if (!BindListenPort(strError))
-        {
-            ThreadSafeMessageBox(strError, _("Bitcoin"), wxOK | wxMODAL);
+        if (mapArgs.count("-bind")) {
+            BOOST_FOREACH(std::string strBind, mapMultiArgs["-bind"]) {
+                fBound |= Bind(CService(strBind, GetDefaultPort(), false));
+            }
+        } else {
+            struct in_addr inaddr_any = {s_addr: INADDR_ANY};
+            fBound |= Bind(CService(inaddr_any, GetDefaultPort()));
+#ifdef USE_IPV6
+            fBound |= Bind(CService(in6addr_any, GetDefaultPort()));
+#endif
+        }
+        if (!fBound)
             return false;
-        }
     }
 
     if (mapArgs.count("-externalip"))
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -52,7 +52,7 @@
 static CNode* pnodeLocalHost = NULL;
 uint64 nLocalHostNonce = 0;
 array<int, THREAD_MAX> vnThreadsRunning;
-static SOCKET hListenSocket = INVALID_SOCKET;
+static std::vector<SOCKET> vhListenSocket;
 CAddrMan addrman;
 
 vector<CNode*> vNodes;
@@ -719,9 +719,10 @@
         FD_ZERO(&fdsetError);
         SOCKET hSocketMax = 0;
 
-        if(hListenSocket != INVALID_SOCKET)
+        BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket) {
             FD_SET(hListenSocket, &fdsetRecv);
-        hSocketMax = max(hSocketMax, hListenSocket);
+            hSocketMax = max(hSocketMax, hListenSocket);
+        }
         {
             LOCK(cs_vNodes);
             BOOST_FOREACH(CNode* pnode, vNodes)
@@ -762,12 +763,13 @@
         //
         // Accept new connections
         //
+        BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
         if (hListenSocket != INVALID_SOCKET && FD_ISSET(hListenSocket, &fdsetRecv))
         {
 #ifdef USE_IPV6
-            struct sockaddr_in6 sockaddr;
+            struct sockaddr_storage sockaddr;
 #else
-            struct sockaddr_in sockaddr;
+            struct sockaddr sockaddr;
 #endif
             socklen_t len = sizeof(sockaddr);
             SOCKET hSocket = accept(hListenSocket, (struct sockaddr*)&sockaddr, &len);
@@ -775,7 +777,8 @@
             int nInbound = 0;
 
             if (hSocket != INVALID_SOCKET)
-                addr = CAddress(sockaddr);
+                if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr))
+                    printf("warning: unknown socket family\n");
 
             {
                 LOCK(cs_vNodes);
@@ -1656,9 +1659,8 @@
 
 
 
-bool BindListenPort(string& strError)
+bool BindListenPort(const CService &addrBind, string& strError)
 {
-    unsigned short nPort = GetListenPort();
     strError = "";
     int nOne = 1;
 
@@ -1676,11 +1678,19 @@
 
     // Create socket for listening for incoming connections
 #ifdef USE_IPV6
-    int nFamily = AF_INET6;
+    struct sockaddr_storage sockaddr;
 #else
-    int nFamily = AF_INET;
+    struct sockaddr sockaddr;
 #endif
-    hListenSocket = socket(nFamily, SOCK_STREAM, IPPROTO_TCP);
+    socklen_t len = sizeof(sockaddr);
+    if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len))
+    {
+        strError = strprintf("Error: bind address family for %s not supported", addrBind.ToString().c_str());
+        printf("%s\n", strError.c_str());
+        return false;
+    }
+
+    SOCKET hListenSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
     if (hListenSocket == INVALID_SOCKET)
     {
         strError = strprintf("Error: Couldn't open socket for incoming connections (socket returned error %d)", WSAGetLastError());
@@ -1699,6 +1709,7 @@
     setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (void*)&nOne, sizeof(int));
 #endif
 
+
 #ifdef WIN32
     // Set to nonblocking, incoming connections will also inherit this
     if (ioctlsocket(hListenSocket, FIONBIO, (u_long*)&nOne) == SOCKET_ERROR)
@@ -1711,38 +1722,33 @@
         return false;
     }
 
-    // The sockaddr_in structure specifies the address family,
-    // IP address, and port for the socket that is being bound
 #ifdef USE_IPV6
-    struct sockaddr_in6 sockaddr = sockaddr_in6();
-    memset(&sockaddr, 0, sizeof(sockaddr));
-    sockaddr.sin6_family = AF_INET6;
-    sockaddr.sin6_addr = in6addr_any;   // bind to all IPs on this computer
-    sockaddr.sin6_port = htons(nPort);
-#  ifdef WIN32
-    int nProtLevel = 10 /* PROTECTION_LEVEL_UNRESTRICTED */;
-    int nParameterId = 23 /* IPV6_PROTECTION_LEVEl */;
-    // this call is allowed to fail
-    setsockopt(hListenSocket, IPPROTO_IPV6, nParameterId, (const char*)&nProtLevel, sizeof(int));
-#  endif
-#else
-    struct sockaddr_in sockaddr = sockaddr_in();
-    memset(&sockaddr, 0, sizeof(sockaddr));
-    sockaddr.sin_family = AF_INET;
-    sockaddr.sin_addr.s_addr = INADDR_ANY; // bind to all IPs on this computer
-    sockaddr.sin_port = htons(nPort);
+    // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option
+    // and enable it by default or not. Try to enable it, if possible.
+    if (addrBind.IsIPv6()) {
+#ifdef IPV6_V6ONLY
+        setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&nOne, sizeof(int));
 #endif
-    if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == SOCKET_ERROR)
+#ifdef WIN32
+        int nProtLevel = 10 /* PROTECTION_LEVEL_UNRESTRICTED */;
+        int nParameterId = 23 /* IPV6_PROTECTION_LEVEl */;
+        // this call is allowed to fail
+        setsockopt(hListenSocket, IPPROTO_IPV6, nParameterId, (const char*)&nProtLevel, sizeof(int));
+#endif
+    }
+#endif
+
+    if (::bind(hListenSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
     {
         int nErr = WSAGetLastError();
         if (nErr == WSAEADDRINUSE)
-            strError = strprintf(_("Unable to bind to port %d on this computer.  Bitcoin is probably already running."), nPort);
+            strError = strprintf(_("Unable to bind to %s on this computer.  Bitcoin is probably already running."), addrBind.ToString().c_str());
         else
-            strError = strprintf("Error: Unable to bind to port %d on this computer (bind returned error %d, %s)", nPort, nErr, strerror(nErr));
+            strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %d, %s)"), addrBind.ToString().c_str(), nErr, strerror(nErr));
         printf("%s\n", strError.c_str());
         return false;
     }
-    printf("Bound to port %d\n", (int)nPort);
+    printf("Bound to %s\n", addrBind.ToString().c_str());
 
     // Listen for incoming connections
     if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR)
@@ -1752,6 +1758,11 @@
         return false;
     }
 
+    vhListenSocket.push_back(hListenSocket);
+
+    if (addrBind.IsRoutable() && GetBoolArg("-discover", true))
+        AddLocal(addrBind, LOCAL_BIND);
+
     return true;
 }
 
@@ -1915,9 +1926,10 @@
         BOOST_FOREACH(CNode* pnode, vNodes)
             if (pnode->hSocket != INVALID_SOCKET)
                 closesocket(pnode->hSocket);
-        if (hListenSocket != INVALID_SOCKET)
-            if (closesocket(hListenSocket) == SOCKET_ERROR)
-                printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError());
+        BOOST_FOREACH(SOCKET hListenSocket, vhListenSocket)
+            if (hListenSocket != INVALID_SOCKET)
+                if (closesocket(hListenSocket) == SOCKET_ERROR)
+                    printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError());
 
 #ifdef WIN32
         // Shutdown Windows Sockets
--- a/src/net.h
+++ b/src/net.h
@@ -38,7 +38,7 @@
 CNode* FindNode(const CService& ip);
 CNode* ConnectNode(CAddress addrConnect, const char *strDest = NULL, int64 nTimeout=0);
 void MapPort(bool fMapPort);
-bool BindListenPort(std::string& strError=REF(std::string()));
+bool BindListenPort(const CService &bindAddr, std::string& strError=REF(std::string()));
 void StartNode(void* parg);
 bool StopNode();
 
@@ -46,6 +46,7 @@
 {
     LOCAL_NONE,   // unknown
     LOCAL_IF,     // address a local interface listens on
+    LOCAL_BIND,   // address explicit bound to
     LOCAL_UPNP,   // address reported by UPnP
     LOCAL_IRC,    // address reported by IRC (deprecated)
     LOCAL_HTTP,   // address reported by whatismyip.com and similars
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -183,7 +183,12 @@
     }
     char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user";
     struct sockaddr_in addr;
-    addrDest.GetSockAddr(&addr);
+    socklen_t len = sizeof(addr);
+    if (!addrDest.GetSockAddr((struct sockaddr*)&addr, &len) || addr.sin_family != AF_INET)
+    {
+        closesocket(hSocket);
+        return error("Cannot get proxy destination address");
+    }
     memcpy(pszSocks4IP + 2, &addr.sin_port, 2);
     memcpy(pszSocks4IP + 4, &addr.sin_addr, 4);
     char* pszSocks4 = pszSocks4IP;
@@ -319,37 +324,18 @@
 {
     hSocketRet = INVALID_SOCKET;
 
+#ifdef USE_IPV6
     struct sockaddr_storage sockaddr;
-    int nFamily = 0;
-    size_t nSockAddrLen = 0;
-
-    if (addrConnect.IsIPv4())
-    {
-        // Use IPv4 stack to connect to IPv4 addresses
-        struct sockaddr_in sockaddr4;
-        if (!addrConnect.GetSockAddr(&sockaddr4))
-            return false;
-        memcpy(&sockaddr, &sockaddr4, sizeof(sockaddr4));
-        nSockAddrLen = sizeof(sockaddr4);
-        nFamily = AF_INET;
-    }
-#ifdef USE_IPV6
-    else if (addrConnect.IsIPv6())
-    {
-        struct sockaddr_in6 sockaddr6;
-        if (!addrConnect.GetSockAddr6(&sockaddr6))
-            return false;
-        memcpy(&sockaddr, &sockaddr6, sizeof(sockaddr6));
-        nSockAddrLen = sizeof(sockaddr6);
-        nFamily = AF_INET6;
-    }
+#else
+    struct sockaddr sockaddr;
 #endif
-    else {
+    socklen_t len = sizeof(sockaddr);
+    if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
         printf("Cannot connect to %s: unsupported network\n", addrConnect.ToString().c_str());
         return false;
     }
 
-    SOCKET hSocket = socket(nFamily, SOCK_STREAM, IPPROTO_TCP);
+    SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
     if (hSocket == INVALID_SOCKET)
         return false;
 #ifdef SO_NOSIGPIPE
@@ -369,7 +355,7 @@
         return false;
     }
 
-    if (connect(hSocket, (struct sockaddr*)&sockaddr, nSockAddrLen) == SOCKET_ERROR)
+    if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
     {
         // WSAEINVAL is here because some legacy version of winsock uses it
         if (WSAGetLastError() == WSAEINPROGRESS || WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINVAL)
@@ -902,6 +888,22 @@
 }
 #endif
 
+bool CService::SetSockAddr(const struct sockaddr *paddr)
+{
+    switch (paddr->sa_family) {
+    case AF_INET:
+        *this = CService(*(const struct sockaddr_in*)paddr);
+        return true;
+#ifdef USE_IPV6
+    case AF_INET6:
+        *this = CService(*(const struct sockaddr_in6*)paddr);
+        return true;
+#endif
+    default:
+        return false;
+    }
+}
+
 CService::CService(const char *pszIpPort, bool fAllowLookup)
 {
     Init();
@@ -954,30 +956,37 @@
     return (CNetAddr)a < (CNetAddr)b || ((CNetAddr)a == (CNetAddr)b && a.port < b.port);
 }
 
-bool CService::GetSockAddr(struct sockaddr_in* paddr) const
+bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
 {
-    if (!IsIPv4())
-        return false;
-    memset(paddr, 0, sizeof(struct sockaddr_in));
-    if (!GetInAddr(&paddr->sin_addr))
-        return false;
-    paddr->sin_family = AF_INET;
-    paddr->sin_port = htons(port);
-    return true;
+    if (IsIPv4()) {
+        if (*addrlen < sizeof(struct sockaddr_in))
+            return false;
+        *addrlen = sizeof(struct sockaddr_in);
+        struct sockaddr_in *paddrin = (struct sockaddr_in*)paddr;
+        memset(paddrin, 0, *addrlen);
+        if (!GetInAddr(&paddrin->sin_addr))
+            return false;
+        paddrin->sin_family = AF_INET;
+        paddrin->sin_port = htons(port);
+        return true;
+    }
+#ifdef USE_IPV6
+    if (IsIPv6()) {
+        if (*addrlen < sizeof(struct sockaddr_in6))
+            return false;
+        *addrlen = sizeof(struct sockaddr_in6);
+        struct sockaddr_in6 *paddrin6 = (struct sockaddr_in6*)paddr;
+        memset(paddrin6, 0, *addrlen);
+        if (!GetIn6Addr(&paddrin6->sin6_addr))
+            return false;
+        paddrin6->sin6_family = AF_INET6;
+        paddrin6->sin6_port = htons(port);
+        return true;
+    }
+#endif
+    return false;
 }
 
-#ifdef USE_IPV6
-bool CService::GetSockAddr6(struct sockaddr_in6* paddr) const
-{
-    memset(paddr, 0, sizeof(struct sockaddr_in6));
-    if (!GetIn6Addr(&paddr->sin6_addr))
-        return false;
-    paddr->sin6_family = AF_INET6;
-    paddr->sin6_port = htons(port);
-    return true;
-}
-#endif
-
 std::vector<unsigned char> CService::GetKey() const
 {
      std::vector<unsigned char> vKey;
--- a/src/netbase.h
+++ b/src/netbase.h
@@ -105,7 +105,8 @@
         void Init();
         void SetPort(unsigned short portIn);
         unsigned short GetPort() const;
-        bool GetSockAddr(struct sockaddr_in* paddr) const;
+        bool GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const;
+        bool SetSockAddr(const struct sockaddr* paddr);
         friend bool operator==(const CService& a, const CService& b);
         friend bool operator!=(const CService& a, const CService& b);
         friend bool operator<(const CService& a, const CService& b);
@@ -117,7 +118,6 @@
 
 #ifdef USE_IPV6
         CService(const struct in6_addr& ipv6Addr, unsigned short port);
-        bool GetSockAddr6(struct sockaddr_in6* paddr) const;
         CService(const struct sockaddr_in6& addr);
 #endif