]> sjero.net Git - wget/blobdiff - src/connect.c
[svn] Use new macros xnew, xnew0, xnew_array, and xnew0_array in various places.
[wget] / src / connect.c
index 26dc404d830978ef81527d00e953450b137ec882..605ccb459da74ac52092a904e1e23bd32f12e6dc 100644 (file)
@@ -6,7 +6,7 @@ This file is part of GNU Wget.
 GNU Wget is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
+ (at your option) any later version.
 
 GNU Wget is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -37,16 +37,14 @@ so, delete this exception statement from your version.  */
 #endif
 #include <assert.h>
 
-#ifdef WINDOWS
-# include <winsock.h>
-#else
+#ifndef WINDOWS
 # include <sys/socket.h>
 # include <netdb.h>
 # include <netinet/in.h>
-#ifndef __BEOS__
-# include <arpa/inet.h>
-#endif
-#endif /* WINDOWS */
+# ifndef __BEOS__
+#  include <arpa/inet.h>
+# endif
+#endif /* not WINDOWS */
 
 #include <errno.h>
 #ifdef HAVE_STRING_H
@@ -67,40 +65,134 @@ so, delete this exception statement from your version.  */
 extern int errno;
 #endif
 
-/* Variables shared by bindport and acceptport: */
-static int msock = -1;
-static struct sockaddr *addr;
-
-static ip_address bind_address;
-static int bind_address_resolved;
+\f
+/* Fill SA as per the data in IP and PORT.  SA shoult point to struct
+   sockaddr_storage if ENABLE_IPV6 is defined, to struct sockaddr_in
+   otherwise.  */
 
 static void
-resolve_bind_address (void)
+sockaddr_set_data (struct sockaddr *sa, const ip_address *ip, int port)
+{
+  switch (ip->type)
+    {
+    case IPV4_ADDRESS:
+      {
+       struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+       sin->sin_family = AF_INET;
+       sin->sin_port = htons (port);
+       sin->sin_addr = ADDRESS_IPV4_IN_ADDR (ip);
+       break;
+      }
+#ifdef ENABLE_IPV6
+    case IPV6_ADDRESS:
+      {
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+       sin6->sin6_family = AF_INET6;
+       sin6->sin6_port = htons (port);
+       sin6->sin6_addr = ADDRESS_IPV6_IN6_ADDR (ip);
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+       sin6->sin6_scope_id = ADDRESS_IPV6_SCOPE (ip);
+#endif
+       break;
+      }
+#endif /* ENABLE_IPV6 */
+    default:
+      abort ();
+    }
+}
+
+/* Get the data of SA, specifically the IP address and the port.  If
+   you're not interested in one or the other information, pass NULL as
+   the pointer.  */
+
+void
+sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port)
+{
+  switch (sa->sa_family)
+    {
+    case AF_INET:
+      {
+       struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+       if (ip)
+         {
+           ip->type = IPV4_ADDRESS;
+           ADDRESS_IPV4_IN_ADDR (ip) = sin->sin_addr;
+         }
+       if (port)
+         *port = ntohs (sin->sin_port);
+       break;
+      }
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      {
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+       if (ip)
+         {
+           ip->type = IPV6_ADDRESS;
+           ADDRESS_IPV6_IN6_ADDR (ip) = sin6->sin6_addr;
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+           ADDRESS_IPV6_SCOPE (ip) = sin6->sin6_scope_id;
+#endif
+         }
+       if (port)
+         *port = ntohs (sin6->sin6_port);
+       break;
+      }
+#endif
+    default:
+      abort ();
+    }
+}
+
+/* Return the size of the sockaddr structure depending on its
+   family.  */
+
+static socklen_t
+sockaddr_size (const struct sockaddr *sa)
+{
+  switch (sa->sa_family)
+    {
+    case AF_INET:
+      return sizeof (struct sockaddr_in);
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      return sizeof (struct sockaddr_in6);
+#endif
+    default:
+      abort ();
+      return 0;                        /* so the compiler shuts up. */
+    }
+}
+\f
+static int
+resolve_bind_address (const char *host, struct sockaddr *sa, int flags)
 {
   struct address_list *al;
 
-  if (bind_address_resolved || opt.bind_address == NULL)
-    /* Nothing to do. */
-    return;
+  /* #### Shouldn't we do this only once?  opt.bind_address won't
+     change during a Wget run!  */
 
-  al = lookup_host (opt.bind_address, 1);
-  if (!al)
+  al = lookup_host (host, flags | LH_SILENT | LH_PASSIVE);
+  if (al == NULL)
     {
       logprintf (LOG_NOTQUIET,
                 _("Unable to convert `%s' to a bind address.  Reverting to ANY.\n"),
                 opt.bind_address);
-      return;
+      return 0;
     }
 
-  address_list_copy_one (al, 0, &bind_address);
+  /* Pick the first address in the list and use it as bind address.
+     Perhaps we should try multiple addresses, but I don't think
+     that's necessary in practice.  */
+  sockaddr_set_data (sa, address_list_address_at (al, 0), 0);
   address_list_release (al);
-  bind_address_resolved = 1;
+  return 1;
 }
 \f
 struct cwt_context {
   int fd;
   const struct sockaddr *addr;
-  int addrlen;
+  socklen_t addrlen;
   int result;
 };
 
