Browse Source

2009-12-03 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

	Added --interface option.  This feature binds sockets to given
	interface. You can specify interface name, IP address and
	hostname.
	* configure.ac
	* src/OptionHandlerFactory.cc
	* src/SocketCore.cc
	* src/SocketCore.h
	* src/main.cc
	* src/message.h
	* src/prefs.cc
	* src/prefs.h
	* src/usage_text.h
Tatsuhiro Tsujikawa 16 years ago
parent
commit
4156debe5c
12 changed files with 232 additions and 28 deletions
  1. 15 0
      ChangeLog
  2. 6 0
      config.h.in
  3. 3 1
      configure
  4. 3 1
      configure.ac
  5. 10 0
      src/OptionHandlerFactory.cc
  6. 167 26
      src/SocketCore.cc
  7. 13 0
      src/SocketCore.h
  8. 6 0
      src/main.cc
  9. 2 0
      src/message.h
  10. 2 0
      src/prefs.cc
  11. 2 0
      src/prefs.h
  12. 3 0
      src/usage_text.h

+ 15 - 0
ChangeLog

@@ -1,3 +1,18 @@
+2009-12-03  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added --interface option.  This feature binds sockets to given
+	interface. You can specify interface name, IP address and
+	hostname.
+	* configure.ac
+	* src/OptionHandlerFactory.cc
+	* src/SocketCore.cc
+	* src/SocketCore.h
+	* src/main.cc
+	* src/message.h
+	* src/prefs.cc
+	* src/prefs.h
+	* src/usage_text.h
+
 2009-11-29  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Updated man page for bt-prioritize-piece option in -i list.

+ 6 - 0
config.h.in

@@ -162,6 +162,9 @@
 /* Define to 1 if you have the `gethostbyname' function. */
 #undef HAVE_GETHOSTBYNAME
 
+/* Define to 1 if you have the `getifaddrs' function. */
+#undef HAVE_GETIFADDRS
+
 /* Define to 1 if you have the `getpagesize' function. */
 #undef HAVE_GETPAGESIZE
 
@@ -177,6 +180,9 @@
 /* Define if you have the iconv() function and it works. */
 #undef HAVE_ICONV
 
+/* Define to 1 if you have the <ifaddrs.h> header file. */
+#undef HAVE_IFADDRS_H
+
 /* Define to 1 if you have the `inet_aton' function. */
 #undef HAVE_INET_ATON
 

+ 3 - 1
configure

@@ -7682,7 +7682,8 @@ for ac_header in argz.h \
                   termios.h \
                   unistd.h \
 		  utime.h \
-                  wchar.h
+                  wchar.h \
+		  ifaddrs.h
 do :
   as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
 ac_fn_cxx_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
@@ -14402,6 +14403,7 @@ for ac_func in __argz_count \
                 getcwd \
                 gethostbyaddr \
                 gethostbyname \
+		getifaddrs \
                 getpagesize \
                 inet_ntoa \
                 memchr \

+ 3 - 1
configure.ac

@@ -214,7 +214,8 @@ AC_CHECK_HEADERS([argz.h \
                   termios.h \
                   unistd.h \
 		  utime.h \
-                  wchar.h])
+                  wchar.h \
+		  ifaddrs.h])
 
 # Checks for typedefs, structures, and compiler characteristics.
 AC_HEADER_STDBOOL
