]> 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 483d4c54cf73aadd6720daa4aa220a254aa84337..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,7 +208,7 @@ connect_with_timeout_callback (void *arg)
    ETIMEDOUT.  */
 
 static int
-connect_with_timeout (int fd, const struct sockaddr *addr, int addrlen,
+connect_with_timeout (int fd, const struct sockaddr *addr, socklen_t addrlen,
                      double timeout)
 {
   struct cwt_context ctx;
@@ -134,84 +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;
-
-void
-set_connection_host_name (const char *host)
-{
-  if (host)
-    assert (connection_host_name == NULL);
-  else
-    assert (connection_host_name != NULL);
+/* Connect to a remote endpoint whose IP address is known.  */
 
-  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;
 
   /* For very small rate limits, set the buffer size (and hence,
-     hopefully, the size of the kernel window) to the size of the
-     limit.  That way we don't sleep for more than 1s between network
-     reads.  */
+     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;
+       bufsize = 512;          /* avoid pathologically small values */
 #ifdef SO_RCVBUF
       setsockopt (sock, SOL_SOCKET, SO_RCVBUF,
-                 (char *)&bufsize, sizeof (bufsize));
+                 (void *)&bufsize, (socklen_t)sizeof (bufsize));
 #endif
-      /* When we add opt.limit_rate support for writing, as with
-        `--post-file', also set SO_SNDBUF here.  */
+      /* When we add limit_rate support for writing, which is useful
+        for POST, we should also set SO_SNDBUF here.  */
     }
 
-  resolve_bind_address ();
-  if (bind_address_resolved)
+  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 (),
+  /* 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;
     }
@@ -220,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;
     }
@@ -235,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
@@ -293,61 +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
+
+#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
 
-  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)
+  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;
 }
 
@@ -386,65 +498,67 @@ select_fd (int fd, double 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.CONNECT_TIMEOUT seconds, the
+/* 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.connect_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;
 }