@@ -116,8 +208,8 @@ connect_with_timeout_callback (void *arg)
    ETIMEDOUT.  */
 
 static int
-connect_with_timeout (int fd, const struct sockaddr *addr, int addrlen,
-                     int timeout)
+connect_with_timeout (int fd, const struct sockaddr *addr, socklen_t addrlen,
+                     double timeout)
 {
   struct cwt_context ctx;
   ctx.fd = fd;
@@ -134,66 +226,74 @@ connect_with_timeout (int fd, const struct sockaddr *addr, int addrlen,
   return ctx.result;
 }
 \f
-/* A kludge, but still better than passing the host name all the way
-   to connect_to_one.  */
-static const char *connection_host_name;
+/* Connect to a remote endpoint whose IP address is known.  */
 
-void
-set_connection_host_name (const char *host)
-{
-  if (host)
-    assert (connection_host_name == NULL);
-  else
-    assert (connection_host_name != NULL);
-
-  connection_host_name = host;
-}
-
-/* Connect to a remote host whose address has been resolved. */
 int
-connect_to_one (ip_address *addr, unsigned short port, int silent)
+connect_to_ip (const ip_address *ip, int port, const char *print)
 {
-  wget_sockaddr sa;
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
   int sock, save_errno;
 
-  /* Set port and protocol */
-  wget_sockaddr_set_address (&sa, ip_default_family, port, addr);
-
-  if (!silent)
+  /* If PRINT is non-NULL, print the "Connecting to..." line, with
+     PRINT being the host name we're connecting to.  */
+  if (print)
     {
-      char *pretty_addr = pretty_print_address (addr);
-      if (connection_host_name
-         && 0 != strcmp (connection_host_name, pretty_addr))
-       logprintf (LOG_VERBOSE, _("Connecting to %s[%s]:%hu... "),
-                  connection_host_name, pretty_addr, port);
+      const char *txt_addr = pretty_print_address (ip);
+      if (print && 0 != strcmp (print, txt_addr))
+       logprintf (LOG_VERBOSE,
+                  _("Connecting to %s{%s}:%d... "), print, txt_addr, port);
       else
-       logprintf (LOG_VERBOSE, _("Connecting to %s:%hu... "),
-                  pretty_addr, port);
+       logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port);
     }
 
-  /* Make an internet socket, stream type.  */
-  sock = socket (ip_default_family, SOCK_STREAM, 0);
+  /* Store the sockaddr info to SA.  */
+  sockaddr_set_data (sa, ip, port);
+
+  /* Create the socket of the family appropriate for the address.  */
+  sock = socket (sa->sa_family, SOCK_STREAM, 0);
   if (sock < 0)
     goto out;
 
-  resolve_bind_address ();
-  if (bind_address_resolved)
+  /* For very small rate limits, set the buffer size (and hence,
+     hopefully, the kernel's TCP window size) to the per-second limit.
+     That way we should never have to sleep for more than 1s between
+     network reads.  */
+  if (opt.limit_rate && opt.limit_rate < 8192)
+    {
+      int bufsize = opt.limit_rate;
+      if (bufsize < 512)
+       bufsize = 512;          /* avoid pathologically small values */
+#ifdef SO_RCVBUF
+      setsockopt (sock, SOL_SOCKET, SO_RCVBUF,
+                 (void *)&bufsize, (socklen_t)sizeof (bufsize));
+#endif
+      /* When we add limit_rate support for writing, which is useful
+        for POST, we should also set SO_SNDBUF here.  */
+    }
+
+  if (opt.bind_address)
     {
-      /* Bind the client side to the requested address. */
-      wget_sockaddr bsa;
-      wget_sockaddr_set_address (&bsa, ip_default_family, 0, &bind_address);
-      if (bind (sock, &bsa.sa, sockaddr_len ()))
+      /* Bind the client side of the socket to the requested
+        address.  */
+      struct sockaddr_storage bind_ss;
+      struct sockaddr *bind_sa = (struct sockaddr *)&bind_ss;
+      if (resolve_bind_address (opt.bind_address, bind_sa, 0))
        {
-         close (sock);
-         sock = -1;
-         goto out;
+          if (bind (sock, bind_sa, sockaddr_size (bind_sa)) < 0)
+           {
+             CLOSE (sock);
+             sock = -1;
+             goto out;
+           }
        }
     }
 
-  /* Connect the socket to the remote host.  */
-  if (connect_with_timeout (sock, &sa.sa, sockaddr_len (), opt.timeout) < 0)
+  /* Connect the socket to the remote endpoint.  */
+  if (connect_with_timeout (sock, sa, sockaddr_size (sa),
+                           opt.connect_timeout) < 0)
     {
-      close (sock);
+      CLOSE (sock);
       sock = -1;
       goto out;
     }
@@ -202,14 +302,14 @@ connect_to_one (ip_address *addr, unsigned short port, int silent)
   if (sock >= 0)
     {
       /* Success. */
-      if (!silent)
+      if (print)
        logprintf (LOG_VERBOSE, _("connected.\n"));
       DEBUGP (("Created socket %d.\n", sock));
     }
   else
     {
       save_errno = errno;
-      if (!silent)
+      if (print)
        logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno));
       errno = save_errno;
     }