@@ -263,6 +264,7 @@ AC_CHECK_FUNCS([__argz_count \
                 getcwd \
                 gethostbyaddr \
                 gethostbyname \
+		getifaddrs \
                 getpagesize \
                 inet_ntoa \
                 memchr \

+ 10 - 0
src/OptionHandlerFactory.cc

@@ -240,6 +240,16 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers()
     op->addTag(TAG_BASIC);
     handlers.push_back(op);
   }
+  {
+    SharedHandle<OptionHandler> op(new DefaultOptionHandler
+				   (PREF_INTERFACE,
+				    TEXT_INTERFACE,
+				    NO_DEFAULT_VALUE,
+				    "interface, IP address, hostname",
+				    OptionHandler::REQ_ARG));
+    op->addTag(TAG_ADVANCED);
+    handlers.push_back(op);
+  }
   {
     SharedHandle<OptionHandler> op(new DefaultOptionHandler
 				   (PREF_LOG,

+ 167 - 26
src/SocketCore.cc

@@ -35,6 +35,9 @@
 #include "SocketCore.h"
 
 #include <unistd.h>
+#ifdef HAVE_IFADDRS_H
+# include <ifaddrs.h>
+#endif // HAVE_IFADDRS_H
 
 #include <cerrno>
 #include <cstring>
@@ -51,6 +54,7 @@
 #include "util.h"
 #include "TimeA2.h"
 #include "a2functional.h"
+#include "LogFactory.h"
 #ifdef ENABLE_SSL
 # include "TLSContext.h"
 #endif // ENABLE_SSL
@@ -83,6 +87,10 @@ SocketCore::PollMethod SocketCore::_pollMethod = SocketCore::POLL_METHOD_SELECT;
 
 int SocketCore::_protocolFamily = AF_UNSPEC;
 
+SharedHandle<struct sockaddr_storage> SocketCore::_bindAddr;
+
+socklen_t SocketCore::_bindAddrLen = 0;
+
 #ifdef ENABLE_SSL
 SharedHandle<TLSContext> SocketCore::_tlsContext;
 
@@ -159,46 +167,81 @@ std::string uitos(T value)
   return str;
 }
 
-void SocketCore::bind(uint16_t port, int flags)
+static sock_t bindInternal(int family, int socktype, int protocol,
+			   const struct sockaddr* addr, socklen_t addrlen)
 {
-  closeConnection();
-
-  struct addrinfo hints;
-  struct addrinfo* res;
-  memset(&hints, 0, sizeof(hints));
-  hints.ai_family = _protocolFamily;
-  hints.ai_socktype = _sockType;
-  hints.ai_flags = flags;
-  hints.ai_protocol = 0;
-  int s;
-  s = getaddrinfo(0, uitos(port).c_str(), &hints, &res);
-  if(s) {
-    throw DL_ABORT_EX(StringFormat(EX_SOCKET_BIND, gai_strerror(s)).str());
-  }
-  struct addrinfo* rp;
-  for(rp = res; rp; rp = rp->ai_next) {
-    sock_t fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    sock_t fd = socket(family, socktype, protocol);
     if(fd == (sock_t) -1) {
-      continue;
+      return -1;
     }
     int sockopt = 1;
-    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (a2_sockopt_t) &sockopt, sizeof(sockopt)) < 0) {
+    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (a2_sockopt_t) &sockopt,
+		  sizeof(sockopt)) < 0) {
       CLOSE(fd);
-      continue;
+      return -1;
     }
-    if(::bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
+    if(::bind(fd, addr, addrlen) == -1) {
+      LogFactory::getInstance()->debug("%s", strerror(errno));
       CLOSE(fd);
-      continue;
+      return -1;
+    }
+    return fd;
+}
+
+void SocketCore::bind(uint16_t port, int flags)
+{
+  closeConnection();
+
+  if(flags == 0 || _bindAddr.isNull()) {
+    struct addrinfo hints;
+    struct addrinfo* res;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = _protocolFamily;
+    hints.ai_socktype = _sockType;
+    hints.ai_flags = flags;
+    hints.ai_protocol = 0;
+    int s;
+    s = getaddrinfo(0, uitos(port).c_str(), &hints, &res);
+    if(s) {
+      throw DL_ABORT_EX(StringFormat(EX_SOCKET_BIND, gai_strerror(s)).str());
+    }
+    struct addrinfo* rp;
+    for(rp = res; rp; rp = rp->ai_next) {
+      sock_t fd = bindInternal(rp->ai_family, rp->ai_socktype, rp->ai_protocol,
+			       rp->ai_addr, rp->ai_addrlen);
+      if(fd == (sock_t) -1) {
+	continue;
+      }
+      sockfd = fd;
+      break;
+    }
+    freeaddrinfo(res);
+  } else {
+    sock_t fd = bindInternal
+      (_bindAddr->ss_family, _sockType, 0,
+       reinterpret_cast<const struct sockaddr*>(_bindAddr.get()), _bindAddrLen);
+    if(fd != (sock_t)-1) {
+      sockfd = fd;
     }
-    sockfd = fd;
-    break;
   }
-  freeaddrinfo(res);
   if(sockfd == (sock_t) -1) {
     throw DL_ABORT_EX(StringFormat(EX_SOCKET_BIND, "all addresses failed").str());
   }
 }
 
+void SocketCore::bind(const struct sockaddr* addr, socklen_t addrlen)
+{
+  closeConnection();
+
+  sock_t fd = bindInternal(addr->sa_family, _sockType, 0, addr, addrlen);
+  if(fd != (sock_t)-1) {
+    sockfd = fd;
+  }
+  if(sockfd == (sock_t) -1) {
+    throw DL_ABORT_EX(StringFormat(EX_SOCKET_BIND, strerror(errno)).str());
+  }
+}
+
 void SocketCore::beginListen()
 {
   if(listen(sockfd, 1) == -1) {
@@ -268,6 +311,15 @@ void SocketCore::establishConnection(const std::string& host, uint16_t port)
       CLOSE(fd);
       continue;
     }
+
+    if(!_bindAddr.isNull()) {
+      if(::bind(fd, reinterpret_cast<const struct sockaddr*>(_bindAddr.get()),
+		_bindAddrLen) == -1) {
+	CLOSE(fd);
+	continue;
+      }
+    }
+
     sockfd = fd;
     // make socket non-blocking mode
     setNonBlockingMode();
@@ -1073,4 +1125,93 @@ void SocketCore::useSelect()
   _pollMethod = SocketCore::POLL_METHOD_SELECT;
 }
 
+void SocketCore::bindAddress(const std::string& interface)
+{
+  SharedHandle<struct sockaddr_storage> bindAddr(new struct sockaddr_storage());
+  socklen_t bindAddrLen = 0;
+  memset(bindAddr.get(), 0, sizeof(struct sockaddr_storage));
+  bool found = false;
+#ifdef HAVE_GETIFADDRS
+  // First find interface in interface addresses
+  struct ifaddrs* ifaddr = 0;
+  if(getifaddrs(&ifaddr) == -1) {
+    throw DL_ABORT_EX
+      (StringFormat(MSG_INTERFACE_NOT_FOUND,
+		    interface.c_str(), strerror(errno)).str());
+  } else {
+    auto_delete<struct ifaddrs*> ifaddrDeleter(ifaddr, freeifaddrs);
+    for(struct ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
+      if(!ifa->ifa_addr) {
+	continue;
+      }
+      int family = ifa->ifa_addr->sa_family;
+      if(_protocolFamily == AF_UNSPEC) {
+	if(family != AF_INET && family != AF_INET6) {
+	  continue;
+	}
+      } else if(_protocolFamily == AF_INET) {
+	if(family != AF_INET) {
+	  continue;
+	}
+      } else if(_protocolFamily == AF_INET6) {
+	if(family != AF_INET6) {
+	  continue;
+	}
+      } else {
+	continue;
+      }
+      if(std::string(ifa->ifa_name) == interface) {
+	bindAddrLen =
+	  family == AF_INET?sizeof(struct sockaddr_in):
+	  sizeof(struct sockaddr_in6);
+	memcpy(bindAddr.get(), ifa->ifa_addr, bindAddrLen);
+	found = true;
+	break;
+      }
+    }
+  }
+#endif // HAVE_GETIFADDRS
+  if(!found) {
+    struct addrinfo hints;
+    struct addrinfo* res = 0;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = _protocolFamily;
+    hints.ai_socktype = 0;
+    hints.ai_flags = 0;
+    hints.ai_protocol = 0;
+    int s;
+    s = getaddrinfo(interface.c_str(), 0, &hints, &res);
+    if(s) {
+      throw DL_ABORT_EX
+	(StringFormat(MSG_INTERFACE_NOT_FOUND,
+		      interface.c_str(), gai_strerror(s)).str());
+    } else {
+      auto_delete<struct addrinfo*> resDeleter(res, freeaddrinfo);
+      struct addrinfo* rp;
+      for(rp = res; rp; rp = rp->ai_next) {
+	bindAddrLen = rp->ai_addrlen;
+	memcpy(bindAddr.get(), rp->ai_addr, rp->ai_addrlen);
+	// Try to bind socket with this address. If it fails, the
+	// address is not for this machine.
+	try {
+	  SocketCore socket;
+	  socket.bind
+	    (reinterpret_cast<const struct sockaddr*>(bindAddr.get()),
+	     bindAddrLen);
+	} catch(RecoverableException& e) {
+	  throw DL_ABORT_EX2
+	    (StringFormat(MSG_INTERFACE_NOT_FOUND,
+			  interface.c_str()).str(), e);
+	}
+	found = true;
+	break;
+      }
+    }
+  }
+  if(found) {
+    _bindAddr = bindAddr;
+    _bindAddrLen = bindAddrLen;
+  }
+}
+
 } // namespace aria2

+ 13 - 0
src/SocketCore.h

@@ -92,6 +92,10 @@ private:
 
   static int _protocolFamily;
 
+  static SharedHandle<struct sockaddr_storage> _bindAddr;
+
+  static socklen_t _bindAddrLen;
+
   bool blocking;
   int secure;
 
@@ -124,6 +128,8 @@ private:
 
   void init();
 
+  void bind(const struct sockaddr* addr, socklen_t addrlen);
+
 #ifdef HAVE_EPOLL
 
   void initEPOLL();
@@ -349,6 +355,13 @@ public:
   {
     _protocolFamily = protocolFamily;
   }
+
+  // Bind socket to interface. interface may be specified as a
+  // hostname, IP address or interface name like eth0.  If the given
+  // interface is not found or binding socket is failed, exception
+  // will be thrown.  Set _protocolFamily before calling this function
+  // if you limit protocol family.
+  static void bindAddress(const std::string& interface);
 };
 
 } // namespace aria2

