+2003-11-08 Hrvoje Niksic <hniksic@xemacs.org>
+
+ * http.c (persistent_available_p): Instead of matching all the
+ addresses of HOST and last host, determine the peer's IP address
+ with socket_ip_address and see if that address is one of those
+ HOST resolves to.
+
+ * host.c (address_list_match_all): Removed.
+ (address_list_find): New function, finds an IP address in the
+ address list.
+
+ * ftp.c (ftp_do_pasv): Get the peer's address here, and pass it to
+ ftp_epsv so it doesn't need to call getpeername.
+
+ * ftp-basic.c (ftp_port): Use socket_ip_address instead of
+ getpeername.
+ (ftp_lprt): Ditto.
+
+ * connect.c (socket_ip_address): Replaces conaddr, generalized to
+ either get peer's or local address.
+ (sockaddr_get_data): Made local to this file.
+
2003-11-08 Hrvoje Niksic <hniksic@xemacs.org>
* hash.c (HASH_POSITION): Explicitly accept the hash function.
you're not interested in one or the other information, pass NULL as
the pointer. */
-void
+static void
sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port)
{
switch (sa->sa_family)
return ACCEPTOK;
}
-/* Return the local IP address associated with the connection on FD. */
+/* Get the IP address associated with the connection on FD and store
+ it to IP. Return 1 on success, 0 otherwise.
+
+ If ENDPOINT is ENDPOINT_LOCAL, it returns the address of the local
+ (client) side of the socket. Else if ENDPOINT is ENDPOINT_PEER, it
+ returns the address of the remote (peer's) side of the socket. */
int
-conaddr (int fd, ip_address *ip)
+socket_ip_address (int sock, ip_address *ip, int endpoint)
{
struct sockaddr_storage storage;
struct sockaddr *sockaddr = (struct sockaddr *)&storage;
socklen_t addrlen = sizeof (storage);
+ int ret;
- if (getsockname (fd, sockaddr, &addrlen) < 0)
+ if (endpoint == ENDPOINT_LOCAL)
+ ret = getsockname (sock, sockaddr, &addrlen);
+ else if (endpoint == ENDPOINT_PEER)
+ ret = getpeername (sock, sockaddr, &addrlen);
+ else
+ abort ();
+ if (ret < 0)
return 0;
switch (sockaddr->sa_family)
int connect_to_host PARAMS ((const char *, int));
int connect_to_ip PARAMS ((const ip_address *, int, const char *));
-void sockaddr_get_data PARAMS ((const struct sockaddr *, ip_address *, int *));
uerr_t bindport PARAMS ((const ip_address *, int *, int *));
uerr_t acceptport PARAMS ((int, int *));
-int conaddr PARAMS ((int, ip_address *));
+
+enum {
+ ENDPOINT_LOCAL,
+ ENDPOINT_PEER
+};
+int socket_ip_address PARAMS ((int, ip_address *, int));
/* Flags for select_fd's WAIT_FOR argument. */
enum {
assert (rbuf_initialized_p (rbuf));
/* Get the address of this side of the connection. */
- if (!conaddr (RBUF_FD (rbuf), &addr))
+ if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
return BINDERR;
assert (addr.type == IPV4_ADDRESS);
assert (rbuf_initialized_p (rbuf));
/* Get the address of this side of the connection. */
- if (!conaddr (RBUF_FD (rbuf), &addr))
+ if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
return BINDERR;
assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
assert (rbuf_initialized_p(rbuf));
/* Get the address of this side of the connection. */
- if (!conaddr (RBUF_FD (rbuf), &addr))
+ if (!socket_ip_address (RBUF_FD (rbuf), &addr, ENDPOINT_LOCAL))
return BINDERR;
assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
int nwritten, i;
uerr_t err;
int tport;
- socklen_t addrlen;
- struct sockaddr_storage ss;
- struct sockaddr *sa = (struct sockaddr *)&ss;
assert (rbuf != NULL);
assert (rbuf_initialized_p(rbuf));
assert (ip != NULL);
assert (port != NULL);
- addrlen = sizeof (ss);
- if (getpeername (rbuf->fd, sa, &addrlen) < 0)
- /* Mauro Tortonesi: HOW DO WE HANDLE THIS ERROR? */
- return CONPORTERR;
-
- assert (sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
-
- sockaddr_get_data (sa, ip, NULL);
+ /* IP already contains the IP address of the control connection's
+ peer, so we don't need to call socket_ip_address here. */
/* Form the request. */
/* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
- request = ftp_request ("EPSV", (sa->sa_family == AF_INET ? "1" : "2"));
+ request = ftp_request ("EPSV", (ip->type == IPV4_ADDRESS ? "1" : "2"));
/* And send it. */
nwritten = xwrite (RBUF_FD (rbuf), request, strlen (request), -1);
}
#ifdef ENABLE_IPV6
-static int
-getfamily (int fd)
-{
- struct sockaddr_storage ss;
- struct sockaddr *sa = (struct sockaddr *)&ss;
- socklen_t len = sizeof (ss);
-
- assert (fd >= 0);
-
- if (getpeername (fd, sa, &len) < 0)
- /* Mauro Tortonesi: HOW DO WE HANDLE THIS ERROR? */
- abort ();
-
- return sa->sa_family;
-}
-
/*
* This function sets up a passive data connection with the FTP server.
* It is merely a wrapper around ftp_epsv, ftp_lpsv and ftp_pasv.
ftp_do_pasv (struct rbuf *rbuf, ip_address *addr, int *port)
{
uerr_t err;
- int family;
- family = getfamily (rbuf->fd);
- assert (family == AF_INET || family == AF_INET6);
+ /* We need to determine the address family and need to call
+ getpeername, so while we're at it, store the address to ADDR.
+ ftp_pasv and ftp_lpsv can simply override it. */
+ if (!socket_ip_address (RBUF_FD (rbuf), addr, ENDPOINT_PEER))
+ abort ();
/* If our control connection is over IPv6, then we first try EPSV and then
* LPSV if the former is not supported. If the control connection is over
* IPv4, we simply issue the good old PASV request. */
- if (family == AF_INET6)
+ switch (addr->type)
{
+ case IPV4_ADDRESS:
+ if (!opt.server_response)
+ logputs (LOG_VERBOSE, "==> PASV ... ");
+ err = ftp_pasv (rbuf, addr, port);
+ break;
+ case IPV6_ADDRESS:
if (!opt.server_response)
logputs (LOG_VERBOSE, "==> EPSV ... ");
err = ftp_epsv (rbuf, addr, port);
logputs (LOG_VERBOSE, "==> LPSV ... ");
err = ftp_lpsv (rbuf, addr, port);
}
- }
- else
- {
- if (!opt.server_response)
- logputs (LOG_VERBOSE, "==> PASV ... ");
- err = ftp_pasv (rbuf, addr, port);
+ break;
+ default:
+ abort ();
}
return err;
ftp_do_port (struct rbuf *rbuf, int *local_sock)
{
uerr_t err;
- int family;
+ ip_address cip;
assert (rbuf != NULL);
assert (rbuf_initialized_p (rbuf));
- family = getfamily (rbuf->fd);
- assert (family == AF_INET || family == AF_INET6);
+ if (!socket_ip_address (RBUF_FD (rbuf), &cip, ENDPOINT_PEER))
+ abort ();
/* If our control connection is over IPv6, then we first try EPRT and then
* LPRT if the former is not supported. If the control connection is over
* IPv4, we simply issue the good old PORT request. */
- if (family == AF_INET6)
+ switch (cip.type)
{
+ case IPV4_ADDRESS:
+ if (!opt.server_response)
+ logputs (LOG_VERBOSE, "==> PORT ... ");
+ err = ftp_port (rbuf, local_sock);
+ break;
+ case IPV6_ADDRESS:
if (!opt.server_response)
logputs (LOG_VERBOSE, "==> EPRT ... ");
err = ftp_eprt (rbuf, local_sock);
logputs (LOG_VERBOSE, "==> LPRT ... ");
err = ftp_lprt (rbuf, local_sock);
}
+ break;
+ default:
+ abort ();
}
- else
- {
- if (!opt.server_response)
- logputs (LOG_VERBOSE, "==> PORT ... ");
- err = ftp_port (rbuf, local_sock);
- }
-
return err;
}
#else
return al->addresses + pos;
}
-/* Check whether two address lists have all their IPs in common. */
+/* Return 1 if IP is one of the addresses in AL. */
int
-address_list_match_all (const struct address_list *al1,
- const struct address_list *al2)
+address_list_find (const struct address_list *al, const ip_address *ip)
{
-#ifdef ENABLE_IPV6
int i;
-#endif
- if (al1 == al2)
- return 1;
- if (al1->count != al2->count)
- return 0;
-
- /* For the comparison to be complete, we'd need to sort the IP
- addresses first. But that's not necessary because this is only
- used as an optimization. */
-
-#ifndef ENABLE_IPV6
- /* In the non-IPv6 case, there is only one address type, so we can
- compare the whole array with memcmp. */
- return 0 == memcmp (al1->addresses, al2->addresses,
- al1->count * sizeof (ip_address));
-#else /* ENABLE_IPV6 */
- for (i = 0; i < al1->count; ++i)
+ switch (ip->type)
{
- const ip_address *ip1 = &al1->addresses[i];
- const ip_address *ip2 = &al2->addresses[i];
-
- if (ip1->type != ip2->type)
- return 0;
-
- switch (ip1->type)
+ case IPV4_ADDRESS:
+ for (i = 0; i < al->count; i++)
{
- case IPV4_ADDRESS:
- if (ADDRESS_IPV4_IN_ADDR (ip1).s_addr
- !=
- ADDRESS_IPV4_IN_ADDR (ip2).s_addr)
- return 0;
- break;
- case IPV6_ADDRESS:
+ ip_address *cur = al->addresses + i;
+ if (cur->type == IPV4_ADDRESS
+ && (ADDRESS_IPV4_IN_ADDR (cur).s_addr
+ ==
+ ADDRESS_IPV4_IN_ADDR (ip).s_addr))
+ return 1;
+ }
+ return 0;
+#ifdef ENABLE_IPV6
+ case IPV6_ADDRESS:
+ for (i = 0; i < al->count; i++)
+ {
+ ip_address *cur = al->addresses + i;
+ if (cur->type == IPV6_ADDRESS
#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
- if (ADDRESS_IPV6_SCOPE (ip1) != ADDRESS_IPV6_SCOPE (ip2))
- return 0;
-#endif /* HAVE_SOCKADDR_IN6_SCOPE_ID */
- if (!IN6_ARE_ADDR_EQUAL (&ADDRESS_IPV6_IN6_ADDR (ip1),
- &ADDRESS_IPV6_IN6_ADDR (ip2)))
- return 0;
- break;
- default:
- abort ();
+ && ADDRESS_IPV6_SCOPE (cur) == ADDRESS_IPV6_SCOPE (ip)
+#endif
+ && IN6_ARE_ADDR_EQUAL (&ADDRESS_IPV6_IN6_ADDR (cur),
+ &ADDRESS_IPV6_IN6_ADDR (ip)))
+ return 1;
}
- }
- return 1;
+ return 0;
#endif /* ENABLE_IPV6 */
+ default:
+ abort ();
+ return 1;
+ }
}
/* Mark the INDEXth element of AL as faulty, so that the next time
int address_list_cached_p PARAMS ((const struct address_list *));
const ip_address *address_list_address_at PARAMS ((const struct address_list *,
int));
-int address_list_match_all PARAMS ((const struct address_list *,
- const struct address_list *));
+int address_list_find PARAMS ((const struct address_list *, const ip_address *));
void address_list_set_faulty PARAMS ((struct address_list *, int));
void address_list_release PARAMS ((struct address_list *));
/* Persistent connections. Currently, we cache the most recently used
connection as persistent, provided that the HTTP server agrees to
make it such. The persistence data is stored in the variables
- below. Ideally, it would be in a structure, and it should be
- possible to cache an arbitrary fixed number of these connections.
-
- I think the code is quite easy to extend in that direction. */
+ below. Ideally, it should be possible to cache an arbitrary fixed
+ number of these connections. */
/* Whether a persistent connection is active. */
-static int pc_active_p;
+static int pconn_active;
-/* Host and port of currently active persistent connection. */
-static struct address_list *pc_last_host_ip;
-static unsigned short pc_last_port;
+static struct {
+ /* The socket of the connection. */
+ int socket;
-/* File descriptor of the currently active persistent connection. */
-static int pc_last_fd;
+ /* Host and port of the currently active persistent connection. */
+ char *host;
+ int port;
-/* Whether a ssl handshake has occoured on this connection */
-static int pc_last_ssl_p;
+ /* Whether a ssl handshake has occoured on this connection. */
+ int ssl;
+} pconn;
-/* Mark the persistent connection as invalid. This is used by the
- CLOSE_* macros after they forcefully close a registered persistent
- connection. This does not close the file descriptor -- it is left
- to the caller to do that. (Maybe it should, though.) */
+/* Mark the persistent connection as invalid and free the resources it
+ uses. This is used by the CLOSE_* macros after they forcefully
+ close a registered persistent connection. */
static void
invalidate_persistent (void)
{
- pc_active_p = 0;
- pc_last_ssl_p = 0;
- if (pc_last_host_ip != NULL)
- {
- address_list_release (pc_last_host_ip);
- pc_last_host_ip = NULL;
- }
- DEBUGP (("Invalidating fd %d from further reuse.\n", pc_last_fd));
+ DEBUGP (("Disabling further reuse of socket %d.\n", pconn.socket));
+ pconn_active = 0;
+ xclose (pconn.socket);
+ xfree (pconn.host);
+ xzero (pconn);
}
/* Register FD, which should be a TCP/IP connection to HOST:PORT, as
If a previous connection was persistent, it is closed. */
static void
-register_persistent (const char *host, unsigned short port, int fd, int ssl)
+register_persistent (const char *host, int port, int fd, int ssl)
{
- if (pc_active_p)
+ if (pconn_active)
{
- if (pc_last_fd == fd)
+ if (pconn.socket == fd)
{
- /* The connection FD is already registered. Nothing to
- do. */
+ /* The connection FD is already registered. */
return;
}
else
{
- /* The old persistent connection is still active; let's
- close it first. This situation arises whenever a
- persistent connection exists, but we then connect to a
- different host, and try to register a persistent
- connection to that one. */
- xclose (pc_last_fd);
+ /* The old persistent connection is still active; close it
+ first. This situation arises whenever a persistent
+ connection exists, but we then connect to a different
+ host, and try to register a persistent connection to that
+ one. */
invalidate_persistent ();
}
}
- assert (pc_last_host_ip == NULL);
+ pconn_active = 1;
+ pconn.socket = fd;
+ pconn.host = xstrdup (host);
+ pconn.port = port;
+ pconn.ssl = ssl;
- /* This lookup_host cannot fail, because it has the results in the
- cache. */
- pc_last_host_ip = lookup_host (host, LH_SILENT);
- assert (pc_last_host_ip != NULL);
-
- pc_last_port = port;
- pc_last_fd = fd;
- pc_active_p = 1;
- pc_last_ssl_p = ssl;
- DEBUGP (("Registered fd %d for persistent reuse.\n", fd));
+ DEBUGP (("Registered socket %d for persistent reuse.\n", fd));
}
/* Return non-zero if a persistent connection is available for
connecting to HOST:PORT. */
static int
-persistent_available_p (const char *host, unsigned short port, int ssl)
+persistent_available_p (const char *host, int port, int ssl,
+ int *host_lookup_failed)
{
- int success;
- struct address_list *this_host_ip;
-
/* First, check whether a persistent connection is active at all. */
- if (!pc_active_p)
- return 0;
- /* Second, check if the active connection pertains to the correct
- (HOST, PORT) ordered pair. */
- if (port != pc_last_port)
+ if (!pconn_active)
return 0;
- /* Second, a): check if current connection is (not) ssl, too. This
- test is unlikely to fail because HTTP and HTTPS typicaly use
- different ports. Yet it is possible, or so I [Christian
- Fraenkel] have been told, to run HTTPS and HTTP simultaneus on
- the same port. */
- if (ssl != pc_last_ssl_p)
+ /* If we want SSL and the last connection wasn't or vice versa,
+ don't use it. Checking for host and port is not enough because
+ HTTP and HTTPS can apparently coexist on the same port. */
+ if (ssl != pconn.ssl)
return 0;
- this_host_ip = lookup_host (host, 0);
- if (!this_host_ip)
+ /* If we're not connecting to the same port, we're not interested. */
+ if (port != pconn.port)
return 0;
- /* To equate the two host names for the purposes of persistent
- connections, they need to share all the IP addresses in the
- list. */
- success = address_list_match_all (pc_last_host_ip, this_host_ip);
- address_list_release (this_host_ip);
- if (!success)
- return 0;
+ /* If the host is the same, we're in business. If not, there is
+ still hope -- read below. */
+ if (0 != strcasecmp (host, pconn.host))
+ {
+ /* This is somewhat evil, but works in practice: if the address
+ that pconn.socket is connected to is one of the IP addresses
+ HOST resolves to, we don't need to reconnect. #### Is it
+ correct to do this by default? */
+ int found;
+ ip_address ip;
+ struct address_list *al;
+
+ if (!socket_ip_address (pconn.socket, &ip, 0))
+ {
+ /* Can't get the peer's address -- something must be wrong
+ with the connection. */
+ invalidate_persistent ();
+ return 0;
+ }
+ al = lookup_host (host, 0);
+ if (!al)
+ {
+ *host_lookup_failed = 1;
+ return 0;
+ }
+
+ found = address_list_find (al, &ip);
+ address_list_release (al);
+
+ if (!found)
+ return 0;
+
+ /* HOST resolves to an address pconn.sock is connected to -- no
+ need to reconnect. */
+ }
- /* Third: check whether the connection is still open. This is
+ /* Finally, check whether the connection is still open. This is
important because most server implement a liberal (short) timeout
on persistent connections. Wget can of course always reconnect
if the connection doesn't work out, but it's nicer to know in
advance. This test is a logical followup of the first test, but
is "expensive" and therefore placed at the end of the list. */
- if (!test_socket_open (pc_last_fd))
+
+ if (!test_socket_open (pconn.socket))
{
/* Oops, the socket is no longer open. Now that we know that,
let's invalidate the persistent connection before returning
0. */
- xclose (pc_last_fd);
invalidate_persistent ();
return 0;
}
+
return 1;
}
#define CLOSE_FINISH(fd) do { \
if (!keep_alive) \
{ \
- xclose (fd); \
- if (pc_active_p && (fd) == pc_last_fd) \
+ if (pconn_active && (fd) == pconn.socket) \
invalidate_persistent (); \
+ else \
+ xclose (fd); \
} \
} while (0)
#define CLOSE_INVALIDATE(fd) do { \
- xclose (fd); \
- if (pc_active_p && (fd) == pc_last_fd) \
+ if (pconn_active && (fd) == pconn.socket) \
invalidate_persistent (); \
+ else \
+ xclose (fd); \
} while (0)
\f
struct http_stat
char *post_content_type, *post_content_length;
long post_data_size = 0;
+ int host_lookup_failed;
+
#ifdef HAVE_SSL
/* Initialize the SSL context. After the first run, this is a
no-op. */
server. */
conn = proxy ? proxy : u;
+ host_lookup_failed = 0;
+
/* First: establish the connection. */
if (inhibit_keep_alive
|| !persistent_available_p (conn->host, conn->port,
#else
0
#endif
- ))
+ , &host_lookup_failed))
{
+ /* In its current implementation, persistent_available_p will
+ look up conn->host in some cases. If that lookup failed, we
+ don't need to bother with connect_to_host. */
+ if (host_lookup_failed)
+ return HOSTERR;
+
sock = connect_to_host (conn->host, conn->port);
if (sock == E_HOST)
return HOSTERR;
conn->host, conn->port);
/* #### pc_last_fd should be accessed through an accessor
function. */
- sock = pc_last_fd;
- using_ssl = pc_last_ssl_p;
+ sock = pconn.socket;
+ using_ssl = pconn.ssl;
DEBUGP (("Reusing fd %d.\n", sock));
}
void
http_cleanup (void)
{
- if (pc_last_host_ip)
- address_list_release (pc_last_host_ip);
}