@@ -217,31 +317,47 @@ connect_to_one (ip_address *addr, unsigned short port, int silent)
   return sock;
 }
 
-/* Connect to a remote host whose address has been resolved. */
+/* Connect to a remote endpoint specified by host name.  */
+
 int
-connect_to_many (struct address_list *al, unsigned short port, int silent)
+connect_to_host (const char *host, int port)
 {
   int i, start, end;
+  struct address_list *al;
+  int sock = -1;
+
+ again:
+  al = lookup_host (host, 0);
+  if (!al)
+    return E_HOST;
 
   address_list_get_bounds (al, &start, &end);
   for (i = start; i < end; i++)
     {
-      ip_address addr;
-      int sock;
-      address_list_copy_one (al, i, &addr);
-
-      sock = connect_to_one (&addr, port, silent);
+      const ip_address *ip = address_list_address_at (al, i);
+      sock = connect_to_ip (ip, port, host);
       if (sock >= 0)
        /* Success. */
-       return sock;
+       break;
 
       address_list_set_faulty (al, i);
 
       /* The attempt to connect has failed.  Continue with the loop
         and try next address. */
     }
+  address_list_release (al);
 
-  return -1;
+  if (sock < 0 && address_list_cached_p (al))
+    {
+      /* We were unable to connect to any address in a list we've
+        obtained from cache.  There is a possibility that the host is
+        under dynamic DNS and has changed its address.  Resolve it
+        again.  */
+      forget_host_lookup (host);
+      goto again;
+    }
+
+  return sock;
 }
 
 int
@@ -275,58 +391,75 @@ test_socket_open (int sock)
 #endif
 }
 
-/* Bind the local port PORT.  This does all the necessary work, which
-   is creating a socket, setting SO_REUSEADDR option on it, then
-   calling bind() and listen().  If *PORT is 0, a random port is
-   chosen by the system, and its value is stored to *PORT.  The
-   internal variable MPORT is set to the value of the ensuing master
-   socket.  Call acceptport() to block for and accept a connection.  */
+/* Create a socket and bind it to PORT locally.  Calling accept() on
+   such a socket waits for and accepts incoming TCP connections.  The
+   resulting socket is stored to LOCAL_SOCK.  */
+
 uerr_t