+ 6 - 0
src/main.cc

@@ -208,6 +208,12 @@ downloadresultcode::RESULT main(int argc, char* argv[])
     MessageDigestHelper::staticSHA1DigestInit();
 #endif // ENABLE_MESSAGE_DIGEST
 
+    // Bind interface
+    if(!op->get(PREF_INTERFACE).empty()) {
+      std::string interface = op->get(PREF_INTERFACE);
+      SocketCore::bindAddress(interface);
+    }
+
 #ifdef SIGPIPE
     util::setGlobalSignalHandler(SIGPIPE, SIG_IGN, 0);
 #endif

+ 2 - 0
src/message.h

@@ -175,6 +175,8 @@
 #define MSG_CANNOT_PARSE_XML_RPC_REQUEST "Failed to parse xml-rpc request."
 #define MSG_GOOD_BYE_SEEDER "Client is in seed state: Good Bye Seeder;)"
 #define MSG_NOT_FILE _("Is '%s' a file?")
+#define MSG_INTERFACE_NOT_FOUND _("Failed to find given interface %s,"\
+				  " cause: %s")
 
 #define EX_TIME_OUT _("Timeout.")
 #define EX_INVALID_CHUNK_SIZE _("Invalid chunk size.")

+ 2 - 0
src/prefs.cc

