X-Git-Url: http://sjero.net/git/?a=blobdiff_plain;f=src%2Fconnect.c;h=eeb4eb288f9a9d7b5782d8a93b14c28a0486d445;hb=d9fea91a0a319e348adb504bd3edff148ff3d8a0;hp=90729aec262970d41724bcc45683d01c66148d57;hpb=02f90322e8fae14dedd7865394bbd3e499ef506d;p=wget diff --git a/src/connect.c b/src/connect.c index 90729aec..eeb4eb28 100644 --- a/src/connect.c +++ b/src/connect.c @@ -182,7 +182,7 @@ resolve_bind_address (struct sockaddr *sa) } called = 1; - al = lookup_host_passive (opt.bind_address); + al = lookup_host (opt.bind_address, LH_BIND | LH_SILENT); if (!al) { /* #### We should be able to print the error message here. */ @@ -241,7 +241,10 @@ connect_with_timeout (int fd, const struct sockaddr *addr, socklen_t addrlen, return ctx.result; } -/* Connect to a remote endpoint whose IP address is known. */ +/* Connect via TCP to the specified address and port. + + If PRINT is non-NULL, it is the host name to print that we're + connecting to. */ int connect_to_ip (const ip_address *ip, int port, const char *print) @@ -318,7 +321,7 @@ connect_to_ip (const ip_address *ip, int port, const char *print) logprintf. */ int save_errno = errno; if (sock >= 0) - xclose (sock); + fd_close (sock); if (print) logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno)); errno = save_errno; @@ -326,17 +329,21 @@ connect_to_ip (const ip_address *ip, int port, const char *print) } } -/* Connect to a remote endpoint specified by host name. */ +/* Connect via TCP to a remote host on the specified port. + + HOST is resolved as an Internet host name. If HOST resolves to + more than one IP address, they are tried in the order returned by + DNS until connecting to one of them succeeds. */ int connect_to_host (const char *host, int port) { int i, start, end; - struct address_list *al; - int sock = -1; + int sock; + + struct address_list *al = lookup_host (host, 0); - again: - al = lookup_host (host, 0); + retry: if (!al) return E_HOST; @@ -346,70 +353,49 @@ connect_to_host (const char *host, int port) const ip_address *ip = address_list_address_at (al, i); sock = connect_to_ip (ip, port, host); if (sock >= 0) - /* Success. */ - break; - - address_list_set_faulty (al, i); + { + /* Success. */ + address_list_set_connected (al); + address_list_release (al); + return sock; + } /* The attempt to connect has failed. Continue with the loop and try next address. */ + + address_list_set_faulty (al, i); } - address_list_release (al); - if (sock >= 0) - /* Mark a successful connection to one of the addresses. */ - address_list_set_connected (al); + /* Failed to connect to any of the addresses in AL. */ - if (sock < 0 && address_list_connected_p (al)) + if (address_list_connected_p (al)) { - /* We are unable to connect to any of HOST's addresses, although - we were previously able to connect to HOST. That might - indicate that HOST is under dynamic DNS and the addresses - we're connecting to have expired. Resolve it again. */ - forget_host_lookup (host); - goto again; + /* We connected to AL before, but cannot do so now. That might + indicate that our DNS cache entry for HOST has expired. */ + address_list_release (al); + al = lookup_host (host, LH_REFRESH); + goto retry; } + address_list_release (al); - return sock; + return -1; } + +/* Create a socket, bind it to local interface BIND_ADDRESS on port + *PORT, set up a listen backlog, and return the resulting socket, or + -1 in case of error. -int -test_socket_open (int sock) -{ -#ifdef HAVE_SELECT - fd_set check_set; - struct timeval to; - - /* Check if we still have a valid (non-EOF) connection. From Andrew - * Maholski's code in the Unix Socket FAQ. */ - - FD_ZERO (&check_set); - FD_SET (sock, &check_set); - - /* Wait one microsecond */ - to.tv_sec = 0; - to.tv_usec = 1; - - /* If we get a timeout, then that means still connected */ - if (select (sock + 1, &check_set, NULL, NULL, &to) == 0) - { - /* Connection is valid (not EOF), so continue */ - return 1; - } - else - return 0; -#else - /* Without select, it's hard to know for sure. */ - return 1; -#endif -} + BIND_ADDRESS is the address of the interface to bind to. If it is + NULL, the socket is bound to the default address. PORT should + point to the port number that will be used for the binding. If + that number is 0, the system will choose a suitable port, and the + chosen value will be written to *PORT. -/* 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. */ + Calling accept() on such a socket waits for and accepts incoming + TCP connections. */ -uerr_t -bindport (const ip_address *bind_address, int *port, int *local_sock) +int +bind_local (const ip_address *bind_address, int *port) { int sock; int family = AF_INET; @@ -426,8 +412,9 @@ bindport (const ip_address *bind_address, int *port, int *local_sock) family = AF_INET6; #endif - if ((sock = socket (family, SOCK_STREAM, 0)) < 0) - return CONSOCKERR; + sock = socket (family, SOCK_STREAM, 0); + if (sock < 0) + return -1; #ifdef SO_REUSEADDR setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, setopt_ptr, setopt_size); @@ -444,17 +431,23 @@ bindport (const ip_address *bind_address, int *port, int *local_sock) sockaddr_set_data (sa, bind_address, *port); if (bind (sock, sa, sockaddr_size (sa)) < 0) { - xclose (sock); - return BINDERR; + fd_close (sock); + return -1; } DEBUGP (("Local socket fd %d bound.\n", sock)); - if (!*port) + + /* If *PORT is 0, find out which port we've bound to. */ + if (*port == 0) { socklen_t sa_len = sockaddr_size (sa); if (getsockname (sock, sa, &sa_len) < 0) { - xclose (sock); - return CONPORTERR; + /* If we can't find out the socket's local address ("name"), + something is seriously wrong with the socket, and it's + unusable for us anyway because we must know the chosen + port. */ + fd_close (sock); + return -1; } sockaddr_get_data (sa, NULL, port); DEBUGP (("binding to address %s using port %i.\n", @@ -462,69 +455,44 @@ bindport (const ip_address *bind_address, int *port, int *local_sock) } if (listen (sock, 1) < 0) { - xclose (sock); - return LISTENERR; + fd_close (sock); + return -1; } - *local_sock = sock; - return BINDOK; + return sock; } -#ifdef HAVE_SELECT -/* Wait for file descriptor FD to be readable or writable or both, - timing out after MAXTIME seconds. Returns 1 if FD is available, 0 - for timeout and -1 for error. The argument WAIT_FOR can be a - combination of WAIT_READ and WAIT_WRITE. - - This is a mere convenience wrapper around the select call, and - should be taken as such. */ - -int -select_fd (int fd, double maxtime, int wait_for) -{ - fd_set fdset; - fd_set *rd = NULL, *wr = NULL; - struct timeval tmout; - int result; - - FD_ZERO (&fdset); - FD_SET (fd, &fdset); - if (wait_for & WAIT_FOR_READ) - rd = &fdset; - if (wait_for & WAIT_FOR_WRITE) - wr = &fdset; - - tmout.tv_sec = (long) maxtime; - tmout.tv_usec = 1000000L * (maxtime - (long) maxtime); - - do - result = select (fd + 1, rd, wr, NULL, &tmout); - while (result < 0 && errno == EINTR); +/* Like a call to accept(), but with the added check for timeout. - return result; -} -#endif /* HAVE_SELECT */ + In other words, accept a client connection on LOCAL_SOCK, and + return the new socket used for communication with the client. + LOCAL_SOCK should have been bound, e.g. using bind_local(). -/* 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 + The caller is blocked 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 local_sock, int *sock) +int +accept_connection (int local_sock) { + int sock; + + /* We don't need the values provided by accept, but accept + apparently requires them to be present. */ struct sockaddr_storage ss; struct sockaddr *sa = (struct sockaddr *)&ss; socklen_t addrlen = sizeof (ss); -#ifdef HAVE_SELECT if (opt.connect_timeout) - if (select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ) <= 0) - return ACCEPTERR; -#endif - if ((*sock = accept (local_sock, sa, &addrlen)) < 0) - return ACCEPTERR; - DEBUGP (("Created socket fd %d.\n", *sock)); - return ACCEPTOK; + { + int test = select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ); + if (test == 0) + errno = ETIMEDOUT; + if (test <= 0) + return -1; + } + sock = accept (local_sock, sa, &addrlen); + DEBUGP (("Accepted client at socket %d.\n", sock)); + return sock; } /* Get the IP address associated with the connection on FD and store @@ -580,6 +548,153 @@ socket_ip_address (int sock, ip_address *ip, int endpoint) return 0; } + +/* Return non-zero if the error from the connect code can be + considered retryable. Wget normally retries after errors, but the + exception are the "unsupported protocol" type errors (possible on + IPv4/IPv6 dual family systems) and "connection refused". */ + +int +retryable_socket_connect_error (int err) +{ + /* Have to guard against some of these values not being defined. + Cannot use a switch statement because some of the values might be + equal. */ + if (0 +#ifdef EAFNOSUPPORT + || err == EAFNOSUPPORT +#endif +#ifdef EPFNOSUPPORT + || err == EPFNOSUPPORT +#endif +#ifdef ESOCKTNOSUPPORT /* no, "sockt" is not a typo! */ + || err == ESOCKTNOSUPPORT +#endif +#ifdef EPROTONOSUPPORT + || err == EPROTONOSUPPORT +#endif +#ifdef ENOPROTOOPT + || err == ENOPROTOOPT +#endif + /* Apparently, older versions of Linux and BSD used EINVAL + instead of EAFNOSUPPORT and such. */ + || err == EINVAL + ) + return 0; + + if (err == ECONNREFUSED && !opt.retry_connrefused) + return 0; + + return 1; +} + +#ifdef ENABLE_IPV6 +# ifndef HAVE_GETADDRINFO_AI_ADDRCONFIG + +/* Return non-zero if the INET6 socket family is supported on the + system. + + This doesn't guarantee that we're able to connect to IPv6 hosts, + but it's better than nothing. It is only used on systems where + getaddrinfo doesn't support AI_ADDRCONFIG. (See lookup_host.) */ + +int +socket_has_inet6 (void) +{ + static int supported = -1; + if (supported == -1) + { + int sock = socket (AF_INET6, SOCK_STREAM, 0); + if (sock < 0) + supported = 0; + else + { + fd_close (sock); + supported = 1; + } + } + return supported; +} + +# endif/* not HAVE_GETADDRINFO_AI_ADDRCONFIG */ +#endif /* ENABLE_IPV6 */ + +/* Wait for a single descriptor to become available, timing out after + MAXTIME seconds. Returns 1 if FD is available, 0 for timeout and + -1 for error. The argument WAIT_FOR can be a combination of + WAIT_FOR_READ and WAIT_FOR_WRITE. + + This is a mere convenience wrapper around the select call, and + should be taken as such (for example, it doesn't implement Wget's + 0-timeout-means-no-timeout semantics.) */ + +int +select_fd (int fd, double maxtime, int wait_for) +{ +#ifdef HAVE_SELECT + fd_set fdset; + fd_set *rd = NULL, *wr = NULL; + struct timeval tmout; + int result; + + FD_ZERO (&fdset); + FD_SET (fd, &fdset); + if (wait_for & WAIT_FOR_READ) + rd = &fdset; + if (wait_for & WAIT_FOR_WRITE) + wr = &fdset; + + tmout.tv_sec = (long) maxtime; + tmout.tv_usec = 1000000L * (maxtime - (long) maxtime); + + do + result = select (fd + 1, rd, wr, NULL, &tmout); + while (result < 0 && errno == EINTR); + + return result; + +#else /* not HAVE_SELECT */ + + /* If select() unavailable, just return 1. In most usages in Wget, + this is the appropriate response -- "if we can't poll, go ahead + with the blocking operation". If a specific part of code needs + different behavior, it can use #ifdef HAVE_SELECT to test whether + polling really occurs. */ + return 1; + +#endif /* not HAVE_SELECT */ +} + +int +test_socket_open (int sock) +{ +#ifdef HAVE_SELECT + fd_set check_set; + struct timeval to; + + /* Check if we still have a valid (non-EOF) connection. From Andrew + * Maholski's code in the Unix Socket FAQ. */ + + FD_ZERO (&check_set); + FD_SET (sock, &check_set); + + /* Wait one microsecond */ + to.tv_sec = 0; + to.tv_usec = 1; + + /* If we get a timeout, then that means still connected */ + if (select (sock + 1, &check_set, NULL, NULL, &to) == 0) + { + /* Connection is valid (not EOF), so continue */ + return 1; + } + else + return 0; +#else + /* Without select, it's hard to know for sure. */ + return 1; +#endif +} /* Basic socket operations, mostly EINTR wrappers. */ @@ -607,7 +722,7 @@ sock_read (int fd, char *buf, int bufsize) static int sock_write (int fd, char *buf, int bufsize) { - int res = 0; + int res; do res = write (fd, buf, bufsize); while (res == -1 && errno == EINTR); @@ -617,11 +732,17 @@ sock_write (int fd, char *buf, int bufsize) static int sock_poll (int fd, double timeout, int wait_for) { -#ifdef HAVE_SELECT return select_fd (fd, timeout, wait_for); -#else - return 1; -#endif +} + +static int +sock_peek (int fd, char *buf, int bufsize) +{ + int res; + do + res = recv (fd, buf, bufsize, MSG_PEEK); + while (res == -1 && errno == EINTR); + return res; } static void @@ -639,133 +760,151 @@ sock_close (int fd) that are not mere file descriptors under the hood, such as SSL sockets. - That way the user code can call xread(fd, ...) and we'll run read + That way the user code can call fd_read(fd, ...) and we'll run read or SSL_read or whatever is necessary. */ -static struct hash_table *extended_map; -static int extended_map_modified_tick; +static struct hash_table *transport_map; +static int transport_map_modified_tick; -struct extended_info { - xreader_t reader; - xwriter_t writer; - xpoller_t poller; - xcloser_t closer; +struct transport_info { + fd_reader_t reader; + fd_writer_t writer; + fd_poller_t poller; + fd_peeker_t peeker; + fd_closer_t closer; void *ctx; }; -/* Register the handlers for operations on FD. This is meant - primarily for transport layers like SSL that piggyback on sockets, - but with their own readers, writers, etc. */ +/* Register the transport layer operations that will be used when + reading, writing, and polling FD. + + This should be used for transport layers like SSL that piggyback on + sockets. FD should otherwise be a real socket, on which you can + call getpeername, etc. */ void -register_extended (int fd, xreader_t reader, xwriter_t writer, - xpoller_t poller, xcloser_t closer, void *ctx) +fd_register_transport (int fd, fd_reader_t reader, fd_writer_t writer, + fd_poller_t poller, fd_peeker_t peeker, + fd_closer_t closer, void *ctx) { - struct extended_info *info; + struct transport_info *info; /* The file descriptor must be non-negative to be registered. - Negative values are ignored by xclose(), and -1 cannot be used as + Negative values are ignored by fd_close(), and -1 cannot be used as hash key. */ assert (fd >= 0); - info = xnew (struct extended_info); + info = xnew (struct transport_info); info->reader = reader; info->writer = writer; info->poller = poller; + info->peeker = peeker; info->closer = closer; info->ctx = ctx; - if (!extended_map) - extended_map = hash_table_new (0, NULL, NULL); - hash_table_put (extended_map, (void *) fd, info); - ++extended_map_modified_tick; + if (!transport_map) + transport_map = hash_table_new (0, NULL, NULL); + hash_table_put (transport_map, (void *) fd, info); + ++transport_map_modified_tick; } -/* When xread/xwrite are called multiple times in a loop, they should +/* When fd_read/fd_write are called multiple times in a loop, they should remember the INFO pointer instead of fetching it every time. It is not enough to compare FD to LAST_FD because FD might have been closed and reopened. modified_tick ensures that changes to - extended_map will not be unnoticed. + transport_map will not be unnoticed. This is a macro because we want the static storage variables to be per-function. */ #define LAZY_RETRIEVE_INFO(info) do { \ - static struct extended_info *last_info; \ + static struct transport_info *last_info; \ static int last_fd = -1, last_tick; \ - if (!extended_map) \ + if (!transport_map) \ info = NULL; \ - else if (last_fd == fd && last_tick == extended_map_modified_tick) \ + else if (last_fd == fd && last_tick == transport_map_modified_tick) \ info = last_info; \ else \ { \ - info = hash_table_get (extended_map, (void *) fd); \ + info = hash_table_get (transport_map, (void *) fd); \ last_fd = fd; \ last_info = info; \ - last_tick = extended_map_modified_tick; \ + last_tick = transport_map_modified_tick; \ } \ } while (0) -/* Read no more than BUFSIZE bytes of data from FD, storing them to - BUF. If TIMEOUT is non-zero, the operation aborts if no data is - received after that many seconds. If TIMEOUT is -1, the value of - opt.timeout is used for TIMEOUT. */ - -int -xread (int fd, char *buf, int bufsize, double timeout) +static int +poll_internal (int fd, struct transport_info *info, int wf, double timeout) { - struct extended_info *info; - LAZY_RETRIEVE_INFO (info); if (timeout == -1) timeout = opt.read_timeout; if (timeout) { int test; if (info && info->poller) - test = info->poller (fd, timeout, WAIT_FOR_READ, info->ctx); + test = info->poller (fd, timeout, wf, info->ctx); else - test = sock_poll (fd, timeout, WAIT_FOR_READ); + test = sock_poll (fd, timeout, wf); if (test == 0) errno = ETIMEDOUT; if (test <= 0) - return -1; + return 0; } + return 1; +} + +/* Read no more than BUFSIZE bytes of data from FD, storing them to + BUF. If TIMEOUT is non-zero, the operation aborts if no data is + received after that many seconds. If TIMEOUT is -1, the value of + opt.timeout is used for TIMEOUT. */ + +int +fd_read (int fd, char *buf, int bufsize, double timeout) +{ + struct transport_info *info; + LAZY_RETRIEVE_INFO (info); + if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) + return -1; if (info && info->reader) return info->reader (fd, buf, bufsize, info->ctx); else return sock_read (fd, buf, bufsize); } +/* The same as xread, but don't actually read the data, just copy it + instead. */ + +int +fd_peek (int fd, char *buf, int bufsize, double timeout) +{ + struct transport_info *info; + LAZY_RETRIEVE_INFO (info); + if (!poll_internal (fd, info, WAIT_FOR_READ, timeout)) + return -1; + if (info && info->peeker) + return info->peeker (fd, buf, bufsize, info->ctx); + else + return sock_peek (fd, buf, bufsize); +} + /* Write the entire contents of BUF to FD. If TIMEOUT is non-zero, the operation aborts if no data is received after that many seconds. If TIMEOUT is -1, the value of opt.timeout is used for TIMEOUT. */ int -xwrite (int fd, char *buf, int bufsize, double timeout) +fd_write (int fd, char *buf, int bufsize, double timeout) { int res; - struct extended_info *info; + struct transport_info *info; LAZY_RETRIEVE_INFO (info); - if (timeout == -1) - timeout = opt.read_timeout; /* `write' may write less than LEN bytes, thus the loop keeps trying it until all was written, or an error occurred. */ res = 0; while (bufsize > 0) { - if (timeout) - { - int test; - if (info && info->poller) - test = info->poller (fd, timeout, WAIT_FOR_WRITE, info->ctx); - else - test = sock_poll (fd, timeout, WAIT_FOR_WRITE); - if (test == 0) - errno = ETIMEDOUT; - if (test <= 0) - return -1; - } + if (!poll_internal (fd, info, WAIT_FOR_WRITE, timeout)) + return -1; if (info && info->writer) res = info->writer (fd, buf, bufsize, info->ctx); else @@ -781,17 +920,17 @@ xwrite (int fd, char *buf, int bufsize, double timeout) /* Close the file descriptor FD. */ void -xclose (int fd) +fd_close (int fd) { - struct extended_info *info; + struct transport_info *info; if (fd < 0) return; - /* We don't need to be extra-fast here, so save some code by - avoiding LAZY_RETRIEVE_INFO. */ + /* Don't use LAZY_RETRIEVE_INFO because fd_close() is only called once + per socket, so that particular optimization wouldn't work. */ info = NULL; - if (extended_map) - info = hash_table_get (extended_map, (void *) fd); + if (transport_map) + info = hash_table_get (transport_map, (void *) fd); if (info && info->closer) info->closer (fd, info->ctx); @@ -800,8 +939,8 @@ xclose (int fd) if (info) { - hash_table_remove (extended_map, (void *) fd); + hash_table_remove (transport_map, (void *) fd); xfree (info); - ++extended_map_modified_tick; + ++transport_map_modified_tick; } }