-bindport (unsigned short *port, int family)
+bindport (const ip_address *bind_address, int *port, int *local_sock)
 {
-  int optval = 1;
-  wget_sockaddr srv;
-  memset (&srv, 0, sizeof (wget_sockaddr));
+  int msock;
+  int family = AF_INET;
+  int optval;
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
+  xzero (ss);
 
-  msock = -1;
+#ifdef ENABLE_IPV6
+  if (bind_address->type == IPV6_ADDRESS) 
+    family = AF_INET6;
+#endif
 
   if ((msock = socket (family, SOCK_STREAM, 0)) < 0)
     return CONSOCKERR;
+
+#ifdef SO_REUSEADDR
+  optval = 1;
   if (setsockopt (msock, SOL_SOCKET, SO_REUSEADDR,
-                 (char *)&optval, sizeof (optval)) < 0)
-    return CONSOCKERR;
+                 (void *)&optval, (socklen_t)sizeof (optval)) < 0)
+    {
+      CLOSE (msock);
+      return CONSOCKERR;
+    }
+#endif
 
-  resolve_bind_address ();
-  wget_sockaddr_set_address (&srv, ip_default_family, htons (*port),
-                            bind_address_resolved ? &bind_address : NULL);
-  if (bind (msock, &srv.sa, sockaddr_len ()) < 0)
+#ifdef ENABLE_IPV6
+# ifdef HAVE_IPV6_V6ONLY
+  if (family == AF_INET6)
+    {
+      optval = 1;
+      /* if setsockopt fails, go on anyway */
+      setsockopt (msock, IPPROTO_IPV6, IPV6_V6ONLY,
+                  (void *)&optval, (socklen_t)sizeof (optval));
+    }
+# endif
+#endif
+
+  sockaddr_set_data (sa, bind_address, *port);
+  if (bind (msock, sa, sockaddr_size (sa)) < 0)
     {
       CLOSE (msock);
-      msock = -1;
       return BINDERR;
     }
-  DEBUGP (("Master socket fd %d bound.\n", msock));
+  DEBUGP (("Local socket fd %d bound.\n", msock));
   if (!*port)
     {
-      /* #### addrlen should be a 32-bit type, which int is not
-         guaranteed to be.  Oh, and don't try to make it a size_t,
-         because that can be 64-bit.  */
-      int sa_len = sockaddr_len ();
-      if (getsockname (msock, &srv.sa, &sa_len) < 0)
+      socklen_t sa_len = sockaddr_size (sa);
+      if (getsockname (msock, sa, &sa_len) < 0)
        {
          CLOSE (msock);
-         msock = -1;
          return CONPORTERR;
        }
-      *port = wget_sockaddr_get_port (&srv);
-      DEBUGP (("using port %i.\n", *port));
+      sockaddr_get_data (sa, NULL, port);
+      DEBUGP (("binding to address %s using port %i.\n", 
+              pretty_print_address (bind_address), *port));
     }
   if (listen (msock, 1) < 0)
     {
       CLOSE (msock);
-      msock = -1;
       return LISTENERR;
     }
+  *local_sock = msock;
   return BINDOK;
 }
 
@@ -338,7 +471,7 @@ bindport (unsigned short *port, int family)
    Returns 1 if FD is available, 0 for timeout and -1 for error.  */
 
 int
-select_fd (int fd, int maxtime, int writep)
+select_fd (int fd, double maxtime, int writep)
 {
   fd_set fds;
   fd_set *rd = NULL, *wrt = NULL;
@@ -349,8 +482,8 @@ select_fd (int fd, int maxtime, int writep)
   FD_SET (fd, &fds);
   *(writep ? &wrt : &rd) = &fds;
 
-  tmout.tv_sec = maxtime;
-  tmout.tv_usec = 0;
+  tmout.tv_sec = (long)maxtime;
+  tmout.tv_usec = 1000000L * (maxtime - (long)maxtime);
 
   do
     result = select (fd + 1, rd, wrt, NULL, &tmout);
@@ -365,72 +498,74 @@ select_fd (int fd, int maxtime, int writep)
 }
 #endif /* HAVE_SELECT */
 