@@ -172,6 +172,8 @@ const std::string PREF_ON_DOWNLOAD_COMPLETE("on-download-complete");
 const std::string PREF_ON_DOWNLOAD_ERROR("on-download-error");
 // value: true | false
 const std::string PREF_XML_RPC_LISTEN_ALL("xml-rpc-listen-all");
+// value: string
+const std::string PREF_INTERFACE("interface");
 
 /**
  * FTP related preferences

+ 2 - 0
src/prefs.h

@@ -176,6 +176,8 @@ extern const std::string PREF_ON_DOWNLOAD_COMPLETE;
 extern const std::string PREF_ON_DOWNLOAD_ERROR;
 // value: true | false
 extern const std::string PREF_XML_RPC_LISTEN_ALL;
+// value: string
+extern const std::string PREF_INTERFACE;
 
 /**
  * FTP related preferences

+ 3 - 0
src/usage_text.h

@@ -575,3 +575,6 @@ _(" --bt-prioritize-piece=head[=SIZE],tail[=SIZE] Try to download first and last
   "                              priority. tail=SIZE means the range of last SIZE\n"\
   "                              bytes of each file. SIZE can include K or M(1K =\n"\
   "                              1024, 1M = 1024K).")
+#define TEXT_INTERFACE \
+_(" --interface=INTERFACE        Bind sockets to given interface. You can specify\n"\
+  "                              interface name, IP address and hostname.")