-/* Call accept() on MSOCK and store the result to *SOCK.  This assumes
-   that bindport() has been used to initialize MSOCK to a correct
-   value.  It blocks the caller until a connection is established.  If
-   no connection is established for OPT.TIMEOUT seconds, the function
-   exits with an error status.  */
+/* Accept a connection on LOCAL_SOCK, and store the new socket to
+   *SOCK.  It blocks the caller until a connection is established.  If
+   no connection is established for opt.connect_timeout seconds, the
+   function exits with an error status.  */
+
 uerr_t
-acceptport (int *sock)
+acceptport (int local_sock, int *sock)
 {
-  int addrlen = sockaddr_len ();
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
+  socklen_t addrlen = sizeof (ss);
 
 #ifdef HAVE_SELECT
-  if (select_fd (msock, opt.timeout, 0) <= 0)
+  if (select_fd (local_sock, opt.connect_timeout, 0) <= 0)
     return ACCEPTERR;
 #endif
-  if ((*sock = accept (msock, addr, &addrlen)) < 0)
+  if ((*sock = accept (local_sock, sa, &addrlen)) < 0)
     return ACCEPTERR;
   DEBUGP (("Created socket fd %d.\n", *sock));
   return ACCEPTOK;
 }
 
-/* Close SOCK, as well as the most recently remembered MSOCK, created
-   via bindport().  If SOCK is -1, close MSOCK only.  */
-void
-closeport (int sock)
-{
-  /*shutdown (sock, 2);*/
-  if (sock != -1)
-    CLOSE (sock);
-  if (msock != -1)
-    CLOSE (msock);
-  msock = -1;
-}
-
 /* Return the local IP address associated with the connection on FD.  */
 
 int
 conaddr (int fd, ip_address *ip)
 {
-  wget_sockaddr mysrv;
+  struct sockaddr_storage storage;
+  struct sockaddr *sockaddr = (struct sockaddr *)&storage;
+  socklen_t addrlen = sizeof (storage);
 
-  /* see bindport() for discussion of using `int' here. */
-  int addrlen = sizeof (mysrv);        
-
-  if (getsockname (fd, &mysrv.sa, (int *)&addrlen) < 0)
+  if (getsockname (fd, sockaddr, &addrlen) < 0)
     return 0;
 
-  switch (mysrv.sa.sa_family)
+  switch (sockaddr->sa_family)
     {
 #ifdef ENABLE_IPV6
     case AF_INET6:
-      memcpy (ip, &mysrv.sin6.sin6_addr, 16);
-      return 1;
+      {
+       struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&storage;
+       ip->type = IPV6_ADDRESS;
+       ADDRESS_IPV6_IN6_ADDR (ip) = sa6->sin6_addr;
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+       ADDRESS_IPV6_SCOPE (ip) = sa6->sin6_scope_id;
+#endif
+       DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
+       return 1;
+      }
 #endif
     case AF_INET:
-      map_ipv4_to_ip ((ip4_address *)&mysrv.sin.sin_addr, ip);
-      return 1;
+      {
+       struct sockaddr_in *sa = (struct sockaddr_in *)&storage;
+       ip->type = IPV4_ADDRESS;
+       ADDRESS_IPV4_IN_ADDR (ip) = sa->sin_addr;
+       DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
+       return 1;
+      }
     default:
       abort ();
     }
+
   return 0;
 }
 
 /* Read at most LEN bytes from FD, storing them to BUF.  This is
    virtually the same as read(), but takes care of EINTR braindamage
    and uses select() to timeout the stale connections (a connection is
-   stale if more than OPT.TIMEOUT time is spent in select() or
+   stale if more than OPT.READ_TIMEOUT time is spent in select() or
    read()).  */
 
 int
@@ -439,8 +574,8 @@ iread (int fd, char *buf, int len)
   int res;
 
 #ifdef HAVE_SELECT
-  if (opt.timeout)
-    if (select_fd (fd, opt.timeout, 0) <= 0)
+  if (opt.read_timeout)
+    if (select_fd (fd, opt.read_timeout, 0) <= 0)
       return -1;
 #endif
   do
@@ -467,8 +602,8 @@ iwrite (int fd, char *buf, int len)
   while (len > 0)
     {
 #ifdef HAVE_SELECT
-      if (opt.timeout)
-       if (select_fd (fd, opt.timeout, 1) <= 0)
+      if (opt.read_timeout)
+       if (select_fd (fd, opt.read_timeout, 1) <= 0)
          return -1;
 #endif
       do