[PATCH] Cleanup comm connection setup layering

From: Amos Jeffries <squid3_at_treenet.co.nz>
Date: Wed, 09 Jun 2010 23:44:25 +1200

(since last submission I've implemented most of Alex suggestions, except
the one about making single-Comm::Connection cases a vector)

What has changed from HEAD:

ConnectionDetails objects have been renamed Comm::Connection and been
extended to hold the FD and Squids' socket flags.

Peer selection has been extended to do DNS lookups on the peers chosen
for forwarding to and produce a vector<> of possible connection
endpoints (squid local IP via tcp_outgoing_address or tproxy) and remote
server.
  Limited by forward_max_tries as to how many IP address destinations it
generates for a single request.

Various connection openers have been converted to use the new
ConnectStateData API and CommCalls (function based so far).

ConnectStateData has been moved into src/comm/ (not yet namespaced) and
had all its DNS lookup operations dropped. To be replaced by a looping
process of attempting to open a socket and join a link as described by
some Comm::Connection or vector<> of same.
  Limited by connect_timeout in its running time.
  Extended by connect_retries number of attempts to open each path.

ConnectStateData::connect() will go away and do some async work. Will
come back at some point by calling the handler with COMM_OK,
COMM_ERR_CONNECT, COMM_TIMEOUT and ptrs to the Comm::Connection or
vector (whichever were passed in).
  On COMM_OK the Comm::ConnectionPointer or the first entry of the
vector will be an open Comm::ConnectionPointer which we can now use.
  On COMM_ERR_CONNECT the vector will be empty (all tried and
discarded), the single Comm::ConnectionPointer will be !isOpen() or NULL.
  On COMM_TIMEOUT their content is as per COMM_ERR_CONNECT but the
vector may have untried paths still present but closed.

FD opening, FD problems, connection errors, timeouts, early remote
TCP_RST or NACK closure during the setup are all now wrapped out of
sight inside ConnectStateData.
  NP: I was not able to determine accurately the ICAP code which handled
some of those errors. So it is likely to be sitting around still an
unused. If so it needs to be called from the connect-callback handler on
the appropriate result state as described above.

The main-level component may set FD handlers as needed for read/write
and closure of the link in their connection-done handler where the FD
first becomes visible to them.

Future work once this is stable in HEAD is to (in no particular order):

  * Maybe implement the TODO in CommCalls about Connect and proper
AsynCalls handlers. It should be easier now.

  * make ICAP do DNS lookups to set its server Comm::Connection
properly. For now it's stuck with the gethostbyname() blocking lookup.

  * push the IDENT, NAT, EUI and TLS operations down into the Comm layer
with simple flags for other layers to turn them on/off as desired.

  * make the general code pass Comm::Connection around so everything
like ACLs can access the client and server conn when they need to.

  * implement SOCKS as just another connection mode flag.

Amos

-- 
Please be using
   Current Stable Squid 2.7.STABLE9 or 3.1.4

# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: squid3_at_treenet.co.nz-20100609112359-40kdtxunzd661fy8
# target_branch: http://www.squid-cache.org/bzr/squid3/trunk/
# testament_sha1: dddae81ca0d795022ceaf4b96c3a07843a95bfde
# timestamp: 2010-06-09 23:31:45 +1200
# base_revision_id: amosjeffries_at_squid-cache.org-20100609002621-\
# rnxi3js1ayij3m52
#
# Begin patch
=== modified file 'doc/release-notes/release-3.2.sgml'
--- doc/release-notes/release-3.2.sgml 2010-06-03 10:10:36 +0000
+++ doc/release-notes/release-3.2.sgml 2010-06-09 11:23:59 +0000
@@ -169,6 +169,10 @@
         <p>Access control based on altered HTTP request following adaptation alterations (ICAP, eCAP, URL rewriter).
         An upgraded drop-in replacement for <em>http_access2</em> found in Squid-2.
 
+ <tag>connect_retries</tag>
+ <p>Replacement for <em>maximum_single_addr_tries</em>, but instead of only applying to hosts with single addresses.
+ This directive applies to all hosts, extending the number of connection attempts to each IP address.
+
         <tag>eui_lookup</tag>
         <p>Whether to lookup the EUI or MAC address of a connected client.
 
@@ -260,6 +264,10 @@
         <tag>ftp_list_width</tag>
         <p>Obsolete.
 
+ <tag>maximum_single_addr_tries</tag>
+ <p>The behaviour controlled by this directive is no longer possible.
+ It has been replaced by <em>connect_retries</em> option which operates a little differently.
+
         <tag>url_rewrite_concurrency</tag>
         <p>Replaced by url_rewrite_children ... concurrency=N option.
 

=== modified file 'src/CommCalls.cc'
--- src/CommCalls.cc 2009-07-12 22:56:47 +0000
+++ src/CommCalls.cc 2010-06-08 14:03:24 +0000
@@ -1,5 +1,6 @@
 #include "squid.h"
 #include "fde.h"
+#include "comm/Connection.h"
 #include "CommCalls.h"
 
 /* CommCommonCbParams */
@@ -45,6 +46,7 @@
     CommCommonCbParams::print(os);
     if (nfd >= 0)
         os << ", newFD " << nfd;
+ os << ", " << details;
 }
 
 
@@ -71,7 +73,11 @@
 CommConnectCbParams::print(std::ostream &os) const
 {
     CommCommonCbParams::print(os);
- os << ", " << dns;
+ if (conn != NULL)
+ os << ", from my " << conn->local << " to " << conn->remote;
+ else if (paths && paths->size() > 0) {
+ // TODO: for each path. print the to => from path being attempted.
+ }
 }
 
 /* CommIoCbParams */
@@ -133,7 +139,7 @@
 void
 CommAcceptCbPtrFun::dial()
 {
- handler(params.fd, params.nfd, &params.details, params.flag, params.xerrno, params.data);
+ handler(params.fd, params.nfd, params.details, params.flag, params.xerrno, params.data);
 }
 
 void
@@ -157,7 +163,7 @@
 void
 CommConnectCbPtrFun::dial()
 {
- handler(params.fd, params.dns, params.flag, params.xerrno, params.data);
+ handler(params.conn, params.paths, params.flag, params.xerrno, params.data);
 }
 
 void

=== modified file 'src/CommCalls.h'
--- src/CommCalls.h 2009-11-17 15:44:34 +0000
+++ src/CommCalls.h 2010-06-08 14:03:24 +0000
@@ -6,11 +6,10 @@
 #ifndef SQUID_COMMCALLS_H
 #define SQUID_COMMCALLS_H
 
-#include "comm.h"
-#include "ConnectionDetail.h"
-#include "DnsLookupDetails.h"
 #include "base/AsyncCall.h"
 #include "base/AsyncJobCalls.h"
+#include "comm/comm_err_t.h"
+#include "comm/forward.h"
 
 /* CommCalls implement AsyncCall interface for comm_* callbacks.
  * The classes cover two call dialer kinds:
@@ -22,6 +21,10 @@
  * - I/O (IOCB).
  */
 
+typedef void IOACB(int fd, int nfd, Comm::ConnectionPointer details, comm_err_t flag, int xerrno, void *data);
+typedef void CNCB(Comm::ConnectionPointer conn, Comm::PathsPointer paths, comm_err_t status, int xerrno, void *data);
+typedef void IOCB(int fd, char *, size_t size, comm_err_t flag, int xerrno, void *data);
+
 /*
  * TODO: When there are no function-pointer-based callbacks left, all
  * this complexity can be removed. Jobs that need comm services will just
@@ -69,7 +72,7 @@
     void print(std::ostream &os) const;
 
 public:
- ConnectionDetail details;
+ Comm::ConnectionPointer details;
     int nfd; // TODO: rename to fdNew or somesuch
 };
 
@@ -84,7 +87,8 @@
     void print(std::ostream &os) const;
 
 public:
- DnsLookupDetails dns;
+ Comm::ConnectionPointer conn;
+ Comm::PathsPointer paths;
 };
 
 // read/write (I/O) parameters

=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2010-04-08 12:14:42 +0000
+++ src/HttpRequest.cc 2010-06-03 07:18:25 +0000
@@ -35,15 +35,16 @@
  */
 
 #include "squid.h"
+#include "acl/FilledChecklist.h"
+#if ICAP_CLIENT
+#include "adaptation/icap/icap_log.h"
+#endif
+#include "auth/UserRequest.h"
+#include "DnsLookupDetails.h"
 #include "HttpRequest.h"
-#include "auth/UserRequest.h"
 #include "HttpHeaderRange.h"
 #include "MemBuf.h"
 #include "Store.h"
-#if ICAP_CLIENT
-#include "adaptation/icap/icap_log.h"
-#endif
-#include "acl/FilledChecklist.h"
 
 HttpRequest::HttpRequest() : HttpMsg(hoRequest)
 {

=== modified file 'src/Makefile.am'
--- src/Makefile.am 2010-05-13 06:20:23 +0000
+++ src/Makefile.am 2010-06-03 07:18:25 +0000
@@ -289,7 +289,6 @@
         ConfigOption.cc \
         ConfigParser.cc \
         ConfigParser.h \
- ConnectionDetail.h \
         debug.cc \
         Debug.h \
         defines.h \
@@ -532,7 +531,7 @@
 
 squid_LDADD = \
         $(COMMON_LIBS) \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         eui/libeui.la \
         icmp/libicmp.la icmp/libicmp-core.la \
         log/liblog.la \
@@ -1235,10 +1234,10 @@
         wordlist.cc
 nodist_tests_testCacheManager_SOURCES = \
         $(BUILT_SOURCES)
-# comm.cc only requires comm/libcomm-listener.la until fdc_table is dead.
+# comm.cc only requires comm/libcomm.la until fdc_table is dead.
 tests_testCacheManager_LDADD = \
         $(COMMON_LIBS) \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         icmp/libicmp.la icmp/libicmp-core.la \
         log/liblog.la \
         $(REPL_OBJS) \
@@ -1421,7 +1420,7 @@
 tests_testEvent_LDADD = \
         $(COMMON_LIBS) \
         icmp/libicmp.la icmp/libicmp-core.la \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         log/liblog.la \
         $(REPL_OBJS) \
         ${ADAPTATION_LIBS} \
@@ -1576,7 +1575,7 @@
 tests_testEventLoop_LDADD = \
         $(COMMON_LIBS) \
         icmp/libicmp.la icmp/libicmp-core.la \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         log/liblog.la \
         $(REPL_OBJS) \
         ${ADAPTATION_LIBS} \
@@ -1726,7 +1725,7 @@
 tests_test_http_range_LDADD = \
         $(COMMON_LIBS) \
         icmp/libicmp.la icmp/libicmp-core.la \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         log/liblog.la \
         $(REPL_OBJS) \
         ${ADAPTATION_LIBS} \
@@ -1881,7 +1880,7 @@
 tests_testHttpRequest_LDADD = \
         $(COMMON_LIBS) \
         icmp/libicmp.la icmp/libicmp-core.la \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         log/liblog.la \
         $(REPL_OBJS) \
         ${ADAPTATION_LIBS} \
@@ -2253,7 +2252,7 @@
 tests_testURL_LDADD = \
         $(COMMON_LIBS) \
         icmp/libicmp.la icmp/libicmp-core.la \
- comm/libcomm-listener.la \
+ comm/libcomm.la \
         log/liblog.la \
         $(REGEXLIB) \
         $(REPL_OBJS) \

=== modified file 'src/PeerSelectState.h'
--- src/PeerSelectState.h 2010-05-02 19:32:42 +0000
+++ src/PeerSelectState.h 2010-06-08 14:03:24 +0000
@@ -33,9 +33,37 @@
 #ifndef SQUID_PEERSELECTSTATE_H
 #define SQUID_PEERSELECTSTATE_H
 
+#include "Array.h"
 #include "cbdata.h"
+#include "comm/forward.h"
+#include "hier_code.h"
+#include "ip/Address.h"
 #include "PingData.h"
-#include "ip/Address.h"
+
+class HttpRequest;
+class StoreEntry;
+
+typedef void PSC(Comm::PathsPointer, void *);
+
+SQUIDCEXTERN void peerSelect(Comm::PathsPointer, HttpRequest *, StoreEntry *, PSC *, void *data);
+SQUIDCEXTERN void peerSelectInit(void);
+
+/**
+ * A peer which has been selected as a possible destination.
+ * Listed as pointers here so as to prevent duplicates being added but will
+ * be converted to a set of IP address path options before handing back out
+ * to the caller.
+ *
+ * Certain connection flags and outgoing settings will also be looked up and
+ * set based on the received request and peer settings before handing back.
+ */
+class FwdServer
+{
+public:
+ peer *_peer; /* NULL --> origin server */
+ hier_code code;
+ FwdServer *next;
+};
 
 class ps_state
 {
@@ -50,7 +78,10 @@
     int direct;
     PSC *callback;
     void *callback_data;
- FwdServer *servers;
+
+ Comm::PathsPointer paths; ///< the callers paths array. to be filled with our final results.
+ FwdServer *servers; ///< temporary linked list of peers we will pass back.
+
     /*
      * Why are these Ip::Address instead of peer *? Because a
      * peer structure can become invalid during the peer selection

=== modified file 'src/adaptation/icap/Xaction.cc'
--- src/adaptation/icap/Xaction.cc 2010-04-17 02:29:04 +0000
+++ src/adaptation/icap/Xaction.cc 2010-06-09 10:11:25 +0000
@@ -4,6 +4,7 @@
 
 #include "squid.h"
 #include "comm.h"
+#include "comm/ConnectStateData.h"
 #include "CommCalls.h"
 #include "HttpMsg.h"
 #include "adaptation/icap/Xaction.h"
@@ -29,7 +30,7 @@
         icapRequest(NULL),
         icapReply(NULL),
         attempts(0),
- connection(-1),
+ connection(NULL),
         theService(aService),
         commBuf(NULL), commBufSize(0),
         commEof(false),
@@ -89,23 +90,32 @@
 {
     Ip::Address client_addr;
 
- Must(connection < 0);
+ Must(connection == NULL || !connection->isOpen());
 
     const Adaptation::Service &s = service();
 
     if (!TheConfig.reuse_connections)
         disableRetries(); // this will also safely drain pconn pool
 
+ connection = new Comm::Connection;
+
+ /* NP: set these here because it applies whether a pconn or a new conn is used */
+
+ // TODO: where do we get the DNS info for the ICAP server host ??
+ // Ip::Address will do a BLOCKING lookup if s.cfg().host is a hostname
+ connection->remote = s.cfg().host.termedBuf();
+ connection->remote.SetPort(s.cfg().port);
+
     // TODO: check whether NULL domain is appropriate here
- connection = icapPconnPool->pop(s.cfg().host.termedBuf(), s.cfg().port, NULL, client_addr, isRetriable);
- if (connection >= 0) {
- debugs(93,3, HERE << "reused pconn FD " << connection);
+ connection->fd = icapPconnPool->pop(s.cfg().host.termedBuf(), s.cfg().port, NULL, client_addr, isRetriable);
+ if (connection->isOpen()) {
+ debugs(93,3, HERE << "reused pconn FD " << connection->fd);
 
         // fake the connect callback
         // TODO: can we sync call Adaptation::Icap::Xaction::noteCommConnected here instead?
         typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> Dialer;
         Dialer dialer(this, &Adaptation::Icap::Xaction::noteCommConnected);
- dialer.params.fd = connection;
+ dialer.params.fd = connection->fd;
         dialer.params.flag = COMM_OK;
         // fake other parameters by copying from the existing connection
         connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer);
@@ -115,32 +125,14 @@
 
     disableRetries(); // we only retry pconn failures
 
- Ip::Address outgoing;
- connection = comm_open(SOCK_STREAM, 0, outgoing,
- COMM_NONBLOCKING, s.cfg().uri.termedBuf());
-
- if (connection < 0)
- dieOnConnectionFailure(); // throws
-
- debugs(93,3, typeName << " opens connection to " << s.cfg().host << ":" << s.cfg().port);
-
- // TODO: service bypass status may differ from that of a transaction
- typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
- TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout));
-
- commSetTimeout(connection, TheConfig.connect_timeout(
- service().cfg().bypass), timeoutCall);
-
- typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommCloseCbParams> CloseDialer;
- closer = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
- CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed));
- comm_add_close_handler(connection, closer);
-
     typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommConnectCbParams> ConnectDialer;
     connector = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected",
                           ConnectDialer(this, &Adaptation::Icap::Xaction::noteCommConnected));
- commConnectStart(connection, s.cfg().host.termedBuf(), s.cfg().port, connector);
+
+ ConnectStateData *cs = new ConnectStateData(connection, connector);
+ cs->host = xstrdup(s.cfg().host.termedBuf());
+ cs->connect_timeout = TheConfig.connect_timeout(service().cfg().bypass);
+ cs->connect();
 }
 
 /*
@@ -159,10 +151,10 @@
 
 void Adaptation::Icap::Xaction::closeConnection()
 {
- if (connection >= 0) {
+ if (connection != NULL && connection->isOpen()) {
 
         if (closer != NULL) {
- comm_remove_close_handler(connection, closer);
+ comm_remove_close_handler(connection->fd, closer);
             closer = NULL;
         }
 
@@ -179,35 +171,46 @@
             //status() adds leading spaces.
             debugs(93,3, HERE << "pushing pconn" << status());
             AsyncCall::Pointer call = NULL;
- commSetTimeout(connection, -1, call);
- icapPconnPool->push(connection, theService->cfg().host.termedBuf(),
+ commSetTimeout(connection->fd, -1, call);
+ icapPconnPool->push(connection->fd, theService->cfg().host.termedBuf(),
                                 theService->cfg().port, NULL, client_addr);
             disableRetries();
+ connection->fd = -1; // prevent premature real closing.
         } else {
             //status() adds leading spaces.
             debugs(93,3, HERE << "closing pconn" << status());
             // comm_close will clear timeout
- comm_close(connection);
+ connection->close();
         }
 
         writer = NULL;
         reader = NULL;
         connector = NULL;
- connection = -1;
     }
 }
 
 // connection with the ICAP service established
 void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams &io)
 {
+ if (io.flag == COMM_TIMEOUT) {
+ handleCommTimedout();
+ return;
+ }
+
     Must(connector != NULL);
     connector = NULL;
 
     if (io.flag != COMM_OK)
         dieOnConnectionFailure(); // throws
 
- fd_table[connection].noteUse(icapPconnPool);
-
+ typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommCloseCbParams> CloseDialer;
+ closer = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
+ CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed));
+ comm_add_close_handler(io.conn->fd, closer);
+
+ fd_table[io.conn->fd].noteUse(icapPconnPool);
+
+ connection = io.conn;
     handleCommConnected();
 }
 
@@ -225,7 +228,7 @@
     writer = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommWrote",
                        Dialer(this, &Adaptation::Icap::Xaction::noteCommWrote));
 
- comm_write_mbuf(connection, &buf, writer);
+ comm_write_mbuf(connection->fd, &buf, writer);
     updateTimeout();
 }
 
@@ -307,19 +310,19 @@
         AsyncCall::Pointer call = asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
                                              TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout));
 
- commSetTimeout(connection,
+ commSetTimeout(connection->fd,
                        TheConfig.io_timeout(service().cfg().bypass), call);
     } else {
         // clear timeout when there is no I/O
         // Do we need a lifetime timeout?
         AsyncCall::Pointer call = NULL;
- commSetTimeout(connection, -1, call);
+ commSetTimeout(connection->fd, -1, call);
     }
 }
 
 void Adaptation::Icap::Xaction::scheduleRead()
 {
- Must(connection >= 0);
+ Must(connection->isOpen());
     Must(!reader);
     Must(readBuf.hasSpace());
 
@@ -331,7 +334,7 @@
     reader = asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommRead",
                        Dialer(this, &Adaptation::Icap::Xaction::noteCommRead));
 
- comm_read(connection, commBuf, readBuf.spaceSize(), reader);
+ comm_read(connection->fd, commBuf, readBuf.spaceSize(), reader);
     updateTimeout();
 }
 
@@ -369,7 +372,7 @@
 void Adaptation::Icap::Xaction::cancelRead()
 {
     if (reader != NULL) {
- comm_read_cancel(connection, reader);
+ comm_read_cancel(connection->fd, reader);
         reader = NULL;
     }
 }
@@ -410,7 +413,7 @@
 
 bool Adaptation::Icap::Xaction::doneWithIo() const
 {
- return connection >= 0 && // or we could still be waiting to open it
+ return connection != NULL && connection->isOpen() && // or we could still be waiting to open it
            !connector && !reader && !writer && // fast checks, some redundant
            doneReading() && doneWriting();
 }
@@ -525,8 +528,8 @@
 
 void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf &buf) const
 {
- if (connection >= 0) {
- buf.Printf("FD %d", connection);
+ if (connection->isOpen()) {
+ buf.Printf("FD %d", connection->fd);
 
         if (writer != NULL)
             buf.append("w", 1);
@@ -540,8 +543,8 @@
 
 void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf &buf) const
 {
- if (connection >= 0 && commEof)
- buf.Printf("Comm(%d)", connection);
+ if (connection->isOpen() && commEof)
+ buf.Printf("Comm(%d)", connection->fd);
 
     if (stopReason != NULL)
         buf.Printf("Stopped");

=== modified file 'src/adaptation/icap/Xaction.h'
--- src/adaptation/icap/Xaction.h 2009-08-23 09:30:49 +0000
+++ src/adaptation/icap/Xaction.h 2010-06-09 10:11:25 +0000
@@ -34,7 +34,7 @@
 #ifndef SQUID_ICAPXACTION_H
 #define SQUID_ICAPXACTION_H
 
-#include "comm.h"
+#include "comm/forward.h"
 #include "CommCalls.h"
 #include "MemBuf.h"
 #include "adaptation/icap/ServiceRep.h"
@@ -140,7 +140,7 @@
     void maybeLog();
 
 protected:
- int connection; // FD of the ICAP server connection
+ Comm::ConnectionPointer connection; // Handle to the ICAP server connection
     Adaptation::Icap::ServiceRep::Pointer theService;
 
     /*

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc 2010-06-03 00:12:32 +0000
+++ src/cache_cf.cc 2010-06-09 11:23:59 +0000
@@ -526,12 +526,9 @@
     else
         Config.appendDomainLen = 0;
 
- if (Config.retry.maxtries > 10)
- fatal("maximum_single_addr_tries cannot be larger than 10");
-
- if (Config.retry.maxtries < 1) {
- debugs(3, 0, "WARNING: resetting 'maximum_single_addr_tries to 1");
- Config.retry.maxtries = 1;
+ if (Config.connect_retries > 10) {
+ debugs(0,DBG_CRITICAL, "WARNING: connect_retries cannot be larger than 10. Resetting to 10.");
+ Config.connect_retries = 10;
     }
 
     requirePathnameExists("MIME Config Table", Config.mimeTablePathname);
@@ -1929,7 +1926,7 @@
 
     p->icp.version = ICP_VERSION_CURRENT;
 
- p->test_fd = -1;
+ p->testing_now = false;
 
 #if USE_CACHE_DIGESTS
 

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre 2010-06-03 07:49:20 +0000
+++ src/cf.data.pre 2010-06-09 11:23:59 +0000
@@ -2232,6 +2232,9 @@
 DOC_START
         Controls how many different forward paths Squid will try
         before giving up. See also forward_timeout.
+
+ NOTE: connect_retries (default: none) can make each of these
+ possible forwarding paths be tried multiple times.
 DOC_END
 
 NAME: hierarchy_stoplist
@@ -6785,21 +6788,26 @@
         see also refresh_pattern for a more selective approach.
 DOC_END
 
-NAME: maximum_single_addr_tries
+NAME: connect_retries
 TYPE: int
-LOC: Config.retry.maxtries
-DEFAULT: 1
+LOC: Config.connect_retries
+DEFAULT: 0
 DOC_START
- This sets the maximum number of connection attempts for a
- host that only has one address (for multiple-address hosts,
- each address is tried once).
-
- The default value is one attempt, the (not recommended)
- maximum is 255 tries. A warning message will be generated
- if it is set to a value greater than ten.
-
- Note: This is in addition to the request re-forwarding which
- takes place if Squid fails to get a satisfying response.
+ This sets the maximum number of connection attempts for each
+ potential host address selected by forwarding.
+
+ The default is not to re-try if the first connection attempt fails.
+ The (not recommended) maximum is 10 tries.
+
+ A warning message will be generated if it is set to a too-high
+ value and the configured value will be over-ridden.
+
+ Note: These re-tries are in addition to forward_max_tries
+ which limit how many different addresses may be tried to find
+ a useful server.
+
+ The connect_retries * forward_max_tries attempts must all still
+ complete within the connection timeout period.
 DOC_END
 
 NAME: retry_on_error

=== modified file 'src/client_side.cc'
--- src/client_side.cc 2010-05-13 06:20:23 +0000
+++ src/client_side.cc 2010-06-08 14:03:24 +0000
@@ -92,8 +92,8 @@
 #include "ClientRequestContext.h"
 #include "clientStream.h"
 #include "comm.h"
+#include "comm/Connection.h"
 #include "comm/ListenStateData.h"
-#include "ConnectionDetail.h"
 #include "eui/Config.h"
 #include "fde.h"
 #include "HttpHdrContRange.h"
@@ -3054,7 +3054,7 @@
 
 /** Handle a new connection on HTTP socket. */
 void
-httpAccept(int sock, int newfd, ConnectionDetail *details,
+httpAccept(int sock, int newfd, Comm::ConnectionPointer details,
            comm_err_t flag, int xerrno, void *data)
 {
     http_port_list *s = (http_port_list *)data;
@@ -3067,7 +3067,7 @@
 
     debugs(33, 4, "httpAccept: FD " << newfd << ": accepted");
     fd_note(newfd, "client http connect");
- connState = connStateCreate(&details->peer, &details->me, newfd, s);
+ connState = connStateCreate(&details->remote, &details->local, newfd, s);
 
     typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer;
     AsyncCall::Pointer call = asyncCall(33, 5, "ConnStateData::connStateClosed",
@@ -3075,7 +3075,7 @@
     comm_add_close_handler(newfd, call);
 
     if (Config.onoff.log_fqdn)
- fqdncache_gethostbyaddr(details->peer, FQDN_LOOKUP_IF_MISS);
+ fqdncache_gethostbyaddr(details->remote, FQDN_LOOKUP_IF_MISS);
 
     typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall = asyncCall(33, 5, "ConnStateData::requestTimeout",
@@ -3085,19 +3085,19 @@
 #if USE_IDENT
     if (Ident::TheConfig.identLookup) {
         ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, NULL);
- identChecklist.src_addr = details->peer;
- identChecklist.my_addr = details->me;
+ identChecklist.src_addr = details->remote;
+ identChecklist.my_addr = details->local;
         if (identChecklist.fastCheck())
- Ident::Start(details->me, details->peer, clientIdentDone, connState);
+ Ident::Start(details, clientIdentDone, connState);
     }
 #endif
 
 #if USE_SQUID_EUI
     if (Eui::TheConfig.euiLookup) {
- if (details->peer.IsIPv4()) {
- connState->peer_eui48.lookup(details->peer);
- } else if (details->peer.IsIPv6()) {
- connState->peer_eui64.lookup(details->peer);
+ if (details->remote.IsIPv4()) {
+ connState->peer_eui48.lookup(details->remote);
+ } else if (details->remote.IsIPv6()) {
+ connState->peer_eui64.lookup(details->remote);
         }
     }
 #endif
@@ -3108,7 +3108,7 @@
 
     connState->readSomeData();
 
- clientdbEstablished(details->peer, 1);
+ clientdbEstablished(details->remote, 1);
 
     incoming_sockets_accepted++;
 }
@@ -3117,7 +3117,7 @@
 
 /** Create SSL connection structure and update fd_table */
 static SSL *
-httpsCreate(int newfd, ConnectionDetail *details, SSL_CTX *sslContext)
+httpsCreate(int newfd, Comm::ConnectionPointer details, SSL_CTX *sslContext)
 {
     SSL *ssl = SSL_new(sslContext);
 
@@ -3260,7 +3260,7 @@
 
 /** handle a new HTTPS connection */
 static void
-httpsAccept(int sock, int newfd, ConnectionDetail *details,
+httpsAccept(int sock, int newfd, Comm::ConnectionPointer details,
             comm_err_t flag, int xerrno, void *data)
 {
     https_port_list *s = (https_port_list *)data;
@@ -3278,7 +3278,7 @@
 
     debugs(33, 5, "httpsAccept: FD " << newfd << " accepted, starting SSL negotiation.");
     fd_note(newfd, "client https connect");
- ConnStateData *connState = connStateCreate(details->peer, details->me,
+ ConnStateData *connState = connStateCreate(details->remote, details->local,
                                newfd, &s->http);
     typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer;
     AsyncCall::Pointer call = asyncCall(33, 5, "ConnStateData::connStateClosed",
@@ -3286,7 +3286,7 @@
     comm_add_close_handler(newfd, call);
 
     if (Config.onoff.log_fqdn)
- fqdncache_gethostbyaddr(details->peer, FQDN_LOOKUP_IF_MISS);
+ fqdncache_gethostbyaddr(details->remote, FQDN_LOOKUP_IF_MISS);
 
     typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall = asyncCall(33, 5, "ConnStateData::requestTimeout",
@@ -3296,10 +3296,10 @@
 #if USE_IDENT
     if (Ident::TheConfig.identLookup) {
         ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, NULL);
- identChecklist.src_addr = details->peer;
- identChecklist.my_addr = details->me;
+ identChecklist.src_addr = details->remote;
+ identChecklist.my_addr = details->local;
         if (identChecklist.fastCheck())
- Ident::Start(details->me, details->peer, clientIdentDone, connState);
+ Ident::Start(details, clientIdentDone, connState);
     }
 #endif
 
@@ -3309,7 +3309,7 @@
 
     commSetSelect(newfd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
 
- clientdbEstablished(details->peer, 1);
+ clientdbEstablished(details->remote, 1);
 
     incoming_sockets_accepted++;
 }
@@ -3326,10 +3326,10 @@
 
     debugs(33, 5, HERE << "converting FD " << fd << " to SSL");
 
- // fake a ConnectionDetail object; XXX: make ConnState a ConnectionDetail?
- ConnectionDetail detail;
- detail.me = me;
- detail.peer = peer;
+ // fake a Comm::Connection object; XXX: make ConnState a Comm::Connection?
+ Comm::Connection detail;
+ detail.local = me;
+ detail.remote = peer;
 
     SSL_CTX *sslContext = port->sslContext;
     SSL *ssl = NULL;

=== modified file 'src/client_side.h'
--- src/client_side.h 2010-05-04 12:33:30 +0000
+++ src/client_side.h 2010-06-03 07:18:25 +0000
@@ -124,8 +124,6 @@
 };
 
 
-class ConnectionDetail;
-
 /** A connection to a socket */
 class ConnStateData : public BodyProducer/*, public RefCountable*/
 {

=== modified file 'src/comm.cc'
--- src/comm.cc 2010-05-26 03:06:02 +0000
+++ src/comm.cc 2010-06-09 09:50:31 +0000
@@ -33,16 +33,17 @@
  */
 
 #include "squid.h"
+#include "base/AsyncCall.h"
 #include "StoreIOBuffer.h"
 #include "comm.h"
 #include "event.h"
 #include "fde.h"
 #include "comm/AcceptLimiter.h"
 #include "comm/comm_internal.h"
+#include "comm/Connection.h"
 #include "comm/ListenStateData.h"
 #include "CommIO.h"
 #include "CommRead.h"
-#include "ConnectionDetail.h"
 #include "MemBuf.h"
 #include "pconn.h"
 #include "SquidTime.h"
@@ -195,38 +196,6 @@
 {
 }
 
-class ConnectStateData
-{
-
-public:
- void *operator new (size_t);
- void operator delete (void *);
- static void Connect (int fd, void *me);
- void connect();
- void callCallback(comm_err_t status, int xerrno);
- void defaults();
-
-// defaults given by client
- char *host;
- u_short default_port;
- Ip::Address default_addr;
- // NP: CANNOT store the default addr:port together as it gets set/reset differently.
-
- DnsLookupDetails dns; ///< host lookup details
- Ip::Address S;
- AsyncCall::Pointer callback;
-
- int fd;
- int tries;
- int addrcount;
- int connstart;
-
-private:
- int commResetFD();
- int commRetryConnect();
- CBDATA_CLASS(ConnectStateData);
-};
-
 /* STATIC */
 
 static DescriptorSet *TheHalfClosed = NULL; /// the set of half-closed FDs
@@ -241,7 +210,6 @@
 static void commSetTcpNoDelay(int);
 #endif
 static void commSetTcpRcvbuf(int, int);
-static PF commConnectFree;
 static PF commHandleWrite;
 static IPH commConnectDnsHandle;
 
@@ -825,110 +793,6 @@
     return new_socket;
 }
 
-CBDATA_CLASS_INIT(ConnectStateData);
-
-void *
-ConnectStateData::operator new (size_t size)
-{
- CBDATA_INIT_TYPE(ConnectStateData);
- return cbdataAlloc(ConnectStateData);
-}
-
-void
-ConnectStateData::operator delete (void *address)
-{
- cbdataFree(address);
-}
-
-
-
-void
-commConnectStart(int fd, const char *host, u_short port, AsyncCall::Pointer &cb)
-{
- debugs(cb->debugSection, cb->debugLevel, "commConnectStart: FD " << fd <<
- ", cb " << cb << ", " << host << ":" << port); // TODO: just print *cb
-
- ConnectStateData *cs;
- cs = new ConnectStateData;
- cs->fd = fd;
- cs->host = xstrdup(host);
- cs->default_port = port;
- cs->callback = cb;
-
- comm_add_close_handler(fd, commConnectFree, cs);
- ipcache_nbgethostbyname(host, commConnectDnsHandle, cs);
-}
-
-// TODO: Remove this and similar callback registration functions by replacing
-// (callback,data) parameters with an AsyncCall so that we do not have to use
-// a generic call name and debug level when creating an AsyncCall. This will
-// also cut the number of callback registration routines in half.
-void
-commConnectStart(int fd, const char *host, u_short port, CNCB * callback, void *data)
-{
- debugs(5, 5, "commConnectStart: FD " << fd << ", data " << data << ", " << host << ":" << port);
- AsyncCall::Pointer call = commCbCall(5,3,
- "SomeCommConnectHandler", CommConnectCbPtrFun(callback, data));
- commConnectStart(fd, host, port, call);
-}
-
-static void
-commConnectDnsHandle(const ipcache_addrs *ia, const DnsLookupDetails &details, void *data)
-{
- ConnectStateData *cs = (ConnectStateData *)data;
- cs->dns = details;
-
- if (ia == NULL) {
- debugs(5, 3, "commConnectDnsHandle: Unknown host: " << cs->host);
- cs->callCallback(COMM_ERR_DNS, 0);
- return;
- }
-
- assert(ia->cur < ia->count);
-
- cs->default_addr = ia->in_addrs[ia->cur];
-
- if (Config.onoff.balance_on_multiple_ip)
- ipcacheCycleAddr(cs->host, NULL);
-
- cs->addrcount = ia->count;
-
- cs->connstart = squid_curtime;
-
- cs->connect();
-}
-
-void
-ConnectStateData::callCallback(comm_err_t status, int xerrno)
-{
- debugs(5, 3, "commConnectCallback: FD " << fd);
-
- comm_remove_close_handler(fd, commConnectFree, this);
- commSetTimeout(fd, -1, NULL, NULL);
-
- typedef CommConnectCbParams Params;
- Params &params = GetCommParams<Params>(callback);
- params.fd = fd;
- params.dns = dns;
- params.flag = status;
- params.xerrno = xerrno;
- ScheduleCallHere(callback);
- callback = NULL;
-
- commConnectFree(fd, this);
-}
-
-static void
-commConnectFree(int fd, void *data)
-{
- ConnectStateData *cs = (ConnectStateData *)data;
- debugs(5, 3, "commConnectFree: FD " << fd);
-// delete cs->callback;
- cs->callback = NULL;
- safe_free(cs->host);
- delete cs;
-}
-
 static void
 copyFDFlags(int to, fde *F)
 {
@@ -949,198 +813,6 @@
         commSetTcpRcvbuf(to, Config.tcpRcvBufsz);
 }
 
-/* Reset FD so that we can connect() again */
-int
-ConnectStateData::commResetFD()
-{
-
-// XXX: do we have to check this?
-//
-// if (!cbdataReferenceValid(callback.data))
-// return 0;
-
- statCounter.syscalls.sock.sockets++;
-
- fde *F = &fd_table[fd];
-
- struct addrinfo *AI = NULL;
- F->local_addr.GetAddrInfo(AI);
- int new_family = AI->ai_family;
-
- int fd2 = socket(new_family, AI->ai_socktype, AI->ai_protocol);
-
- if (fd2 < 0) {
- debugs(5, DBG_CRITICAL, HERE << "WARNING: FD " << fd2 << " socket failed to allocate: " << xstrerror());
-
- if (ENFILE == errno || EMFILE == errno)
- fdAdjustReserved();
-
- F->local_addr.FreeAddrInfo(AI);
- return 0;
- }
-
-#ifdef _SQUID_MSWIN_
-
- /* On Windows dup2() can't work correctly on Sockets, the */
- /* workaround is to close the destination Socket before call them. */
- close(fd);
-
-#endif
-
- if (dup2(fd2, fd) < 0) {
- debugs(5, DBG_CRITICAL, HERE << "WARNING: dup2(FD " << fd2 << ", FD " << fd << ") failed: " << xstrerror());
-
- if (ENFILE == errno || EMFILE == errno)
- fdAdjustReserved();
-
- close(fd2);
-
- F->local_addr.FreeAddrInfo(AI);
- return 0;
- }
- commResetSelect(fd);
-
- close(fd2);
-
- debugs(50, 3, "commResetFD: Reset socket FD " << fd << "->" << fd2 << " : family=" << new_family );
-
- /* INET6: copy the new sockets family type to the FDE table */
- F->sock_family = new_family;
-
- F->flags.called_connect = 0;
-
- /*
- * yuck, this has assumptions about comm_open() arguments for
- * the original socket
- */
-
- /* MUST be done before binding or face OS Error: "(99) Cannot assign requested address"... */
- if ( F->flags.transparent ) {
- comm_set_transparent(fd);
- }
-
- if (commBind(fd, *AI) != COMM_OK) {
- debugs(5, DBG_CRITICAL, "WARNING: Reset of FD " << fd << " for " << F->local_addr << " failed to bind: " << xstrerror());
- F->local_addr.FreeAddrInfo(AI);
- return 0;
- }
- F->local_addr.FreeAddrInfo(AI);
-
- if (F->tos)
- comm_set_tos(fd, F->tos);
-
-#if IPV6_SPECIAL_SPLITSTACK
- if ( F->local_addr.IsIPv6() )
- comm_set_v6only(fd, 1);
-#endif
-
- copyFDFlags(fd, F);
-
- return 1;
-}
-
-int
-ConnectStateData::commRetryConnect()
-{
- assert(addrcount > 0);
-
- if (addrcount == 1) {
- if (tries >= Config.retry.maxtries)
- return 0;
-
- if (squid_curtime - connstart > Config.Timeout.connect)
- return 0;
- } else {
- if (tries > addrcount) {
- /* Flush bad address count in case we are
- * skipping over incompatible protocol
- */
- ipcacheMarkAllGood(host);
- return 0;
- }
- }
-
- return commResetFD();
-}
-
-static void
-commReconnect(void *data)
-{
- ConnectStateData *cs = (ConnectStateData *)data;
- ipcache_nbgethostbyname(cs->host, commConnectDnsHandle, cs);
-}
-
-/** Connect SOCK to specified DEST_PORT at DEST_HOST. */
-void
-ConnectStateData::Connect(int fd, void *me)
-{
- ConnectStateData *cs = (ConnectStateData *)me;
- assert (cs->fd == fd);
- cs->connect();
-}
-
-void
-ConnectStateData::defaults()
-{
- S = default_addr;
- S.SetPort(default_port);
-}
-
-void
-ConnectStateData::connect()
-{
- defaults();
-
- debugs(5,5, HERE << "to " << S);
-
- switch (comm_connect_addr(fd, S) ) {
-
- case COMM_INPROGRESS:
- debugs(5, 5, HERE << "FD " << fd << ": COMM_INPROGRESS");
- commSetSelect(fd, COMM_SELECT_WRITE, ConnectStateData::Connect, this, 0);
- break;
-
- case COMM_OK:
- debugs(5, 5, HERE << "FD " << fd << ": COMM_OK - connected");
- ipcacheMarkGoodAddr(host, S);
- callCallback(COMM_OK, 0);
- break;
-
- case COMM_ERR_PROTOCOL:
- debugs(5, 5, HERE "FD " << fd << ": COMM_ERR_PROTOCOL - try again");
- /* problem using the desired protocol over this socket.
- * skip to the next address and hope it's more compatible
- * but do not mark the current address as bad
- */
- tries++;
- if (commRetryConnect()) {
- /* Force an addr cycle to move forward to the next possible address */
- ipcacheCycleAddr(host, NULL);
- eventAdd("commReconnect", commReconnect, this, this->addrcount == 1 ? 0.05 : 0.0, 0);
- } else {
- debugs(5, 5, HERE << "FD " << fd << ": COMM_ERR_PROTOCOL - ERR tried too many times already.");
- callCallback(COMM_ERR_CONNECT, errno);
- }
- break;
-
- default:
- debugs(5, 5, HERE "FD " << fd << ": * - try again");
- tries++;
- ipcacheMarkBadAddr(host, S);
-
-#if USE_ICMP
- if (Config.onoff.test_reachability)
- netdbDeleteAddrNetwork(S);
-#endif
-
- if (commRetryConnect()) {
- eventAdd("commReconnect", commReconnect, this, this->addrcount == 1 ? 0.05 : 0.0, 0);
- } else {
- debugs(5, 5, HERE << "FD " << fd << ": * - ERR tried too many times already.");
- callCallback(COMM_ERR_CONNECT, errno);
- }
- }
-}
 /*
 int
 commSetTimeout_old(int fd, int timeout, PF * handler, void *data)
@@ -1182,7 +854,8 @@
 }
 
 
-int commSetTimeout(int fd, int timeout, AsyncCall::Pointer &callback)
+int
+commSetTimeout(int fd, int timeout, AsyncCall::Pointer &callback)
 {
     debugs(5, 3, HERE << "FD " << fd << " timeout " << timeout);
     assert(fd >= 0);
@@ -2385,10 +2058,6 @@
     cancelled = true;
 }
 
-ConnectionDetail::ConnectionDetail() : me(), peer()
-{
-}
-
 int
 CommSelectEngine::checkEvents(int timeout)
 {

=== modified file 'src/comm.h'
--- src/comm.h 2010-05-14 05:29:55 +0000
+++ src/comm.h 2010-06-09 09:50:31 +0000
@@ -4,33 +4,15 @@
 #include "squid.h"
 #include "AsyncEngine.h"
 #include "base/AsyncCall.h"
+#include "CommCalls.h"
+#include "comm/comm_err_t.h"
+#include "comm/forward.h"
+#include "ip/Address.h"
 #include "StoreIOBuffer.h"
-#include "Array.h"
-#include "ip/Address.h"
 
 #define COMMIO_FD_READCB(fd) (&commfd_table[(fd)].readcb)
 #define COMMIO_FD_WRITECB(fd) (&commfd_table[(fd)].writecb)
 
-typedef enum {
- COMM_OK = 0,
- COMM_ERROR = -1,
- COMM_NOMESSAGE = -3,
- COMM_TIMEOUT = -4,
- COMM_SHUTDOWN = -5,
- COMM_IDLE = -6, /* there are no active fds and no pending callbacks. */
- COMM_INPROGRESS = -7,
- COMM_ERR_CONNECT = -8,
- COMM_ERR_DNS = -9,
- COMM_ERR_CLOSING = -10,
- COMM_ERR_PROTOCOL = -11, /* IPv4 or IPv6 cannot be used on the fd socket */
- COMM_ERR__END__ = -999999 /* Dummy entry to make syntax valid (comma on line above), do not use. New entries added above */
-} comm_err_t;
-
-class DnsLookupDetails;
-typedef void CNCB(int fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data);
-
-typedef void IOCB(int fd, char *, size_t size, comm_err_t flag, int xerrno, void *data);
-
 
 /* comm.c */
 extern bool comm_iocallbackpending(void); /* inline candidate */
@@ -40,13 +22,11 @@
 SQUIDCEXTERN void commSetCloseOnExec(int fd);
 SQUIDCEXTERN void commSetTcpKeepalive(int fd, int idle, int interval, int timeout);
 extern void _comm_close(int fd, char const *file, int line);
-#define comm_close(fd) (_comm_close((fd), __FILE__, __LINE__))
+#define comm_close(x) (_comm_close((x), __FILE__, __LINE__))
 SQUIDCEXTERN void comm_reset_close(int fd);
 #if LINGERING_CLOSE
 SQUIDCEXTERN void comm_lingering_close(int fd);
 #endif
-SQUIDCEXTERN void commConnectStart(int fd, const char *, u_short, CNCB *, void *);
-void commConnectStart(int fd, const char *, u_short, AsyncCall::Pointer &cb);
 
 SQUIDCEXTERN int comm_connect_addr(int sock, const Ip::Address &addr);
 SQUIDCEXTERN void comm_init(void);
@@ -98,8 +78,7 @@
 SQUIDCEXTERN comm_err_t comm_select(int);
 SQUIDCEXTERN void comm_quick_poll_required(void);
 
-class ConnectionDetail;
-typedef void IOACB(int fd, int nfd, ConnectionDetail *details, comm_err_t flag, int xerrno, void *data);
+//typedef void IOACB(int fd, int nfd, Comm::ConnectionPointer details, comm_err_t flag, int xerrno, void *data);
 extern void comm_add_close_handler(int fd, PF *, void *);
 extern void comm_add_close_handler(int fd, AsyncCall::Pointer &);
 extern void comm_remove_close_handler(int fd, PF *, void *);

=== added file 'src/comm/ConnectStateData.cc'
--- src/comm/ConnectStateData.cc 1970-01-01 00:00:00 +0000
+++ src/comm/ConnectStateData.cc 2010-06-09 11:23:59 +0000
@@ -0,0 +1,204 @@
+#include "config.h"
+#include "comm/ConnectStateData.h"
+#include "comm/Connection.h"
+#include "comm.h"
+#include "CommCalls.h"
+#include "fde.h"
+#include "icmp/net_db.h"
+#include "SquidTime.h"
+
+CBDATA_CLASS_INIT(ConnectStateData);
+
+ConnectStateData::ConnectStateData(Comm::PathsPointer paths, AsyncCall::Pointer handler) :
+ host(NULL),
+ connect_timeout(Config.Timeout.connect),
+ paths(paths),
+ solo(NULL),
+ callback(handler),
+ total_tries(0),
+ fail_retries(0),
+ connstart(0)
+{}
+
+ConnectStateData::ConnectStateData(Comm::ConnectionPointer c, AsyncCall::Pointer handler) :
+ host(NULL),
+ connect_timeout(Config.Timeout.connect),
+ paths(NULL),
+ solo(c),
+ callback(handler),
+ total_tries(0),
+ fail_retries(0),
+ connstart(0)
+{}
+
+ConnectStateData::~ConnectStateData()
+{
+ safe_free(host);
+ paths = NULL; // caller code owns them.
+ solo = NULL;
+}
+
+void
+ConnectStateData::callCallback(comm_err_t status, int xerrno)
+{
+ int fd = -1;
+ if (paths != NULL && paths->size() > 0) {
+ fd = (*paths)[0]->fd;
+ debugs(5, 3, HERE << "FD " << fd);
+ comm_remove_close_handler(fd, ConnectStateData::EarlyAbort, this);
+ commSetTimeout(fd, -1, NULL, NULL);
+ }
+
+ typedef CommConnectCbParams Params;
+ Params &params = GetCommParams<Params>(callback);
+ if (solo != NULL) {
+ params.conn = solo;
+ } else if (paths != NULL) {
+ params.paths = paths;
+ if (paths->size() > 0)
+ params.conn = (*paths)[0];
+ } else {
+ /* catch the error case. */
+ assert(paths != NULL && solo != NULL);
+ }
+ params.flag = status;
+ params.xerrno = xerrno;
+ ScheduleCallHere(callback);
+
+ callback = NULL;
+ safe_free(host);
+ delete this;
+}
+
+void
+ConnectStateData::connect()
+{
+ Comm::ConnectionPointer active;
+
+ /* handle connecting to one single path */
+ /* mainly used by components other than forwarding */
+
+ /* handle connecting to one of multiple paths */
+ /* mainly used by forwarding */
+
+ if (solo != NULL) {
+ active = solo;
+ } else if (paths) {
+ Comm::Paths::iterator i = paths->begin();
+
+ if (connstart == 0) {
+ connstart = squid_curtime;
+ }
+
+ /* find some socket we can use. will also bind the local address to it if needed. */
+ while(paths->size() > 0 && (*i)->fd <= 0) {
+#if USE_IPV6
+ /* outbound sockets have no need to be protocol agnostic. */
+ if ((*i)->local.IsIPv6() && (*i)->local.IsIPv4()) {
+ (*i)->local.SetIPv4();
+ }
+#endif
+ (*i)->fd = comm_openex(SOCK_STREAM, IPPROTO_TCP, (*i)->local, (*i)->flags, (*i)->tos, host);
+ if ((*i)->fd <= 0) {
+ debugs(5 , 2, HERE << "Unable to connect " << (*i)->local << " -> " << (*i)->remote << " for " << host);
+ paths->shift();
+ i = paths->begin();
+ }
+ // else success will terminate the loop with: i->fd >0
+ }
+
+ /* we have nowhere left to try connecting */
+ if (paths->size() < 1) {
+ callCallback(COMM_ERR_CONNECT, 0);
+ return;
+ }
+
+ active = (*i);
+ }
+
+ total_tries++;
+
+ switch (comm_connect_addr(active->fd, active->remote) ) {
+
+ case COMM_INPROGRESS:
+ debugs(5, 5, HERE << "FD " << active->fd << ": COMM_INPROGRESS");
+ commSetSelect(active->fd, COMM_SELECT_WRITE, ConnectStateData::ConnectRetry, this, 0);
+ break;
+
+ case COMM_OK:
+ debugs(5, 5, HERE << "FD " << active->fd << ": COMM_OK - connected");
+
+ /*
+ * stats.conn_open is used to account for the number of
+ * connections that we have open to the peer, so we can limit
+ * based on the max-conn option. We need to increment here,
+ * even if the connection may fail.
+ */
+ if (active->getPeer())
+ active->getPeer()->stats.conn_open++;
+
+ /* TODO: remove this fd_table access. But old code still depends on fd_table flags to
+ * indicate the state of a raw fd object being passed around.
+ */
+ fd_table[active->fd].flags.open = 1;
+
+ ipcacheMarkGoodAddr(host, active->remote);
+ callCallback(COMM_OK, 0);
+ break;
+
+ default:
+ debugs(5, 5, HERE "FD " << active->fd << ": * - try again");
+ fail_retries++;
+ ipcacheMarkBadAddr(host, active->remote);
+
+#if USE_ICMP
+ if (Config.onoff.test_reachability)
+ netdbDeleteAddrNetwork(active->remote);
+#endif
+
+ // check for timeout FIRST.
+ if(squid_curtime - connstart > connect_timeout) {
+ debugs(5, 5, HERE << "FD " << active->fd << ": * - ERR took too long already.");
+ callCallback(COMM_TIMEOUT, errno);
+ } else if (fail_retries < Config.connect_retries) {
+ // check if connect_retries extends the single IP re-try limit.
+ eventAdd("ConnectStateData::Connect", ConnectStateData::Connect, this, 0.5, 0);
+ } else if (paths && paths->size() > 0) {
+ // check if we have more maybe-useful paths to try.
+ paths->shift();
+ fail_retries = 0;
+ eventAdd("ConnectStateData::Connect", ConnectStateData::Connect, this, 0.0, 0);
+ } else {
+ // send ERROR back to the upper layer.
+ debugs(5, 5, HERE << "FD " << active->fd << ": * - ERR tried too many times already.");
+ callCallback(COMM_ERR_CONNECT, errno);
+ }
+ }
+}
+
+void
+ConnectStateData::EarlyAbort(int fd, void *data)
+{
+ ConnectStateData *cs = static_cast<ConnectStateData *>(data);
+ debugs(5, 3, HERE << "FD " << fd);
+ cs->callCallback(COMM_ERR_CLOSING, errno); // NP: is closing or shutdown better?
+
+ /* TODO split cases:
+ * remote end rejecting the connection is normal and one of the other paths may be taken.
+ * squid shutting down or forcing abort on the connection attempt(s) are the only real fatal cases.
+ */
+}
+
+void
+ConnectStateData::Connect(void *data)
+{
+ ConnectStateData *cs = static_cast<ConnectStateData *>(data);
+ cs->connect();
+}
+
+void
+ConnectStateData::ConnectRetry(int fd, void *data)
+{
+ ConnectStateData *cs = static_cast<ConnectStateData *>(data);
+ cs->connect();
+}

=== added file 'src/comm/ConnectStateData.h'
--- src/comm/ConnectStateData.h 1970-01-01 00:00:00 +0000
+++ src/comm/ConnectStateData.h 2010-06-08 14:03:24 +0000
@@ -0,0 +1,76 @@
+#ifndef _SQUID_SRC_COMM_CONNECTSTATEDATA_H
+#define _SQUID_SRC_COMM_CONNECTSTATEDATA_H
+
+#include "base/AsyncCall.h"
+#include "cbdata.h"
+#include "comm/comm_err_t.h"
+#include "comm/forward.h"
+
+/**
+ * State engine handling the opening of a remote outbound connection
+ * to one of multiple destinations.
+ */
+class ConnectStateData
+{
+public:
+ /** open first working of a set of connections */
+ ConnectStateData(Comm::PathsPointer paths, AsyncCall::Pointer handler);
+
+ /** attempt to open one connection. */
+ ConnectStateData(Comm::ConnectionPointer, AsyncCall::Pointer handler);
+
+ ~ConnectStateData();
+
+ /**
+ * Actual connect start function.
+ */
+ void connect();
+
+private:
+ /* These objects may NOT be created without connections to act on. Do not define this operator. */
+ ConnectStateData();
+ /* These objects may NOT be copied. Do not define this operator. */
+ const ConnectStateData operator =(const ConnectStateData &c);
+
+ /**
+ * Wrapper to start the connection attempts happening.
+ */
+ static void Connect(void *data);
+
+ /** retry */
+ static void ConnectRetry(int fd, void *data);
+
+ /**
+ * Temporary close handler used during connect.
+ * Handles the case(s) when a partially setup connection gets closed early.
+ */
+ static void EarlyAbort(int fd, void *data);
+
+ /**
+ * Connection attempt are completed. One way or the other.
+ * Pass the results back to the external handler.
+ */
+ void callCallback(comm_err_t status, int xerrno);
+
+public:
+ char *host; ///< domain name we are trying to connect to.
+
+ /**
+ * time at which to abandon the connection.
+ * the connection-done callback will be passed COMM_TIMEOUT
+ */
+ time_t connect_timeout;
+
+private:
+ Comm::PathsPointer paths; ///< forwarding paths to be tried. front of the list is the current being opened.
+ Comm::ConnectionPointer solo; ///< single connection currently being opened.
+ AsyncCall::Pointer callback; ///< handler to be called on connection completion.
+
+ int total_tries; ///< total number of connection attempts over all destinations so far.
+ int fail_retries; ///< number of retries current destination has been tried.
+ time_t connstart; ///< time at which this series of connection attempts was started.
+
+ CBDATA_CLASS2(ConnectStateData);
+};
+
+#endif /* _SQUID_SRC_COMM_CONNECTSTATEDATA_H */

=== added file 'src/comm/Connection.cc'
--- src/comm/Connection.cc 1970-01-01 00:00:00 +0000
+++ src/comm/Connection.cc 2010-06-09 09:50:31 +0000
@@ -0,0 +1,71 @@
+#include "config.h"
+#include "cbdata.h"
+#include "comm.h"
+#include "comm/Connection.h"
+
+Comm::Connection::Connection() :
+ local(),
+ remote(),
+ peer_type(HIER_NONE),
+ fd(-1),
+ tos(0),
+ flags(COMM_NONBLOCKING),
+ _peer(NULL)
+{}
+
+Comm::Connection::Connection(const Comm::Connection &c) :
+ local(c.local),
+ remote(c.remote),
+ peer_type(c.peer_type),
+ fd(c.fd),
+ tos(c.tos),
+ flags(c.flags)
+{
+ _peer = cbdataReference(c._peer);
+}
+
+const Comm::Connection &
+Comm::Connection::operator =(const Comm::Connection &c)
+{
+ memcpy(this, &c, sizeof(Comm::Connection));
+
+ /* ensure we have a cbdata reference to _peer not a straight ptr copy. */
+ _peer = cbdataReference(c._peer);
+
+ return *this;
+}
+
+Comm::Connection::~Connection()
+{
+ close();
+ if (_peer) {
+ cbdataReferenceDone(_peer);
+ }
+}
+
+void
+Comm::Connection::close()
+{
+ if (isOpen())
+ comm_close(fd);
+ fd = -1;
+}
+
+void
+Comm::Connection::setPeer(peer *p)
+{
+ /* set to self. nothing to do. */
+ if (_peer == p)
+ return;
+
+ /* clear any previous ptr */
+ if (_peer) {
+ cbdataReferenceDone(_peer);
+ _peer = NULL;
+ }
+
+ /* set the new one (unless it is NULL */
+ if (p) {
+ _peer = cbdataReference(p);
+ }
+}

=== renamed file 'src/ConnectionDetail.h' => 'src/comm/Connection.h'
--- src/ConnectionDetail.h 2010-05-26 03:06:02 +0000
+++ src/comm/Connection.h 2010-06-09 10:44:03 +0000
@@ -1,5 +1,6 @@
 /*
  * DEBUG: section 05 Socket Functions
+ * AUTHOR: Amos Jeffries
  * AUTHOR: Robert Collins
  *
  * SQUID Web Proxy Cache http://www.squid-cache.org/
@@ -30,23 +31,102 @@
  *
  *
  * Copyright (c) 2003, Robert Collins <robertc_at_squid-cache.org>
+ * Copyright (c) 2010, Amos Jeffries <amosjeffries_at_squid-cache.org>
  */
 
 #ifndef _SQUIDCONNECTIONDETAIL_H_
 #define _SQUIDCONNECTIONDETAIL_H_
 
+#include "hier_code.h"
 #include "ip/Address.h"
-
-class ConnectionDetail
+#include "RefCount.h"
+
+struct peer;
+
+namespace Comm {
+
+/* TODO: make these a struct of boolean flags members in the connection instead of a bitmap.
+ * we can't do that until all non-comm code uses Commm::Connection objects to create FD
+ * currently there is code still using comm_open() and comm_openex() synchronously!!
+ */
+#define COMM_UNSET 0x00
+#define COMM_NONBLOCKING 0x01
+#define COMM_NOCLOEXEC 0x02
+#define COMM_REUSEADDR 0x04
+#define COMM_TRANSPARENT 0x08
+#define COMM_DOBIND 0x10
+
+/**
+ * Store data about the physical and logical attributes of a connection.
+ *
+ * Some link state can be infered from the data, however this is not an
+ * object for state data. But a semantic equivalent for FD with easily
+ * accessible cached properties not requiring repeated complex lookups.
+ *
+ * While the properties may be changed, this is for teh purpose of creating
+ * potential connection descriptors which may be opened. Properties should
+ * be considered read-only outside of the Comm layer code once the connection
+ * is open.
+ *
+ * These objects must not be passed around directly,
+ * but a Comm::ConnectionPointer must be passed instead.
+ */
+class Connection : public RefCountable
 {
-
 public:
-
- ConnectionDetail();
-
- Ip::Address me;
-
- Ip::Address peer;
+ /** standard empty connection creation */
+ Connection();
+
+ /** Clear the conection properties and close any open socket. */
+ ~Connection();
+
+ /** Clone an existing connections properties.
+ * This includes the FD, if one is open its a good idea to set it to -1 (unopen)
+ * on one after copying to prevent both clones calling comm_close() when destructed.
+ */
+ Connection(const Connection &c);
+ /** see Comm::Connection::Connection */
+ const Connection & operator =(const Connection &c);
+
+ void close();
+
+ /** determine whether this object describes an active connection or not. */
+ bool isOpen() { return (fd >= 0); }
+
+ /** Address/Port for the Squid end of a TCP link. */
+ Ip::Address local;
+
+ /** Address for the Remote end of a TCP link. */
+ Ip::Address remote;
+
+ /** Hierarchy code for this connection link */
+ hier_code peer_type;
+
+ /** Socket used by this connection. -1 if no socket has been opened. */
+ int fd;
+
+ /** Quality of Service TOS values currently sent on this connection */
+ int tos;
+
+ /** COMM flags set on this connection */
+ int flags;
+
+ /** retrieve the peer pointer for use.
+ * The caller is responsible for all CBDATA operations regarding the
+ * used of the pointer returned.
+ */
+ peer * const getPeer() const { return _peer; }
+
+ /** alter the stored peer pointer.
+ * Perform appropriate CBDATA operations for locking the peer pointer
+ */
+ void setPeer(peer * p);
+
+private:
+ /** cache_peer data object (if any) */
+ peer *_peer;
 };
 
+}; // namespace Comm
+
 #endif

=== modified file 'src/comm/ListenStateData.cc'
--- src/comm/ListenStateData.cc 2010-05-26 03:06:02 +0000
+++ src/comm/ListenStateData.cc 2010-06-08 14:03:24 +0000
@@ -35,9 +35,9 @@
 #include "squid.h"
 #include "CommCalls.h"
 #include "comm/AcceptLimiter.h"
+#include "comm/Connection.h"
 #include "comm/comm_internal.h"
 #include "comm/ListenStateData.h"
-#include "ConnectionDetail.h"
 #include "fde.h"
 #include "protos.h"
 #include "SquidTime.h"
@@ -151,8 +151,8 @@
      */
 
     /* Accept a new connection */
- ConnectionDetail connDetails;
- int newfd = oldAccept(connDetails);
+ Connection *connDetails = new Connection();
+ int newfd = oldAccept(*connDetails);
 
     /* Check for errors */
     if (newfd < 0) {
@@ -171,7 +171,7 @@
     }
 
     debugs(5, 5, HERE << "accepted: FD " << fd <<
- " newfd: " << newfd << " from: " << connDetails.peer <<
+ " newfd: " << newfd << " from: " << connDetails->remote <<
            " handler: " << *theCallback);
     notify(newfd, COMM_OK, 0, connDetails);
     return true;
@@ -186,7 +186,7 @@
 }
 
 void
-Comm::ListenStateData::notify(int newfd, comm_err_t errcode, int xerrno, const ConnectionDetail &connDetails)
+Comm::ListenStateData::notify(int newfd, comm_err_t errcode, int xerrno, Comm::ConnectionPointer connDetails)
 {
     // listener socket handlers just abandon the port with COMM_ERR_CLOSING
     // it should only happen when this object is deleted...
@@ -213,17 +213,17 @@
  * Wait for an incoming connection on FD.
  */
 int
-Comm::ListenStateData::oldAccept(ConnectionDetail &details)
+Comm::ListenStateData::oldAccept(Comm::Connection &details)
 {
     PROF_start(comm_accept);
     statCounter.syscalls.sock.accepts++;
     int sock;
     struct addrinfo *gai = NULL;
- details.me.InitAddrInfo(gai);
+ details.local.InitAddrInfo(gai);
 
     if ((sock = accept(fd, gai->ai_addr, &gai->ai_addrlen)) < 0) {
 
- details.me.FreeAddrInfo(gai);
+ details.local.FreeAddrInfo(gai);
 
         PROF_stop(comm_accept);
 
@@ -239,21 +239,21 @@
         }
     }
 
- details.peer = *gai;
+ details.remote = *gai;
 
     if ( Config.client_ip_max_connections >= 0) {
- if (clientdbEstablished(details.peer, 0) > Config.client_ip_max_connections) {
- debugs(50, DBG_IMPORTANT, "WARNING: " << details.peer << " attempting more than " << Config.client_ip_max_connections << " connections.");
- details.me.FreeAddrInfo(gai);
+ if (clientdbEstablished(details.remote, 0) > Config.client_ip_max_connections) {
+ debugs(50, DBG_IMPORTANT, "WARNING: " << details.remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
+ details.local.FreeAddrInfo(gai);
             return COMM_ERROR;
         }
     }
 
- details.me.InitAddrInfo(gai);
+ details.local.InitAddrInfo(gai);
 
- details.me.SetEmpty();
+ details.local.SetEmpty();
     getsockname(sock, gai->ai_addr, &gai->ai_addrlen);
- details.me = *gai;
+ details.local = *gai;
 
     commSetCloseOnExec(sock);
 
@@ -264,15 +264,15 @@
     fdd_table[sock].close_line = 0;
 
     fde *F = &fd_table[sock];
- details.peer.NtoA(F->ipaddr,MAX_IPSTRLEN);
- F->remote_port = details.peer.GetPort();
- F->local_addr.SetPort(details.me.GetPort());
+ details.remote.NtoA(F->ipaddr,MAX_IPSTRLEN);
+ F->remote_port = details.remote.GetPort();
+ F->local_addr.SetPort(details.local.GetPort());
 #if USE_IPV6
     F->sock_family = AF_INET;
 #else
- F->sock_family = details.me.IsIPv4()?AF_INET:AF_INET6;
+ F->sock_family = details.local.IsIPv4()?AF_INET:AF_INET6;
 #endif
- details.me.FreeAddrInfo(gai);
+ details.local.FreeAddrInfo(gai);
 
     commSetNonBlocking(sock);
 

=== modified file 'src/comm/ListenStateData.h'
--- src/comm/ListenStateData.h 2010-01-13 01:13:17 +0000
+++ src/comm/ListenStateData.h 2010-06-08 14:03:24 +0000
@@ -3,16 +3,18 @@
 
 #include "config.h"
 #include "base/AsyncCall.h"
-#include "comm.h"
+#include "comm/comm_err_t.h"
+#include "comm/forward.h"
+
 #if HAVE_MAP
 #include <map>
 #endif
 
-class ConnectionDetail;
-
 namespace Comm
 {
 
+class Connection;
+
 class ListenStateData
 {
 
@@ -23,7 +25,7 @@
 
     void subscribe(AsyncCall::Pointer &call);
     void acceptNext();
- void notify(int newfd, comm_err_t, int xerrno, const ConnectionDetail &);
+ void notify(int newfd, comm_err_t, int xerrno, Comm::ConnectionPointer);
 
     int fd;
 
@@ -42,7 +44,7 @@
     static void doAccept(int fd, void *data);
 
     bool acceptOne();
- int oldAccept(ConnectionDetail &details);
+ int oldAccept(Comm::Connection &details);
 
     AsyncCall::Pointer theCallback;
     bool mayAcceptMore;

=== modified file 'src/comm/Makefile.am'
--- src/comm/Makefile.am 2009-12-31 02:35:01 +0000
+++ src/comm/Makefile.am 2010-06-08 14:03:24 +0000
@@ -1,13 +1,22 @@
 include $(top_srcdir)/src/Common.am
 include $(top_srcdir)/src/TestHeaders.am
 
-noinst_LTLIBRARIES = libcomm-listener.la
+noinst_LTLIBRARIES = libcomm.la
 
-## Library holding listener comm socket handlers
-libcomm_listener_la_SOURCES= \
+## First group are listener comm socket handlers
+## Second group are outbound connection setup handlers
+## Third group are misc shared comm objects
+libcomm_la_SOURCES= \
         AcceptLimiter.cc \
         AcceptLimiter.h \
         ListenStateData.cc \
         ListenStateData.h \
         \
- comm_internal.h
+ ConnectStateData.cc \
+ ConnectStateData.h \
+ \
+ Connection.cc \
+ Connection.h \
+ comm_err_t.h \
+ comm_internal.h \
+ forward.h

=== added file 'src/comm/comm_err_t.h'
--- src/comm/comm_err_t.h 1970-01-01 00:00:00 +0000
+++ src/comm/comm_err_t.h 2010-05-19 11:28:21 +0000
@@ -0,0 +1,21 @@
+#ifndef _SQUID_COMM_COMM_ERR_T_H
+#define _SQUID_COMM_COMM_ERR_T_H
+
+#include "config.h"
+
+typedef enum {
+ COMM_OK = 0,
+ COMM_ERROR = -1,
+ COMM_NOMESSAGE = -3,
+ COMM_TIMEOUT = -4,
+ COMM_SHUTDOWN = -5,
+ COMM_IDLE = -6, /* there are no active fds and no pending callbacks. */
+ COMM_INPROGRESS = -7,
+ COMM_ERR_CONNECT = -8,
+ COMM_ERR_DNS = -9,
+ COMM_ERR_CLOSING = -10,
+ COMM_ERR_PROTOCOL = -11, /* IPv4 or IPv6 cannot be used on the fd socket */
+ COMM_ERR__END__ = -999999 /* Dummy entry to make syntax valid (comma on line above), do not use. New entries added above */
+} comm_err_t;
+
+#endif /* _SQUID_COMM_COMM_ERR_T_H */

=== added file 'src/comm/forward.h'
--- src/comm/forward.h 1970-01-01 00:00:00 +0000
+++ src/comm/forward.h 2010-06-08 14:03:24 +0000
@@ -0,0 +1,18 @@
+#ifndef _SQUID_COMM_FORWARD_H
+#define _SQUID_COMM_FORWARD_H
+
+#include "Array.h"
+#include "RefCount.h"
+
+namespace Comm {
+
+class Connection;
+
+typedef RefCount<Comm::Connection> ConnectionPointer;
+
+typedef Vector<Comm::ConnectionPointer> Paths;
+typedef Vector<Comm::ConnectionPointer>* PathsPointer;
+
+}; // namespace Comm
+
+#endif /* _SQUID_COMM_FORWARD_H */

=== modified file 'src/defines.h'
--- src/defines.h 2009-09-16 07:34:24 +0000
+++ src/defines.h 2010-05-19 11:28:21 +0000
@@ -62,12 +62,6 @@
 #define COMM_SELECT_READ (0x1)
 #define COMM_SELECT_WRITE (0x2)
 
-#define COMM_NONBLOCKING 0x01
-#define COMM_NOCLOEXEC 0x02
-#define COMM_REUSEADDR 0x04
-#define COMM_TRANSPARENT 0x08
-#define COMM_DOBIND 0x10
-
 #define safe_free(x) if (x) { xxfree(x); x = NULL; }
 
 #define DISK_OK (0)

=== modified file 'src/dns_internal.cc'
--- src/dns_internal.cc 2010-05-09 14:25:58 +0000
+++ src/dns_internal.cc 2010-06-09 09:50:31 +0000
@@ -1,4 +1,3 @@
-
 /*
  * $Id$
  *
@@ -33,16 +32,16 @@
  *
  */
 
-#include "config.h"
 #include "squid.h"
-#include "event.h"
 #include "CacheManager.h"
-#include "SquidTime.h"
-#include "Store.h"
+#include "comm/Connection.h"
+#include "comm/ConnectStateData.h"
 #include "comm.h"
+#include "event.h"
 #include "fde.h"
 #include "MemBuf.h"
-
+#include "SquidTime.h"
+#include "Store.h"
 #include "wordlist.h"
 
 #if HAVE_ARPA_NAMESER_H
@@ -176,6 +175,7 @@
 #endif
 static void idnsCacheQuery(idns_query * q);
 static void idnsSendQuery(idns_query * q);
+static CNCB idnsInitVCConnected;
 static IOCB idnsReadVCHeader;
 static void idnsDoSendQueryVC(nsvc *vc);
 
@@ -186,6 +186,7 @@
 static EVH idnsCheckQueue;
 static void idnsTickleQueue(void);
 static void idnsRcodeCount(int, int);
+static void idnsVCClosed(int fd, void *data);
 
 static void
 idnsAddNameserver(const char *buf)
@@ -698,18 +699,21 @@
 }
 
 static void
-idnsInitVCConnected(int fd, const DnsLookupDetails &, comm_err_t status, int xerrno, void *data)
+idnsInitVCConnected(Comm::ConnectionPointer conn, Comm::PathsPointer unused, comm_err_t status, int xerrno, void *data)
 {
     nsvc * vc = (nsvc *)data;
 
- if (status != COMM_OK) {
+ if (status != COMM_OK || !conn) {
         char buf[MAX_IPSTRLEN];
- debugs(78, 1, "idnsInitVCConnected: Failed to connect to nameserver " << nameservers[vc->ns].S.NtoA(buf,MAX_IPSTRLEN) << " using TCP!");
- comm_close(fd);
+ debugs(78, DBG_IMPORTANT, "Failed to connect to nameserver " << nameservers[vc->ns].S.NtoA(buf,MAX_IPSTRLEN) << " using TCP!");
+ conn = NULL;
         return;
     }
 
- comm_read(fd, (char *)&vc->msglen, 2 , idnsReadVCHeader, vc);
+ vc->fd = conn->fd; // TODO: make the vc store the conn instead?
+
+ comm_add_close_handler(conn->fd, idnsVCClosed, vc);
+ comm_read(conn->fd, (char *)&vc->msglen, 2 , idnsReadVCHeader, vc);
     vc->busy = 0;
     idnsDoSendQueryVC(vc);
 }
@@ -727,37 +731,27 @@
 static void
 idnsInitVC(int ns)
 {
- char buf[MAX_IPSTRLEN];
-
     nsvc *vc = cbdataAlloc(nsvc);
     nameservers[ns].vc = vc;
     vc->ns = ns;
+ vc->queue = new MemBuf;
+ vc->msg = new MemBuf;
+ vc->busy = 1;
 
- Ip::Address addr;
+ Comm::ConnectionPointer conn = new Comm::Connection;
 
     if (!Config.Addrs.udp_outgoing.IsNoAddr())
- addr = Config.Addrs.udp_outgoing;
+ conn->local = Config.Addrs.udp_outgoing;
     else
- addr = Config.Addrs.udp_incoming;
-
- vc->queue = new MemBuf;
-
- vc->msg = new MemBuf;
-
- vc->fd = comm_open(SOCK_STREAM,
- IPPROTO_TCP,
- addr,
- COMM_NONBLOCKING,
- "DNS TCP Socket");
-
- if (vc->fd < 0)
- fatal("Could not create a DNS socket");
-
- comm_add_close_handler(vc->fd, idnsVCClosed, vc);
-
- vc->busy = 1;
-
- commConnectStart(vc->fd, nameservers[ns].S.NtoA(buf,MAX_IPSTRLEN), nameservers[ns].S.GetPort(), idnsInitVCConnected, vc);
+ conn->local = Config.Addrs.udp_incoming;
+
+ conn->remote = nameservers[ns].S;
+
+ AsyncCall::Pointer call = commCbCall(78,3, "idnsInitVCConnected", CommConnectCbPtrFun(idnsInitVCConnected, vc));
+
+ ConnectStateData *cs = new ConnectStateData(conn, call);
+ cs->host = xstrdup("DNS TCP Socket");
+ cs->connect();
 }
 
 static void

=== modified file 'src/forward.cc'
--- src/forward.cc 2010-06-03 07:49:20 +0000
+++ src/forward.cc 2010-06-09 09:50:31 +0000
@@ -32,34 +32,36 @@
 
 
 #include "squid.h"
-#include "forward.h"
 #include "acl/FilledChecklist.h"
 #include "acl/Gadgets.h"
 #include "CacheManager.h"
+#include "comm/Connection.h"
+#include "comm/ConnectStateData.h"
+#include "CommCalls.h"
 #include "event.h"
 #include "errorpage.h"
 #include "fde.h"
+#include "forward.h"
 #include "hier_code.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "MemObject.h"
 #include "pconn.h"
+#include "PeerSelectState.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "icmp/net_db.h"
 #include "ip/Intercept.h"
 
+
 static PSC fwdStartCompleteWrapper;
 static PF fwdServerClosedWrapper;
 #if USE_SSL
 static PF fwdNegotiateSSLWrapper;
 #endif
-static PF fwdConnectTimeoutWrapper;
-static EVH fwdConnectStartWrapper;
 static CNCB fwdConnectDoneWrapper;
 
 static OBJH fwdStats;
-static void fwdServerFree(FwdServer * fs);
 
 #define MAX_FWD_STATS_IDX 9
 static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][HTTP_INVALID_HEADER + 1];
@@ -78,11 +80,7 @@
     FwdState* fwd = (FwdState*)d;
     Pointer tmp = fwd; // Grab a temporary pointer to keep the object alive during our scope.
 
- if (fwd->server_fd >= 0) {
- comm_close(fwd->server_fd);
- fwd->server_fd = -1;
- }
-
+ fwd->paths[0]->close();
     fwd->self = NULL;
 }
 
@@ -92,7 +90,6 @@
 {
     entry = e;
     client_fd = fd;
- server_fd = -1;
     request = HTTPMSGLOCK(r);
     start_t = squid_curtime;
 
@@ -113,10 +110,7 @@
     // Otherwise we are going to leak our object.
 
     entry->registerAbort(FwdState::abort, this);
- peerSelect(request, entry, fwdStartCompleteWrapper, this);
-
- // TODO: set self _after_ the peer is selected because we do not need
- // self until we start talking to some Server.
+ peerSelect(&paths, request, entry, fwdStartCompleteWrapper, this);
 }
 
 void
@@ -162,8 +156,6 @@
     if (! flags.forward_completed)
         completed();
 
- serversFree(&servers);
-
     HTTPMSGUNLOCK(request);
 
     if (err)
@@ -175,15 +167,14 @@
 
     entry = NULL;
 
- int fd = server_fd;
-
- if (fd > -1) {
- server_fd = -1;
- comm_remove_close_handler(fd, fwdServerClosedWrapper, this);
- debugs(17, 3, "fwdStateFree: closing FD " << fd);
- comm_close(fd);
+ if (paths[0]->fd > -1) {
+ comm_remove_close_handler(paths[0]->fd, fwdServerClosedWrapper, this);
+ debugs(17, 3, HERE << "closing FD " << paths[0]->fd);
+ paths[0]->close();
     }
 
+ paths.clean();
+
     debugs(17, 3, HERE << "FwdState destructor done");
 }
 
@@ -226,7 +217,7 @@
         }
     }
 
- debugs(17, 3, "FwdState::start() '" << entry->url() << "'");
+ debugs(17, 3, HERE << "'" << entry->url() << "'");
     /*
      * This seems like an odd place to bind mem_obj and request.
      * Might want to assert that request is NULL at this point
@@ -260,13 +251,6 @@
 
     default:
         FwdState::Pointer fwd = new FwdState(client_fd, entry, request);
-
- /* If we need to transparently proxy the request
- * then we need the client source protocol, address and port */
- if (request->flags.spoof_client_ip) {
- fwd->src = request->client_addr;
- }
-
         fwd->start(fwd);
         return;
     }
@@ -275,6 +259,22 @@
 }
 
 void
+FwdState::startComplete()
+{
+ debugs(17, 3, HERE << entry->url() );
+
+ if (paths.size() > 0) {
+ connectStart();
+ } else {
+ debugs(17, 3, HERE << entry->url() );
+ ErrorState *anErr = errorCon(ERR_CANNOT_FORWARD, HTTP_SERVICE_UNAVAILABLE, request);
+ anErr->xerrno = errno;
+ fail(anErr);
+ self = NULL; // refcounted
+ }
+}
+
+void
 FwdState::fail(ErrorState * errorState)
 {
     debugs(17, 3, HERE << err_type_str[errorState->type] << " \"" << httpStatusString(errorState->httpStatus) << "\"\n\t" << entry->url() );
@@ -295,10 +295,9 @@
 FwdState::unregister(int fd)
 {
     debugs(17, 3, HERE << entry->url() );
- assert(fd == server_fd);
+ assert(fd == paths[0]->fd);
     assert(fd > -1);
     comm_remove_close_handler(fd, fwdServerClosedWrapper, this);
- server_fd = -1;
 }
 
 /**
@@ -310,9 +309,8 @@
 void
 FwdState::complete()
 {
- StoreEntry *e = entry;
     assert(entry->store_status == STORE_PENDING);
- debugs(17, 3, HERE << e->url() << "\n\tstatus " << entry->getReply()->sline.status );
+ debugs(17, 3, HERE << entry->url() << "\n\tstatus " << entry->getReply()->sline.status );
 #if URL_CHECKSUM_DEBUG
 
     entry->mem_obj->checkUrlChecksum();
@@ -321,20 +319,28 @@
     logReplyStatus(n_tries, entry->getReply()->sline.status);
 
     if (reforward()) {
- debugs(17, 3, "fwdComplete: re-forwarding " << entry->getReply()->sline.status << " " << e->url());
-
- if (server_fd > -1)
- unregister(server_fd);
-
- e->reset();
-
- startComplete(servers);
+ debugs(17, 3, HERE << "re-forwarding " << entry->getReply()->sline.status << " " << entry->url());
+
+ if (paths[0]->fd > -1)
+ unregister(paths[0]->fd);
+
+ entry->reset();
+
+ /* the call to reforward() has already dropped the last path off the
+ * selection list. all we have now are the next path(s) to be tried.
+ */
+
+ AsyncCall::Pointer call = commCbCall(17,3, "fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper, this));
+ ConnectStateData *cs = new ConnectStateData(&paths, call);
+ cs->host = xstrdup(entry->url());
+ cs->connect_timeout = Config.Timeout.connect;
+ cs->connect();
     } else {
- debugs(17, 3, "fwdComplete: server FD " << server_fd << " not re-forwarding status " << entry->getReply()->sline.status);
+ debugs(17, 3, HERE << "server FD " << paths[0]->fd << " not re-forwarding status " << entry->getReply()->sline.status);
         EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
         entry->complete();
 
- if (server_fd < 0)
+ if (paths[0]->fd < 0)
             completed();
 
         self = NULL; // refcounted
@@ -345,10 +351,10 @@
 /**** CALLBACK WRAPPERS ************************************************************/
 
 static void
-fwdStartCompleteWrapper(FwdServer * servers, void *data)
+fwdStartCompleteWrapper(Comm::PathsPointer unused, void *data)
 {
     FwdState *fwd = (FwdState *) data;
- fwd->startComplete(servers);
+ fwd->startComplete();
 }
 
 static void
@@ -372,31 +378,13 @@
     FwdState *fwd = (FwdState *) data;
     fwd->negotiateSSL(fd);
 }
-
 #endif
 
-static void
-fwdConnectDoneWrapper(int server_fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
-{
- FwdState *fwd = (FwdState *) data;
- fwd->connectDone(server_fd, dns, status, xerrno);
-}
-
-static void
-fwdConnectTimeoutWrapper(int fd, void *data)
-{
- FwdState *fwd = (FwdState *) data;
- fwd->connectTimeout(fd);
-}
-
-/*
- * Accounts for closed persistent connections
- */
-static void
-fwdPeerClosed(int fd, void *data)
-{
- peer *p = (peer *)data;
- p->stats.conn_open--;
+void
+fwdConnectDoneWrapper(Comm::ConnectionPointer conn, Comm::PathsPointer paths, comm_err_t status, int xerrno, void *data)
+{
+ FwdState *fwd = (FwdState *) data;
+ fwd->connectDone(conn, paths, status, xerrno);
 }
 
 /**** PRIVATE *****************************************************************/
@@ -487,9 +475,12 @@
 void
 FwdState::serverClosed(int fd)
 {
- debugs(17, 2, "fwdServerClosed: FD " << fd << " " << entry->url());
- assert(server_fd == fd);
- server_fd = -1;
+ debugs(17, 2, HERE << "FD " << fd << " " << entry->url());
+ assert(paths[0]->fd == fd);
+
+ if (paths[0]->getPeer()) {
+ paths[0]->getPeer()->stats.conn_open--;
+ }
 
     retryOrBail();
 }
@@ -504,36 +495,26 @@
     }
 
     if (checkRetry()) {
- int originserver = (servers->_peer == NULL);
- debugs(17, 3, "fwdServerClosed: re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
-
- if (servers->next) {
- /* use next, or cycle if origin server isn't last */
- FwdServer *fs = servers;
- FwdServer **T, *T2 = NULL;
- servers = fs->next;
-
- for (T = &servers; *T; T2 = *T, T = &(*T)->next);
- if (T2 && T2->_peer) {
- /* cycle */
- *T = fs;
- fs->next = NULL;
- } else {
- /* Use next. The last "direct" entry is retried multiple times */
- servers = fs->next;
- fwdServerFree(fs);
- originserver = 0;
- }
+ debugs(17, 3, HERE << "re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
+
+ paths.shift(); // last one failed. try another.
+
+ if (paths.size() > 0) {
+ /* Ditch error page if it was created before.
+ * A new one will be created if there's another problem */
+ err = NULL;
+
+ AsyncCall::Pointer call = commCbCall(17,3,"fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper, this));
+ ConnectStateData *cs = new ConnectStateData(&paths, call);
+ cs->host = xstrdup(entry->url());
+ cs->connect_timeout = Config.Timeout.connect;
+ cs->connect();
+
+ /* use eventAdd to break potential call sequence loops and to slow things down a little */
+ eventAdd("fwdConnectStart", fwdConnectStartWrapper, this, (paths[0]->getPeer() == NULL) ? 0.05 : 0.005, 0);
+ return;
         }
-
- /* Ditch error page if it was created before.
- * A new one will be created if there's another problem */
- err = NULL;
-
- /* use eventAdd to break potential call sequence loops and to slow things down a little */
- eventAdd("fwdConnectStart", fwdConnectStartWrapper, this, originserver ? 0.05 : 0.005, 0);
-
- return;
+ // else bail. no more paths possible to try.
     }
 
     if (!err && shutting_down) {
@@ -547,9 +528,8 @@
 void
 FwdState::handleUnregisteredServerEnd()
 {
- debugs(17, 2, "handleUnregisteredServerEnd: self=" << self <<
- " err=" << err << ' ' << entry->url());
- assert(server_fd < 0);
+ debugs(17, 2, HERE << "self=" << self << " err=" << err << ' ' << entry->url());
+ assert(paths[0]->fd < 0);
     retryOrBail();
 }
 
@@ -557,7 +537,6 @@
 void
 FwdState::negotiateSSL(int fd)
 {
- FwdServer *fs = servers;
     SSL *ssl = fd_table[fd].ssl;
     int ret;
 
@@ -589,21 +568,21 @@
 
             fail(anErr);
 
- if (fs->_peer) {
- peerConnectFailed(fs->_peer);
- fs->_peer->stats.conn_open--;
+ if (paths[0]->getPeer()) {
+ peerConnectFailed(paths[0]->getPeer());
+ paths[0]->getPeer()->stats.conn_open--;
             }
 
- comm_close(fd);
+ paths[0]->close();
             return;
         }
     }
 
- if (fs->_peer && !SSL_session_reused(ssl)) {
- if (fs->_peer->sslSession)
- SSL_SESSION_free(fs->_peer->sslSession);
+ if (paths[0]->getPeer() && !SSL_session_reused(ssl)) {
+ if (paths[0]->getPeer()->sslSession)
+ SSL_SESSION_free(paths[0]->getPeer()->sslSession);
 
- fs->_peer->sslSession = SSL_get1_session(ssl);
+ paths[0]->getPeer()->sslSession = SSL_get1_session(ssl);
     }
 
     dispatch();
@@ -612,11 +591,10 @@
 void
 FwdState::initiateSSL()
 {
- FwdServer *fs = servers;
- int fd = server_fd;
     SSL *ssl;
     SSL_CTX *sslContext = NULL;
- peer *peer = fs->_peer;
+ const peer *peer = paths[0]->getPeer();
+ int fd = paths[0]->fd;
 
     if (peer) {
         assert(peer->use_ssl);
@@ -676,189 +654,170 @@
 #endif
 
 void
-FwdState::connectDone(int aServerFD, const DnsLookupDetails &dns, comm_err_t status, int xerrno)
+FwdState::connectDone(Comm::ConnectionPointer conn, Comm::PathsPointer result_paths, comm_err_t status, int xerrno)
 {
- FwdServer *fs = servers;
- assert(server_fd == aServerFD);
-
- request->recordLookup(dns);
-
- if (Config.onoff.log_ip_on_direct && status != COMM_ERR_DNS && fs->code == HIER_DIRECT)
- updateHierarchyInfo();
-
- if (status == COMM_ERR_DNS) {
- /*
- * Only set the dont_retry flag if the DNS lookup fails on
- * a direct connection. If DNS lookup fails when trying
- * a neighbor cache, we may want to retry another option.
- */
-
- if (NULL == fs->_peer)
- flags.dont_retry = 1;
-
- debugs(17, 4, "fwdConnectDone: Unknown host: " << request->GetHost());
-
- ErrorState *anErr = errorCon(ERR_DNS_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
-
- anErr->dnsError = dns.error;
-
- fail(anErr);
-
- comm_close(server_fd);
- } else if (status != COMM_OK) {
- assert(fs);
+ assert(result_paths == &paths);
+
+ if (status != COMM_OK) {
         ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
         anErr->xerrno = xerrno;
 
         fail(anErr);
 
- if (fs->_peer)
- peerConnectFailed(fs->_peer);
-
- comm_close(server_fd);
- } else {
- debugs(17, 3, "fwdConnectDone: FD " << server_fd << ": '" << entry->url() << "'" );
-
- if (fs->_peer)
- peerConnectSucceded(fs->_peer);
+ /* it might have been a timeout with a partially open link */
+ if (paths.size() > 0) {
+ if (paths[0]->getPeer())
+ peerConnectFailed(paths[0]->getPeer());
+
+ paths[0]->close();
+ }
+
+ return;
+ }
+
+#if REDUNDANT_NOW
+ if (Config.onoff.log_ip_on_direct && paths[0]->peer_type == HIER_DIRECT)
+ updateHierarchyInfo();
+#endif
+
+ debugs(17, 3, "FD " << paths[0]->fd << ": '" << entry->url() << "'" );
+
+ comm_add_close_handler(paths[0]->fd, fwdServerClosedWrapper, this);
+
+ if (paths[0]->getPeer())
+ peerConnectSucceded(paths[0]->getPeer());
+
+ // TODO: Avoid this if %<lp is not used? F->local_port is often cached.
+ request->hier.peer_local_port = comm_local_port(paths[0]->fd);
+
+ updateHierarchyInfo();
 
 #if USE_SSL
-
- if ((fs->_peer && fs->_peer->use_ssl) ||
- (!fs->_peer && request->protocol == PROTO_HTTPS)) {
- initiateSSL();
- return;
- }
-
+ if ((paths[0]->getPeer() && paths[0]->getPeer()->use_ssl) ||
+ (!paths[0]->getPeer() && request->protocol == PROTO_HTTPS)) {
+ initiateSSL();
+ return;
+ }
 #endif
- dispatch();
- }
+
+ dispatch();
 }
 
 void
 FwdState::connectTimeout(int fd)
 {
- FwdServer *fs = servers;
-
     debugs(17, 2, "fwdConnectTimeout: FD " << fd << ": '" << entry->url() << "'" );
- assert(fd == server_fd);
+ assert(fd == paths[0]->fd);
 
- if (Config.onoff.log_ip_on_direct && fs->code == HIER_DIRECT && fd_table[fd].ipaddr[0])
+ if (Config.onoff.log_ip_on_direct && paths[0]->peer_type == HIER_DIRECT)
         updateHierarchyInfo();
 
     if (entry->isEmpty()) {
         ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_GATEWAY_TIMEOUT, request);
         anErr->xerrno = ETIMEDOUT;
         fail(anErr);
- /*
- * This marks the peer DOWN ...
- */
 
- if (servers)
- if (servers->_peer)
- peerConnectFailed(servers->_peer);
+ /* This marks the peer DOWN ... */
+ if (paths.size() > 0)
+ if (paths[0]->getPeer())
+ peerConnectFailed(paths[0]->getPeer());
     }
 
- comm_close(fd);
+ paths[0]->close();
 }
 
+/**
+ * Called after Forwarding path selection (via peer select) has taken place
+ * We have a vector of possible paths now ready to start being connected.
+ */
 void
 FwdState::connectStart()
 {
- const char *url = entry->url();
- int fd = -1;
- FwdServer *fs = servers;
- const char *host;
- unsigned short port;
- int ctimeout;
- int ftimeout = Config.Timeout.forward - (squid_curtime - start_t);
-
- Ip::Address outgoing;
- unsigned short tos;
- Ip::Address client_addr;
- assert(fs);
- assert(server_fd == -1);
- debugs(17, 3, "fwdConnectStart: " << url);
+ debugs(17, 3, "fwdConnectStart: " << entry->url());
 
     if (n_tries == 0) // first attempt
         request->hier.first_conn_start = current_time;
 
- if (fs->_peer) {
- ctimeout = fs->_peer->connect_timeout > 0 ? fs->_peer->connect_timeout
- : Config.Timeout.peer_connect;
+ Comm::ConnectionPointer conn = paths[0];
+
+ /* connection timeout */
+ int ctimeout;
+ if (conn->getPeer()) {
+ ctimeout = conn->getPeer()->connect_timeout > 0 ? conn->getPeer()->connect_timeout : Config.Timeout.peer_connect;
     } else {
         ctimeout = Config.Timeout.connect;
     }
 
- if (request->flags.spoof_client_ip) {
- if (!fs->_peer || !fs->_peer->options.no_tproxy)
- client_addr = request->client_addr;
- // else no tproxy today ...
- }
-
+ /* calculate total forwarding timeout ??? */
+ int ftimeout = Config.Timeout.forward - (squid_curtime - start_t);
     if (ftimeout < 0)
         ftimeout = 5;
 
     if (ftimeout < ctimeout)
         ctimeout = ftimeout;
 
-
     request->flags.pinned = 0;
- if (fs->code == PINNED) {
+ if (conn->peer_type == PINNED) {
         ConnStateData *pinned_connection = request->pinnedConnection();
         assert(pinned_connection);
- fd = pinned_connection->validatePinnedConnection(request, fs->_peer);
- if (fd >= 0) {
+ conn->fd = pinned_connection->validatePinnedConnection(request, conn->getPeer());
+ if (conn->isOpen()) {
             pinned_connection->unpinConnection();
 #if 0
- if (!fs->_peer)
- fs->code = HIER_DIRECT;
+ if (!conn->getPeer())
+ conn->peer_type = HIER_DIRECT;
 #endif
- server_fd = fd;
             n_tries++;
             request->flags.pinned = 1;
             if (pinned_connection->pinnedAuth())
                 request->flags.auth = 1;
- comm_add_close_handler(fd, fwdServerClosedWrapper, this);
             updateHierarchyInfo();
- connectDone(fd, DnsLookupDetails(), COMM_OK, 0);
+ FwdState::connectDone(conn, &paths, COMM_OK, 0);
             return;
         }
         /* Failure. Fall back on next path */
         debugs(17,2,HERE << " Pinned connection " << pinned_connection << " not valid. Releasing.");
         request->releasePinnedConnection();
- servers = fs->next;
- fwdServerFree(fs);
+ paths.shift();
+ conn = NULL; // maybe release the conn memory. it's not needed by us anyway.
         connectStart();
         return;
     }
 
- if (fs->_peer) {
- host = fs->_peer->host;
- port = fs->_peer->http_port;
- fd = fwdPconnPool->pop(fs->_peer->name, fs->_peer->http_port, request->GetHost(), client_addr, checkRetriable());
+// TODO: now that we are dealing with actual IP->IP links. should we still anchor pconn on hostname?
+// or on the remote IP+port?
+// that could reduce the pconns per virtual server a fair amount
+// but would prevent crossover between servers hosting the one domain
+// this currently opens the possibility that conn will lie about where the FD goes.
+
+ const char *host;
+ int port;
+ if (conn->getPeer()) {
+ host = conn->getPeer()->host;
+ port = conn->getPeer()->http_port;
+ conn->fd = fwdPconnPool->pop(conn->getPeer()->name, conn->getPeer()->http_port, request->GetHost(), conn->local, checkRetriable());
     } else {
         host = request->GetHost();
         port = request->port;
- fd = fwdPconnPool->pop(host, port, NULL, client_addr, checkRetriable());
+ conn->fd = fwdPconnPool->pop(host, port, NULL, conn->local, checkRetriable());
     }
- if (fd >= 0) {
- debugs(17, 3, "fwdConnectStart: reusing pconn FD " << fd);
- server_fd = fd;
+ conn->remote.SetPort(port);
+
+ if (conn->isOpen()) {
+ debugs(17, 3, HERE << "reusing pconn FD " << conn->fd);
         n_tries++;
 
- if (!fs->_peer)
+ if (!conn->getPeer())
             origin_tries++;
 
         updateHierarchyInfo();
 
- comm_add_close_handler(fd, fwdServerClosedWrapper, this);
+ comm_add_close_handler(conn->fd, fwdServerClosedWrapper, this);
 
         // TODO: Avoid this if %<lp is not used? F->local_port is often cached.
- request->hier.peer_local_port = comm_local_port(fd);
+ request->hier.peer_local_port = comm_local_port(conn->fd);
 
         dispatch();
-
         return;
     }
 
@@ -866,98 +825,27 @@
     entry->mem_obj->checkUrlChecksum();
 #endif
 
- outgoing = getOutgoingAddr(request, fs->_peer);
-
- tos = getOutgoingTOS(request);
-
- debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing << ", tos " << tos);
-
- int commFlags = COMM_NONBLOCKING;
- if (request->flags.spoof_client_ip) {
- if (!fs->_peer || !fs->_peer->options.no_tproxy)
- commFlags |= COMM_TRANSPARENT;
- // else no tproxy today ...
- }
-
- fd = comm_openex(SOCK_STREAM, IPPROTO_TCP, outgoing, commFlags, tos, url);
-
- debugs(17, 3, "fwdConnectStart: got TCP FD " << fd);
-
- if (fd < 0) {
- debugs(50, 4, "fwdConnectStart: " << xstrerror());
- ErrorState *anErr = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
- anErr->xerrno = errno;
- fail(anErr);
- self = NULL; // refcounted
- return;
- }
-
- server_fd = fd;
- n_tries++;
-
- if (!fs->_peer)
- origin_tries++;
-
- request->hier.peer_local_port = comm_local_port(fd);
-
- /*
- * stats.conn_open is used to account for the number of
- * connections that we have open to the peer, so we can limit
- * based on the max-conn option. We need to increment here,
- * even if the connection may fail.
- */
-
- if (fs->_peer) {
- fs->_peer->stats.conn_open++;
- comm_add_close_handler(fd, fwdPeerClosed, fs->_peer);
- }
-
- comm_add_close_handler(fd, fwdServerClosedWrapper, this);
-
- commSetTimeout(fd, ctimeout, fwdConnectTimeoutWrapper, this);
-
- updateHierarchyInfo();
- commConnectStart(fd, host, port, fwdConnectDoneWrapper, this);
-}
-
-void
-FwdState::startComplete(FwdServer * theServers)
-{
- debugs(17, 3, "fwdStartComplete: " << entry->url() );
-
- if (theServers != NULL) {
- servers = theServers;
- connectStart();
- } else {
- startFail();
- }
-}
-
-void
-FwdState::startFail()
-{
- debugs(17, 3, "fwdStartFail: " << entry->url() );
- ErrorState *anErr = errorCon(ERR_CANNOT_FORWARD, HTTP_SERVICE_UNAVAILABLE, request);
- anErr->xerrno = errno;
- fail(anErr);
- self = NULL; // refcounted
+ AsyncCall::Pointer call = commCbCall(17,3, "fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper, this));
+ ConnectStateData *cs = new ConnectStateData(&paths, call);
+ cs->host = xstrdup(host);
+ cs->connect_timeout = ctimeout;
+ cs->connect();
 }
 
 void
 FwdState::dispatch()
 {
- peer *p = NULL;
     debugs(17, 3, "fwdDispatch: FD " << client_fd << ": Fetching '" << RequestMethodStr(request->method) << " " << entry->url() << "'" );
     /*
      * Assert that server_fd is set. This is to guarantee that fwdState
      * is attached to something and will be deallocated when server_fd
      * is closed.
      */
- assert(server_fd > -1);
-
- fd_note(server_fd, entry->url());
-
- fd_table[server_fd].noteUse(fwdPconnPool);
+ assert(paths.size() > 0 && paths[0]->fd > -1);
+
+ fd_note(paths[0]->fd, entry->url());
+
+ fd_table[paths[0]->fd].noteUse(fwdPconnPool);
 
     /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */
     assert(entry->ping_status != PING_WAITING);
@@ -980,10 +868,10 @@
         int tos = 1;
         int tos_len = sizeof(tos);
         clientFde->upstreamTOS = 0;
- if (setsockopt(server_fd,SOL_IP,IP_RECVTOS,&tos,tos_len)==0) {
+ if (setsockopt(paths[0]->fd,SOL_IP,IP_RECVTOS,&tos,tos_len)==0) {
             unsigned char buf[512];
             int len = 512;
- if (getsockopt(server_fd,SOL_IP,IP_PKTOPTIONS,buf,(socklen_t*)&len) == 0) {
+ if (getsockopt(paths[0]->fd,SOL_IP,IP_PKTOPTIONS,buf,(socklen_t*)&len) == 0) {
                 /* Parse the PKTOPTIONS structure to locate the TOS data message
                  * prepared in the kernel by the ZPH incoming TCP TOS preserving
                  * patch.
@@ -1002,18 +890,18 @@
                     pbuf += CMSG_LEN(o->cmsg_len);
                 }
             } else {
- debugs(33, 1, "ZPH: error in getsockopt(IP_PKTOPTIONS) on FD "<<server_fd<<" "<<xstrerror());
+ debugs(33, DBG_IMPORTANT, "ZPH: error in getsockopt(IP_PKTOPTIONS) on FD " << paths[0]->fd << " " << xstrerror());
             }
         } else {
- debugs(33, 1, "ZPH: error in setsockopt(IP_RECVTOS) on FD "<<server_fd<<" "<<xstrerror());
+ debugs(33, DBG_IMPORTANT, "ZPH: error in setsockopt(IP_RECVTOS) on FD " << paths[0]->fd << " " << xstrerror());
         }
     }
 #endif
 
- if (servers && (p = servers->_peer)) {
- p->stats.fetches++;
- request->peer_login = p->login;
- request->peer_domain = p->domain;
+ if (paths.size() > 0 && paths[0]->getPeer() != NULL) {
+ paths[0]->getPeer()->stats.fetches++;
+ request->peer_login = paths[0]->getPeer()->login;
+ request->peer_domain = paths[0]->getPeer()->domain;
         httpStart(this);
     } else {
         request->peer_login = NULL;
@@ -1068,7 +956,7 @@
              * transient (network) error; its a bug.
              */
             flags.dont_retry = 1;
- comm_close(server_fd);
+ paths[0]->close();
             break;
         }
     }
@@ -1086,7 +974,6 @@
 FwdState::reforward()
 {
     StoreEntry *e = entry;
- FwdServer *fs = servers;
     http_status s;
     assert(e->store_status == STORE_PENDING);
     assert(e->mem_obj);
@@ -1095,10 +982,10 @@
     e->mem_obj->checkUrlChecksum();
 #endif
 
- debugs(17, 3, "fwdReforward: " << e->url() << "?" );
+ debugs(17, 3, HERE << e->url() << "?" );
 
     if (!EBIT_TEST(e->flags, ENTRY_FWD_HDR_WAIT)) {
- debugs(17, 3, "fwdReforward: No, ENTRY_FWD_HDR_WAIT isn't set");
+ debugs(17, 3, HERE << "No, ENTRY_FWD_HDR_WAIT isn't set");
         return 0;
     }
 
@@ -1111,19 +998,15 @@
     if (request->bodyNibbled())
         return 0;
 
- assert(fs);
-
- servers = fs->next;
-
- fwdServerFree(fs);
-
- if (servers == NULL) {
- debugs(17, 3, "fwdReforward: No forward-servers left");
+ paths.shift();
+
+ if (paths.size() > 0) {
+ debugs(17, 3, HERE << "No alternative forwarding paths left");
         return 0;
     }
 
     s = e->getReply()->sline.status;
- debugs(17, 3, "fwdReforward: status " << s);
+ debugs(17, 3, HERE << "status " << s);
     return reforwardableStatus(s);
 }
 
@@ -1192,22 +1075,26 @@
  * - address of the client for which we made the connection
  */
 void
-FwdState::pconnPush(int fd, const peer *_peer, const HttpRequest *req, const char *domain, Ip::Address &client_addr)
+FwdState::pconnPush(Comm::ConnectionPointer conn, const peer *_peer, const HttpRequest *req, const char *domain, Ip::Address &client_addr)
 {
     if (_peer) {
- fwdPconnPool->push(fd, _peer->name, _peer->http_port, domain, client_addr);
+ fwdPconnPool->push(conn->fd, _peer->name, _peer->http_port, domain, client_addr);
     } else {
         /* small performance improvement, using NULL for domain instead of listing it twice */
         /* although this will leave a gap open for url-rewritten domains to share a link */
- fwdPconnPool->push(fd, req->GetHost(), req->port, NULL, client_addr);
+ fwdPconnPool->push(conn->fd, req->GetHost(), req->port, NULL, client_addr);
     }
+
+ /* XXX: remove this when Comm::Connection are stored in the pool
+ * this only prevents the persistent FD being closed when the
+ * Comm::Connection currently using it is destroyed.
+ */
+ conn->fd = -1;
 }
 
 void
 FwdState::initModule()
 {
- memDataInit(MEM_FWD_SERVER, "FwdServer", sizeof(FwdServer), 0);
-
 #if WIP_FWD_LOG
 
     if (logfile)
@@ -1235,9 +1122,7 @@
     if (status > HTTP_INVALID_HEADER)
         return;
 
- assert(tries);
-
- tries--;
+ assert(tries >= 0);
 
     if (tries > MAX_FWD_STATS_IDX)
         tries = MAX_FWD_STATS_IDX;
@@ -1245,17 +1130,6 @@
     FwdReplyCodes[tries][status]++;
 }
 
-void
-FwdState::serversFree(FwdServer ** FSVR)
-{
- FwdServer *fs;
-
- while ((fs = *FSVR)) {
- *FSVR = fs->next;
- fwdServerFree(fs);
- }
-}
-
 /** From Comment #5 by Henrik Nordstrom made at
 http://www.squid-cache.org/bugs/show_bug.cgi?id=2391 on 2008-09-19
 
@@ -1274,54 +1148,28 @@
 {
     assert(request);
 
- FwdServer *fs = servers;
- assert(fs);
-
- const char *nextHop = NULL;
-
- if (fs->_peer) {
+ assert(paths.size() > 0);
+
+ char nextHop[256]; //
+
+ if (paths[0]->getPeer()) {
         // went to peer, log peer host name
- nextHop = fs->_peer->name;
+ snprintf(nextHop,256,"%s", paths[0]->getPeer()->name);
     } else {
         // went DIRECT, must honor log_ip_on_direct
-
- // XXX: or should we use request->host_addr here? how?
- assert(server_fd >= 0);
- nextHop = fd_table[server_fd].ipaddr;
- if (!Config.onoff.log_ip_on_direct || !nextHop[0])
- nextHop = request->GetHost(); // domain name
+ if (!Config.onoff.log_ip_on_direct)
+ snprintf(nextHop,256,"%s",request->GetHost()); // domain name
+ else
+ paths[0]->remote.NtoA(nextHop, 256);
     }
 
- assert(nextHop);
- hierarchyNote(&request->hier, fs->code, nextHop);
+ assert(nextHop[0]);
+ hierarchyNote(&request->hier, paths[0]->peer_type, nextHop);
 }
 
 
 /**** PRIVATE NON-MEMBER FUNCTIONS ********************************************/
 
-static void
-fwdServerFree(FwdServer * fs)
-{
- cbdataReferenceDone(fs->_peer);
- memFree(fs, MEM_FWD_SERVER);
-}
-
-static Ip::Address
-aclMapAddr(acl_address * head, ACLChecklist * ch)
-{
- acl_address *l;
-
- Ip::Address addr;
-
- for (l = head; l; l = l->next) {
- if (!l->aclList || ch->matchAclListFast(l->aclList))
- return l->addr;
- }
-
- addr.SetAnyAddr();
- return addr;
-}
-
 /*
  * DPW 2007-05-19
  * Formerly static, but now used by client_side_request.cc
@@ -1339,27 +1187,39 @@
     return 0;
 }
 
-Ip::Address
-getOutgoingAddr(HttpRequest * request, struct peer *dst_peer)
+void
+getOutgoingAddress(HttpRequest * request, Comm::ConnectionPointer conn)
 {
+ /* skip if an outgoing address is already set. */
+ if (!conn->local.IsAnyAddr()) return;
+
+ // maybe use TPROXY client address
     if (request && request->flags.spoof_client_ip) {
- if (!dst_peer || !dst_peer->options.no_tproxy) {
+ if (!conn->getPeer() || !conn->getPeer()->options.no_tproxy) {
 #if FOLLOW_X_FORWARDED_FOR && LINUX_NETFILTER
             if (Config.onoff.tproxy_uses_indirect_client)
- return request->indirect_client_addr;
+ conn->local = request->indirect_client_addr;
             else
 #endif
- return request->client_addr;
+ conn->local = request->client_addr;
+ // some flags need setting on the socket to use this address
+ conn->flags |= COMM_DOBIND;
+ conn->flags |= COMM_TRANSPARENT;
+ return;
         }
         // else no tproxy today ...
     }
 
     if (!Config.accessList.outgoing_address) {
- return Ip::Address(); // anything will do.
+ return; // anything will do.
     }
 
     ACLFilledChecklist ch(NULL, request, NULL);
- ch.dst_peer = dst_peer;
+ ch.dst_peer = conn->getPeer();
+ ch.dst_addr = conn->remote;
+
+ // TODO use the connection details in ACL.
+ // needs a bit of rework in ACLFilledChecklist to use Comm::Connection instead of ConnStateData
 
     if (request) {
 #if FOLLOW_X_FORWARDED_FOR
@@ -1371,7 +1231,18 @@
         ch.my_addr = request->my_addr;
     }
 
- return aclMapAddr(Config.accessList.outgoing_address, &ch);
+ acl_address *l;
+ for (l = Config.accessList.outgoing_address; l; l = l->next) {
+
+ /* check if the outgoing address is usable to the destination */
+ if (conn->remote.IsIPv4() != l->addr.IsIPv4()) continue;
+
+ /* check ACLs for this outgoing address */
+ if (!l->aclList || ch.matchAclListFast(l->aclList)) {
+ conn->local = l->addr;
+ return;
+ }
+ }
 }
 
 unsigned long

=== modified file 'src/forward.h'
--- src/forward.h 2010-05-02 19:32:42 +0000
+++ src/forward.h 2010-06-08 14:03:24 +0000
@@ -7,16 +7,9 @@
 class HttpRequest;
 
 #include "comm.h"
-#include "hier_code.h"
+#include "comm/Connection.h"
 #include "ip/Address.h"
-
-class FwdServer
-{
-public:
- peer *_peer; /* NULL --> origin server */
- hier_code code;
- FwdServer *next;
-};
+#include "Array.h"
 
 class FwdState : public RefCountable
 {
@@ -26,8 +19,7 @@
     static void initModule();
 
     static void fwdStart(int fd, StoreEntry *, HttpRequest *);
- void startComplete(FwdServer *);
- void startFail();
+ void startComplete();
     void fail(ErrorState *err);
     void unregister(int fd);
     void complete();
@@ -36,14 +28,14 @@
     bool reforwardableStatus(http_status s);
     void serverClosed(int fd);
     void connectStart();
- void connectDone(int server_fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno);
+ void connectDone(Comm::ConnectionPointer conn, Comm::PathsPointer paths, comm_err_t status, int xerrno);
     void connectTimeout(int fd);
     void initiateSSL();
     void negotiateSSL(int fd);
     bool checkRetry();
     bool checkRetriable();
     void dispatch();
- void pconnPush(int fd, const peer *_peer, const HttpRequest *req, const char *domain, Ip::Address &client_addr);
+ void pconnPush(Comm::ConnectionPointer conn, const peer *_peer, const HttpRequest *req, const char *domain, Ip::Address &client_addr);
 
     bool dontRetry() { return flags.dont_retry; }
 
@@ -53,7 +45,7 @@
 
     void ftpPasvFailed(bool val) { flags.ftp_pasv_failed = val; }
 
- static void serversFree(FwdServer **);
+ Comm::ConnectionPointer conn() const { return paths[0]; };
 
 private:
     // hidden for safer management of self; use static fwdStart
@@ -76,8 +68,6 @@
 public:
     StoreEntry *entry;
     HttpRequest *request;
- int server_fd;
- FwdServer *servers;
     static void abort(void*);
 
 private:
@@ -98,7 +88,8 @@
         unsigned int forward_completed:1;
     } flags;
 
- Ip::Address src; /* Client address for this connection. Needed for transparent operations. */
+ /** possible paths which may be tried (in sequence stored) */
+ Comm::Paths paths;
 
     // NP: keep this last. It plays with private/public
     CBDATA_CLASS2(FwdState);

=== modified file 'src/fqdncache.cc'
--- src/fqdncache.cc 2010-06-01 00:12:34 +0000
+++ src/fqdncache.cc 2010-06-03 07:18:25 +0000
@@ -34,6 +34,7 @@
 
 #include "squid.h"
 #include "cbdata.h"
+#include "DnsLookupDetails.h"
 #include "event.h"
 #include "CacheManager.h"
 #include "SquidTime.h"

=== modified file 'src/ftp.cc'
--- src/ftp.cc 2010-05-27 01:56:23 +0000
+++ src/ftp.cc 2010-06-09 09:50:31 +0000
@@ -34,9 +34,9 @@
 
 #include "squid.h"
 #include "comm.h"
+#include "comm/ConnectStateData.h"
 #include "comm/ListenStateData.h"
 #include "compat/strtoll.h"
-#include "ConnectionDetail.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "forward.h"
@@ -480,7 +480,7 @@
     typedef CommCbMemFunT<FtpStateData, CommCloseCbParams> Dialer;
     AsyncCall::Pointer closer = asyncCall(9, 5, "FtpStateData::ctrlClosed",
                                           Dialer(this, &FtpStateData::ctrlClosed));
- ctrl.opened(theFwdState->server_fd, closer);
+ ctrl.opened(theFwdState->conn()->fd, closer);
 
     if (request->method == METHOD_PUT)
         flags.put = 1;
@@ -2412,7 +2412,15 @@
 
     debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
 
- commConnectStart(fd, ftpState->data.host, port, FtpStateData::ftpPasvCallback, ftpState);
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ conn->remote = fd_table[ftpState->ctrl.fd].ipaddr; // TODO: do we have a better info source than fd_table?
+ conn->remote.SetPort(port);
+ conn->fd = fd;
+
+ AsyncCall::Pointer call = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
+ ConnectStateData *cs = new ConnectStateData(conn, call);
+ cs->host = xstrdup(fd_table[ftpState->ctrl.fd].ipaddr);
+ cs->connect();
 }
 
 /** \ingroup ServerProtocolFTPInternal
@@ -2545,10 +2553,11 @@
 
     /** Otherwise, Open data channel with the same local address as control channel (on a new random port!) */
     addr.SetPort(0);
- int fd = comm_open(SOCK_STREAM,
+ int fd = comm_openex(SOCK_STREAM,
                        IPPROTO_TCP,
                        addr,
                        COMM_NONBLOCKING,
+ 0,
                        ftpState->entry->url());
 
     debugs(9, 3, HERE << "Unconnected data socket created on FD " << fd << " from " << addr);
@@ -2610,7 +2619,6 @@
     int n;
     u_short port;
     Ip::Address ipa_remote;
- int fd = ftpState->data.fd;
     char *buf;
     LOCAL_ARRAY(char, ipaddr, 1024);
     debugs(9, 3, HERE);
@@ -2688,15 +2696,24 @@
 
     debugs(9, 3, HERE << "connecting to " << ftpState->data.host << ", port " << ftpState->data.port);
 
- commConnectStart(fd, ipaddr, port, FtpStateData::ftpPasvCallback, ftpState);
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ conn->remote = ipaddr;
+ conn->remote.SetPort(port);
+ conn->fd = ftpState->data.fd;
+
+ AsyncCall::Pointer call = commCbCall(9,3, "FtpStateData::ftpPasvCallback", CommConnectCbPtrFun(FtpStateData::ftpPasvCallback, ftpState));
+ ConnectStateData *cs = new ConnectStateData(conn, call);
+ cs->host = xstrdup(ftpState->data.host);
+ cs->connect_timeout = Config.Timeout.connect;
+ cs->connect();
 }
 
 void
-FtpStateData::ftpPasvCallback(int fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
+FtpStateData::ftpPasvCallback(Comm::ConnectionPointer conn, Comm::PathsPointer unused, comm_err_t status, int xerrno, void *data)
 {
     FtpStateData *ftpState = (FtpStateData *)data;
     debugs(9, 3, HERE);
- ftpState->request->recordLookup(dns);
+// TODO: dead? ftpState->request->recordLookup(dns);
 
     if (status != COMM_OK) {
         debugs(9, 2, HERE << "Failed to connect. Retrying without PASV.");
@@ -2937,16 +2954,17 @@
      * This prevents third-party hacks, but also third-party load balancing handshakes.
      */
     if (Config.Ftp.sanitycheck) {
- io.details.peer.NtoA(ntoapeer,MAX_IPSTRLEN);
+ io.details->remote.NtoA(ntoapeer,MAX_IPSTRLEN);
 
         if (strcmp(fd_table[ctrl.fd].ipaddr, ntoapeer) != 0) {
             debugs(9, DBG_IMPORTANT,
                    "FTP data connection from unexpected server (" <<
- io.details.peer << "), expecting " <<
+ io.details->remote << "), expecting " <<
                    fd_table[ctrl.fd].ipaddr);
 
- /* close the bad soures connection down ASAP. */
- comm_close(io.nfd);
+ /* close the bad sources connection down ASAP. */
+ Comm::ConnectionPointer nonConst = io.details;
+ nonConst->close();
 
             /* we are ony accepting once, so need to re-open the listener socket. */
             typedef CommCbMemFunT<FtpStateData, CommAcceptCbParams> acceptDialer;
@@ -2968,11 +2986,11 @@
      * Replace the Listen socket with the accepted data socket */
     data.close();
     data.opened(io.nfd, dataCloser());
- data.port = io.details.peer.GetPort();
- io.details.peer.NtoA(data.host,SQUIDHOSTNAMELEN);
+ data.port = io.details->remote.GetPort();
+ io.details->remote.NtoA(data.host,SQUIDHOSTNAMELEN);
 
     debugs(9, 3, "ftpAcceptDataConnection: Connected data socket on " <<
- "FD " << io.nfd << " to " << io.details.peer << " FD table says: " <<
+ "FD " << io.nfd << " to " << io.details->remote << " FD table says: " <<
            "ctrl-peer= " << fd_table[ctrl.fd].ipaddr << ", " <<
            "data-peer= " << fd_table[data.fd].ipaddr);
 

=== modified file 'src/gopher.cc'
--- src/gopher.cc 2010-02-06 06:32:11 +0000
+++ src/gopher.cc 2010-06-09 09:50:31 +0000
@@ -990,7 +990,6 @@
 void
 gopherStart(FwdState * fwd)
 {
- int fd = fwd->server_fd;
     StoreEntry *entry = fwd->entry;
     GopherStateData *gopherState;
     CBDATA_INIT_TYPE(GopherStateData);
@@ -1012,7 +1011,7 @@
     gopher_request_parse(fwd->request,
                          &gopherState->type_id, gopherState->request);
 
- comm_add_close_handler(fd, gopherStateFree, gopherState);
+ comm_add_close_handler(fwd->conn()->fd, gopherStateFree, gopherState);
 
     if (((gopherState->type_id == GOPHER_INDEX) || (gopherState->type_id == GOPHER_CSO))
             && (strchr(gopherState->request, '?') == NULL)) {
@@ -1032,12 +1031,12 @@
 
         gopherToHTML(gopherState, (char *) NULL, 0);
         fwd->complete();
- comm_close(fd);
+ fwd->conn()->close();
         return;
     }
 
- gopherState->fd = fd;
+ gopherState->fd = fwd->conn()->fd; // TODO: save the conn() in gopher instead of the FD
     gopherState->fwd = fwd;
- gopherSendRequest(fd, gopherState);
- commSetTimeout(fd, Config.Timeout.read, gopherTimeout, gopherState);
+ gopherSendRequest(fwd->conn()->fd, gopherState);
+ commSetTimeout(fwd->conn()->fd, Config.Timeout.read, gopherTimeout, gopherState);
 }

=== modified file 'src/http.cc'
--- src/http.cc 2010-05-27 01:56:23 +0000
+++ src/http.cc 2010-06-03 07:18:25 +0000
@@ -86,7 +86,7 @@
     debugs(11,5,HERE << "HttpStateData " << this << " created");
     ignoreCacheControl = false;
     surrogateNoStore = false;
- fd = fwd->server_fd;
+ fd = fwd->conn()->fd; // TODO: store Comm::Connection instead of FD
     readBuf = new MemBuf;
     readBuf->init();
     orig_request = HTTPMSGLOCK(fwd->request);
@@ -95,8 +95,8 @@
     orig_request->hier.peer_http_request_sent.tv_sec = 0;
     orig_request->hier.peer_http_request_sent.tv_usec = 0;
 
- if (fwd->servers)
- _peer = fwd->servers->_peer; /* might be NULL */
+ if (fwd->conn() != NULL)
+ _peer = cbdataReference(fwd->conn()->getPeer()); /* might be NULL */
 
     if (_peer) {
         const char *url;
@@ -106,8 +106,7 @@
         else
             url = entry->url();
 
- HttpRequest * proxy_req = new HttpRequest(orig_request->method,
- orig_request->protocol, url);
+ HttpRequest * proxy_req = new HttpRequest(orig_request->method, orig_request->protocol, url);
 
         proxy_req->SetHost(_peer->host);
 
@@ -1371,7 +1370,7 @@
                 orig_request->pinnedConnection()->pinConnection(fd, orig_request, _peer,
                         (request->flags.connection_auth != 0));
             } else {
- fwd->pconnPush(fd, _peer, request, orig_request->GetHost(), client_addr);
+ fwd->pconnPush(fwd->conn(), _peer, request, orig_request->GetHost(), client_addr);
             }
 
             fd = -1;

=== modified file 'src/icp_v2.cc'
--- src/icp_v2.cc 2010-05-02 19:32:42 +0000
+++ src/icp_v2.cc 2010-06-08 14:03:24 +0000
@@ -36,19 +36,19 @@
  */
 
 #include "squid.h"
-#include "Store.h"
-#include "comm.h"
-#include "ICP.h"
+#include "AccessLogEntry.h"
+#include "acl/Acl.h"
+#include "acl/FilledChecklist.h"
+#include "comm/Connection.h"
 #include "HttpRequest.h"
-#include "acl/FilledChecklist.h"
-#include "acl/Acl.h"
-#include "AccessLogEntry.h"
-#include "wordlist.h"
-#include "SquidTime.h"
-#include "SwapDir.h"
 #include "icmp/net_db.h"
+#include "ICP.h"
 #include "ip/Address.h"
 #include "rfc1738.h"
+#include "Store.h"
+#include "SquidTime.h"
+#include "SwapDir.h"
+#include "wordlist.h"
 
 /// \ingroup ServerProtocolICPInternal2
 static void icpLogIcp(const Ip::Address &, log_type, int, const char *, int);

=== modified file 'src/ident/AclIdent.cc'
--- src/ident/AclIdent.cc 2009-06-02 15:37:40 +0000
+++ src/ident/AclIdent.cc 2010-06-08 14:03:24 +0000
@@ -42,6 +42,7 @@
 #include "acl/RegexData.h"
 #include "acl/UserData.h"
 #include "client_side.h"
+#include "comm/Connection.h"
 #include "ident/AclIdent.h"
 #include "ident/Ident.h"
 
@@ -129,7 +130,11 @@
     if (checklist->conn() != NULL) {
         debugs(28, 3, HERE << "Doing ident lookup" );
         checklist->asyncInProgress(true);
- Ident::Start(checklist->conn()->me, checklist->conn()->peer, LookupDone, checklist);
+ // TODO: store a Comm::Connection in either checklist or ConnStateData one day.
+ Comm::Connection cc; // IDENT will clone it's own copy for alterations.
+ cc.local = checklist->conn()->me;
+ cc.remote = checklist->conn()->peer;
+ Ident::Start(&cc, LookupDone, checklist);
     } else {
         debugs(28, DBG_IMPORTANT, "IdentLookup::checkForAsync: Can't start ident lookup. No client connection" );
         checklist->currentAnswer(ACCESS_DENIED);

=== modified file 'src/ident/Ident.cc'
--- src/ident/Ident.cc 2010-04-17 02:29:04 +0000
+++ src/ident/Ident.cc 2010-06-09 09:50:31 +0000
@@ -37,6 +37,9 @@
 #if USE_IDENT
 
 #include "comm.h"
+#include "comm/Connection.h"
+#include "comm/ConnectStateData.h"
+#include "CommCalls.h"
 #include "ident/Config.h"
 #include "ident/Ident.h"
 #include "MemBuf.h"
@@ -56,10 +59,7 @@
 
 typedef struct _IdentStateData {
     hash_link hash; /* must be first */
- int fd; /* IDENT fd */
-
- Ip::Address me;
- Ip::Address my_peer;
+ Comm::Connection conn;
     IdentClient *clients;
     char buf[4096];
 } IdentStateData;
@@ -103,7 +103,7 @@
 {
     IdentStateData *state = (IdentStateData *)data;
     identCallback(state, NULL);
- comm_close(state->fd);
+ state->conn.close();
     hash_remove_link(ident_hash, (hash_link *) state);
     xfree(state->hash.key);
     cbdataFree(state);
@@ -113,26 +113,28 @@
 Ident::Timeout(int fd, void *data)
 {
     IdentStateData *state = (IdentStateData *)data;
- debugs(30, 3, "identTimeout: FD " << fd << ", " << state->my_peer);
-
- comm_close(fd);
+ debugs(30, 3, HERE << "FD " << fd << ", " << state->conn.remote);
+ state->conn.close();
 }
 
 void
-Ident::ConnectDone(int fd, const DnsLookupDetails &, comm_err_t status, int xerrno, void *data)
+Ident::ConnectDone(Comm::ConnectionPointer conn, Comm::PathsPointer unused, comm_err_t status, int xerrno, void *data)
 {
     IdentStateData *state = (IdentStateData *)data;
- IdentClient *c;
 
     if (status != COMM_OK) {
- /* Failed to connect */
- comm_close(fd);
+ if (status == COMM_TIMEOUT) {
+ debugs(30, 3, "IDENT connection timeout to " << state->conn.remote);
+ }
         return;
     }
 
+ assert(conn != NULL && conn == &(state->conn));
+
     /*
      * see if any of our clients still care
      */
+ IdentClient *c;
     for (c = state->clients; c; c = c->next) {
         if (cbdataReferenceValid(c->callback_data))
             break;
@@ -140,18 +142,20 @@
 
     if (c == NULL) {
         /* no clients care */
- comm_close(fd);
+ conn->close();
         return;
     }
 
+ comm_add_close_handler(conn->fd, Ident::Close, state);
+
     MemBuf mb;
     mb.init();
     mb.Printf("%d, %d\r\n",
- state->my_peer.GetPort(),
- state->me.GetPort());
- comm_write_mbuf(fd, &mb, NULL, state);
- comm_read(fd, state->buf, BUFSIZ, Ident::ReadReply, state);
- commSetTimeout(fd, Ident::TheConfig.timeout, Ident::Timeout, state);
+ conn->remote.GetPort(),
+ conn->local.GetPort());
+ comm_write_mbuf(conn->fd, &mb, NULL, state);
+ comm_read(conn->fd, state->buf, BUFSIZ, Ident::ReadReply, state);
+ commSetTimeout(conn->fd, Ident::TheConfig.timeout, Ident::Timeout, state);
 }
 
 void
@@ -161,10 +165,11 @@
     char *ident = NULL;
     char *t = NULL;
 
- assert (buf == state->buf);
+ assert(buf == state->buf);
+ assert(fd == state->conn.fd);
 
     if (flag != COMM_OK || len <= 0) {
- comm_close(fd);
+ state->conn.close();
         return;
     }
 
@@ -181,7 +186,7 @@
     if ((t = strchr(buf, '\n')))
         *t = '\0';
 
- debugs(30, 5, "identReadReply: FD " << fd << ": Read '" << buf << "'");
+ debugs(30, 5, HERE << "FD " << fd << ": Read '" << buf << "'");
 
     if (strstr(buf, "USERID")) {
         if ((ident = strrchr(buf, ':'))) {
@@ -190,7 +195,7 @@
         }
     }
 
- comm_close(fd);
+ state->conn.close();
 }
 
 void
@@ -213,17 +218,15 @@
  * start a TCP connection to the peer host on port 113
  */
 void
-Ident::Start(Ip::Address &me, Ip::Address &my_peer, IDCB * callback, void *data)
+Ident::Start(Comm::ConnectionPointer conn, IDCB * callback, void *data)
 {
     IdentStateData *state;
- int fd;
     char key1[IDENT_KEY_SZ];
     char key2[IDENT_KEY_SZ];
     char key[IDENT_KEY_SZ];
- char ntoabuf[MAX_IPSTRLEN];
 
- me.ToURL(key1, IDENT_KEY_SZ);
- my_peer.ToURL(key2, IDENT_KEY_SZ);
+ conn->local.ToURL(key1, IDENT_KEY_SZ);
+ conn->remote.ToURL(key2, IDENT_KEY_SZ);
     snprintf(key, IDENT_KEY_SZ, "%s,%s", key1, key2);
 
     if (!ident_hash) {
@@ -234,33 +237,22 @@
         return;
     }
 
- Ip::Address addr = me;
- addr.SetPort(0); // NP: use random port for secure outbound to IDENT_PORT
-
- fd = comm_open_listener(SOCK_STREAM,
- IPPROTO_TCP,
- addr,
- COMM_NONBLOCKING,
- "ident");
-
- if (fd == COMM_ERROR) {
- /* Failed to get a local socket */
- callback(NULL, data);
- return;
- }
-
     CBDATA_INIT_TYPE(IdentStateData);
     state = cbdataAlloc(IdentStateData);
     state->hash.key = xstrdup(key);
- state->fd = fd;
- state->me = me;
- state->my_peer = my_peer;
+ /* clone the conn. we are about to destroy the conn
+ * for re-use of the addresses etc by IDENT. */
+ state->conn = *conn;
+ state->conn.local.SetPort(0); // NP: use random port for secure outbound to IDENT_PORT
+ state->conn.flags |= COMM_NONBLOCKING;
+
     ClientAdd(state, callback, data);
     hash_join(ident_hash, &state->hash);
- comm_add_close_handler(fd, Ident::Close, state);
- commSetTimeout(fd, Ident::TheConfig.timeout, Ident::Timeout, state);
- state->my_peer.NtoA(ntoabuf,MAX_IPSTRLEN);
- commConnectStart(fd, ntoabuf, IDENT_PORT, Ident::ConnectDone, state);
+
+ AsyncCall::Pointer call = commCbCall(30,3, "Ident::ConnectDone", CommConnectCbPtrFun(Ident::ConnectDone, state));
+ ConnectStateData *cs = new ConnectStateData(&(state->conn), call);
+ cs->connect_timeout = Ident::TheConfig.timeout;
+ cs->connect();
 }
 
 void

=== modified file 'src/ident/Ident.h'
--- src/ident/Ident.h 2010-05-02 18:52:45 +0000
+++ src/ident/Ident.h 2010-06-08 14:03:24 +0000
@@ -14,8 +14,7 @@
 #if USE_IDENT
 
 #include "cbdata.h"
-
-#include "ip/forward.h"
+#include "comm/forward.h"
 
 namespace Ident
 {
@@ -28,7 +27,7 @@
  * Self-registers with a global ident lookup manager,
  * will call Ident::Init() itself if the manager has not been initialized already.
  */
-void Start(Ip::Address &me, Ip::Address &my_peer, IDCB * callback, void *cbdata);
+void Start(Comm::ConnectionPointer conn, IDCB * callback, void *cbdata);
 
 /**
  \ingroup IdentAPI

=== modified file 'src/ipc.cc'
--- src/ipc.cc 2010-05-02 19:32:42 +0000
+++ src/ipc.cc 2010-06-08 14:03:24 +0000
@@ -31,7 +31,7 @@
  */
 
 #include "squid.h"
-#include "comm.h"
+#include "comm/Connection.h"
 #include "fde.h"
 #include "ip/Address.h"
 #include "rfc1738.h"

=== modified file 'src/ipcache.cc'
--- src/ipcache.cc 2010-06-01 00:12:34 +0000
+++ src/ipcache.cc 2010-06-09 09:50:31 +0000
@@ -32,12 +32,13 @@
 
 #include "squid.h"
 #include "cbdata.h"
+#include "CacheManager.h"
+#include "DnsLookupDetails.h"
 #include "event.h"
-#include "CacheManager.h"
+#include "ip/Address.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "wordlist.h"
-#include "ip/Address.h"
 
 /**
  \defgroup IPCacheAPI IP Cache API
@@ -621,9 +622,9 @@
  * of scheduling an async call. This reentrant behavior means that the
  * user job must be extra careful after calling ipcache_nbgethostbyname,
  * especially if the handler destroys the job. Moreover, the job has
- * no way of knowing whether the reentrant call happened. commConnectStart
- * protects the job by scheduling an async call, but some user code calls
- * ipcache_nbgethostbyname directly.
+ * no way of knowing whether the reentrant call happened.
+ * Comm::Connection setup usually protects the job by scheduling an async call,
+ * but some user code calls ipcache_nbgethostbyname directly.
  */
 void
 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)

=== modified file 'src/log/ModTcp.cc'
--- src/log/ModTcp.cc 2010-04-20 12:51:57 +0000
+++ src/log/ModTcp.cc 2010-06-08 14:03:24 +0000
@@ -33,6 +33,7 @@
 
 #include "squid.h"
 #include "comm.h"
+#include "comm/Connection.h"
 #include "log/File.h"
 #include "log/ModTcp.h"
 #include "Parsing.h"

=== modified file 'src/log/ModUdp.cc'
--- src/log/ModUdp.cc 2010-04-17 02:29:04 +0000
+++ src/log/ModUdp.cc 2010-06-08 14:03:24 +0000
@@ -32,6 +32,7 @@
 
 #include "squid.h"
 #include "comm.h"
+#include "comm/Connection.h"
 #include "log/File.h"
 #include "log/ModUdp.h"
 #include "Parsing.h"

=== modified file 'src/main.cc'
--- src/main.cc 2010-06-02 13:35:20 +0000
+++ src/main.cc 2010-06-03 07:18:25 +0000
@@ -74,7 +74,7 @@
 #include "MemPool.h"
 #include "icmp/IcmpSquid.h"
 #include "icmp/net_db.h"
-
+#include "PeerSelectState.h"
 #if USE_LOADABLE_MODULES
 #include "LoadableModules.h"
 #endif

=== modified file 'src/neighbors.cc'
--- src/neighbors.cc 2010-05-02 19:32:42 +0000
+++ src/neighbors.cc 2010-06-09 09:50:31 +0000
@@ -33,8 +33,10 @@
 #include "squid.h"
 #include "ProtoPort.h"
 #include "acl/FilledChecklist.h"
+#include "comm/Connection.h"
+#include "comm/ConnectStateData.h"
+#include "CacheManager.h"
 #include "event.h"
-#include "CacheManager.h"
 #include "htcp.h"
 #include "HttpRequest.h"
 #include "ICP.h"
@@ -60,7 +62,7 @@
 static void neighborCountIgnored(peer *);
 static void peerRefreshDNS(void *);
 static IPH peerDNSConfigure;
-static int peerProbeConnect(peer *);
+static bool peerProbeConnect(peer *);
 static CNCB peerProbeConnectDone;
 static void peerCountMcastPeersDone(void *data);
 static void peerCountMcastPeersStart(void *data);
@@ -1342,68 +1344,45 @@
         p->tcp_up = p->connect_fail_limit;
 }
 
-/// called by Comm when test_fd is closed while connect is in progress
-static void
-peerProbeClosed(int fd, void *data)
-{
- peer *p = (peer*)data;
- p->test_fd = -1;
- // it is a failure because we failed to connect
- peerConnectFailedSilent(p);
-}
-
-static void
-peerProbeConnectTimeout(int fd, void *data)
-{
- peer * p = (peer *)data;
- comm_remove_close_handler(fd, &peerProbeClosed, p);
- comm_close(fd);
- p->test_fd = -1;
- peerConnectFailedSilent(p);
-}
-
 /*
 * peerProbeConnect will be called on dead peers by neighborUp
 */
-static int
+static bool
 peerProbeConnect(peer * p)
 {
- int fd;
- time_t ctimeout = p->connect_timeout > 0 ? p->connect_timeout
- : Config.Timeout.peer_connect;
- int ret = squid_curtime - p->stats.last_connect_failure > ctimeout * 10;
+ time_t ctimeout = p->connect_timeout > 0 ? p->connect_timeout : Config.Timeout.peer_connect;
+ bool ret = (squid_curtime - p->stats.last_connect_failure) > (ctimeout * 10);
 
- if (p->test_fd != -1)
+ if (p->testing_now)
         return ret;/* probe already running */
 
     if (squid_curtime - p->stats.last_connect_probe == 0)
         return ret;/* don't probe to often */
 
- Ip::Address temp(getOutgoingAddr(NULL,p));
-
- fd = comm_open(SOCK_STREAM, IPPROTO_TCP, temp, COMM_NONBLOCKING, p->host);
-
- if (fd < 0)
- return ret;
-
- comm_add_close_handler(fd, &peerProbeClosed, p);
- commSetTimeout(fd, ctimeout, peerProbeConnectTimeout, p);
-
- p->test_fd = fd;
-
+ /* for each IP address of this peer. find one that we can connect to and probe it. */
+ Comm::PathsPointer paths = new Comm::Paths;
+ for (int i = 0; i < p->n_addresses; i++) {
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ conn->remote = p->addresses[i];
+ conn->remote.SetPort(p->http_port);
+ getOutgoingAddress(NULL, conn);
+ paths->push_back(conn);
+ }
+
+ p->testing_now = true;
     p->stats.last_connect_probe = squid_curtime;
 
- commConnectStart(p->test_fd,
- p->host,
- p->http_port,
- peerProbeConnectDone,
- p);
+ AsyncCall::Pointer call = commCbCall(15,3, "peerProbeConnectDone", CommConnectCbPtrFun(peerProbeConnectDone, p));
+ ConnectStateData *cs = new ConnectStateData(paths, call);
+ cs->connect_timeout = ctimeout;
+ cs->host = xstrdup(p->host);
+ cs->connect();
 
     return ret;
 }
 
 static void
-peerProbeConnectDone(int fd, const DnsLookupDetails &, comm_err_t status, int xerrno, void *data)
+peerProbeConnectDone(Comm::ConnectionPointer conn, Comm::PathsPointer unused, comm_err_t status, int xerrno, void *data)
 {
     peer *p = (peer*)data;
 
@@ -1413,9 +1392,8 @@
         peerConnectFailedSilent(p);
     }
 
- comm_remove_close_handler(fd, &peerProbeClosed, p);
- comm_close(fd);
- p->test_fd = -1;
+ conn->close();
+ p->testing_now = false;
     return;
 }
 

=== modified file 'src/peer_select.cc'
--- src/peer_select.cc 2010-01-30 00:30:56 +0000
+++ src/peer_select.cc 2010-06-09 10:44:03 +0000
@@ -33,17 +33,18 @@
  */
 
 #include "squid.h"
+#include "acl/FilledChecklist.h"
+#include "DnsLookupDetails.h"
 #include "event.h"
-#include "PeerSelectState.h"
-#include "Store.h"
+#include "forward.h"
 #include "hier_code.h"
+#include "htcp.h"
+#include "HttpRequest.h"
+#include "icmp/net_db.h"
 #include "ICP.h"
-#include "HttpRequest.h"
-#include "acl/FilledChecklist.h"
-#include "htcp.h"
-#include "forward.h"
+#include "PeerSelectState.h"
 #include "SquidTime.h"
-#include "icmp/net_db.h"
+#include "Store.h"
 
 static struct {
     int timeouts;
@@ -74,6 +75,8 @@
 static void peerGetAllParents(ps_state *);
 static void peerAddFwdServer(FwdServer **, peer *, hier_code);
 static void peerSelectPinned(ps_state * ps);
+static void peerSelectDnsResults(const ipcache_addrs *ia, const DnsLookupDetails &details, void *data);
+
 
 CBDATA_CLASS_INIT(ps_state);
 
@@ -121,7 +124,8 @@
 
 
 void
-peerSelect(HttpRequest * request,
+peerSelect(Comm::PathsPointer paths,
+ HttpRequest * request,
            StoreEntry * entry,
            PSC * callback,
            void *callback_data)
@@ -139,6 +143,8 @@
 
     psstate->entry = entry;
 
+ psstate->paths = paths;
+
     psstate->callback = callback;
 
     psstate->callback_data = cbdataReference(callback_data);
@@ -182,8 +188,6 @@
 {
     StoreEntry *entry = psstate->entry;
     FwdServer *fs = psstate->servers;
- PSC *callback;
- void *cbdata;
 
     if (entry) {
         debugs(44, 3, "peerSelectCallback: " << entry->url() );
@@ -203,17 +207,92 @@
 
     psstate->ping.stop = current_time;
     psstate->request->hier.ping = psstate->ping;
- callback = psstate->callback;
+}
+
+void
+peerSelectDnsPaths(ps_state *psstate)
+{
+ FwdServer *fs = psstate->servers;
+
+ // convert the list of FwdServer destinations into destinations IP addresses
+ if (fs && psstate->paths->size() < Config.forward_max_tries) {
+ // send the next one off for DNS lookup.
+ const char *host = fs->_peer ? fs->_peer->host : psstate->request->GetHost();
+ debugs(44, 2, "Find IP destination for: " << psstate->entry->url() << "' via " << host);
+ ipcache_nbgethostbyname(host, peerSelectDnsResults, psstate);
+ return;
+ }
+
+ // done with DNS lookups. pass back to caller
+ PSC *callback = psstate->callback;
     psstate->callback = NULL;
 
+ debugs(44, 2, "Found IP destination for: " << psstate->entry->url() << "'");
+
+ void *cbdata;
     if (cbdataReferenceValidDone(psstate->callback_data, &cbdata)) {
- psstate->servers = NULL;
- callback(fs, cbdata);
+ callback(psstate->paths, cbdata);
     }
 
     peerSelectStateFree(psstate);
 }
 
+static void
+peerSelectDnsResults(const ipcache_addrs *ia, const DnsLookupDetails &details, void *data)
+{
+ ps_state *psstate = (ps_state *)data;
+
+ psstate->request->recordLookup(details);
+
+ FwdServer *fs = psstate->servers;
+ if (ia != NULL) {
+
+ assert(ia->cur < ia->count);
+
+ // loop over each result address, adding to the possible destinations.
+ Comm::ConnectionPointer p;
+ int ip = ia->cur;
+ for (int n = 0; n < ia->count; n++, ip++) {
+ if (ip >= ia->count) ip = 0; // looped back to zero.
+
+ // Enforce forward_max_tries configuration.
+ if (psstate->paths->paths() >= Config.forward_max_tries)
+ break;
+
+ // for TPROXY we must skip unusable addresses.
+ if (psstate->request->flags.spoof_client_ip && !(fs->_peer && fs->_peer->options.no_tproxy) ) {
+ if(ia->in_addrs[n].IsIPv4() != psstate->request->client_addr.IsIPv4()) {
+ // we CAN'T spoof the address on this link. find another.
+ continue;
+ }
+ }
+
+ p = new Comm::Connection();
+ p->remote = ia->in_addrs[n];
+ if (fs->_peer)
+ p->remote.SetPort(fs->_peer->http_port);
+ else
+ p->remote.SetPort(psstate->request->port);
+ p->peer_type = fs->code;
+
+ // check for a configured outgoing address for this destination...
+ getOutgoingAddress(psstate->request, p);
+ p->tos = getOutgoingTOS(psstate->request);
+
+ psstate->paths->push_back(p);
+ }
+ } else {
+ debugs(44, 3, HERE << "Unknown host: " << fs->_peer ? fs->_peer->host : psstate->request->GetHost());
+ }
+
+ psstate->servers = fs->next;
+ cbdataReferenceDone(fs->_peer);
+ memFree(fs, MEM_FWD_SERVER);
+
+ // see if more paths can be found
+ peerSelectDnsPaths(psstate);
+}
+
 static int
 peerCheckNetdbDirect(ps_state * psstate)
 {
@@ -265,7 +344,7 @@
     HttpRequest *request = ps->request;
     debugs(44, 3, "peerSelectFoo: '" << RequestMethodStr(request->method) << " " << request->GetHost() << "'");
 
- /** If we don't known whether DIRECT is permitted ... */
+ /** If we don't know whether DIRECT is permitted ... */
     if (ps->direct == DIRECT_UNKNOWN) {
         if (ps->always_direct == 0 && Config.accessList.AlwaysDirect) {
             /** check always_direct; */
@@ -344,15 +423,17 @@
         break;
     }
 
- peerSelectCallback(ps);
+ // resolve the possible peers
+ peerSelectDnsPaths(ps);
 }
 
-/*
+int peerAllowedToUse(const peer * p, HttpRequest * request);
+
+/**
  * peerSelectPinned
  *
- * Selects a pinned connection
+ * Selects a pinned connection.
  */
-int peerAllowedToUse(const peer * p, HttpRequest * request);
 static void
 peerSelectPinned(ps_state * ps)
 {
@@ -374,7 +455,7 @@
     }
 }
 
-/*
+/**
  * peerGetSomeNeighbor
  *
  * Selects a neighbor (parent or sibling) based on one of the
@@ -599,6 +680,7 @@
 peerSelectInit(void)
 {
     memset(&PeerStats, '\0', sizeof(PeerStats));
+ memDataInit(MEM_FWD_SERVER, "FwdServer", sizeof(FwdServer), 0);
 }
 
 static void

=== modified file 'src/protos.h'
--- src/protos.h 2010-06-03 00:12:32 +0000
+++ src/protos.h 2010-06-08 14:03:24 +0000
@@ -398,9 +398,6 @@
 
 SQUIDCEXTERN peer *whichPeer(const Ip::Address &from);
 
-SQUIDCEXTERN void peerSelect(HttpRequest *, StoreEntry *, PSC *, void *data);
-SQUIDCEXTERN void peerSelectInit(void);
-
 /* peer_digest.c */
 class PeerDigest;
 SQUIDCEXTERN PeerDigest *peerDigestCreate(peer * p);
@@ -408,7 +405,8 @@
 SQUIDCEXTERN void peerDigestNotePeerGone(PeerDigest * pd);
 SQUIDCEXTERN void peerDigestStatsReport(const PeerDigest * pd, StoreEntry * e);
 
-extern Ip::Address getOutgoingAddr(HttpRequest * request, struct peer *dst_peer);
+#include "comm/forward.h"
+extern void getOutgoingAddress(HttpRequest * request, Comm::ConnectionPointer conn);
 unsigned long getOutgoingTOS(HttpRequest * request);
 
 SQUIDCEXTERN void urnStart(HttpRequest *, StoreEntry *);

=== modified file 'src/snmp_core.cc'
--- src/snmp_core.cc 2010-05-02 19:32:42 +0000
+++ src/snmp_core.cc 2010-06-08 14:03:24 +0000
@@ -33,6 +33,7 @@
 #include "acl/FilledChecklist.h"
 #include "cache_snmp.h"
 #include "comm.h"
+#include "comm/Connection.h"
 #include "compat/strsep.h"
 #include "ip/Address.h"
 

=== modified file 'src/structs.h'
--- src/structs.h 2010-05-27 01:56:23 +0000
+++ src/structs.h 2010-06-09 11:23:59 +0000
@@ -443,6 +443,7 @@
     } onoff;
 
     int forward_max_tries;
+ int connect_retries;
 
     class ACL *aclList;
 
@@ -521,7 +522,6 @@
     char *errorStylesheet;
 
     struct {
- int maxtries;
         int onerror;
     } retry;
 
@@ -910,7 +910,7 @@
     int n_addresses;
     int rr_count;
     peer *next;
- int test_fd;
+ bool testing_now;
 
     struct {
         unsigned int hash;

=== modified file 'src/tunnel.cc'
--- src/tunnel.cc 2010-04-17 02:29:04 +0000
+++ src/tunnel.cc 2010-06-08 14:03:24 +0000
@@ -1,4 +1,3 @@
-
 /*
  * $Id$
  *
@@ -34,18 +33,22 @@
  */
 
 #include "squid.h"
-#include "errorpage.h"
-#include "HttpRequest.h"
-#include "fde.h"
+#include "acl/FilledChecklist.h"
+#include "Array.h"
 #include "comm.h"
+#include "comm/Connection.h"
+#include "comm/ConnectStateData.h"
+#include "client_side.h"
 #include "client_side_request.h"
-#include "acl/FilledChecklist.h"
 #if DELAY_POOLS
 #include "DelayId.h"
 #endif
-#include "client_side.h"
+#include "errorpage.h"
+#include "fde.h"
+#include "HttpRequest.h"
+#include "http.h"
 #include "MemBuf.h"
-#include "http.h"
+#include "PeerSelectState.h"
 
 class TunnelStateData
 {
@@ -65,7 +68,7 @@
     char *host; /* either request->host or proxy host */
     u_short port;
     HttpRequest *request;
- FwdServer *servers;
+ Comm::PathsPointer paths;
 
     class Connection
     {
@@ -173,7 +176,7 @@
     assert(tunnelState != NULL);
     assert(tunnelState->noConnections());
     safe_free(tunnelState->url);
- FwdState::serversFree(&tunnelState->servers);
+ if (tunnelState->paths) tunnelState->paths->clean();
     tunnelState->host = NULL;
     HTTPMSGUNLOCK(tunnelState->request);
     delete tunnelState;
@@ -181,7 +184,7 @@
 
 TunnelStateData::Connection::~Connection()
 {
- safe_free (buf);
+ safe_free(buf);
 }
 
 int
@@ -463,6 +466,7 @@
     comm_read(from.fd(), from.buf, from.bytesWanted(1, SQUID_TCP_SO_RCVBUF), completion, this);
 }
 
+#if UNUSED //?
 static void
 tunnelConnectTimeout(int fd, void *data)
 {
@@ -470,18 +474,18 @@
     HttpRequest *request = tunnelState->request;
     ErrorState *err = NULL;
 
- if (tunnelState->servers) {
- if (tunnelState->servers->_peer)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- tunnelState->servers->_peer->host);
+ if (tunnelState->paths != NULL && tunnelState->paths->size() > 0) {
+ if ((*(tunnelState->paths))[0]->getPeer())
+ hierarchyNote(&tunnelState->request->hier, (*(tunnelState->paths))[0]->peer_type,
+ (*(tunnelState->paths))[0]->getPeer()->host);
         else if (Config.onoff.log_ip_on_direct)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
+ hierarchyNote(&tunnelState->request->hier, (*(tunnelState->paths))[0]->peer_type,
                           fd_table[tunnelState->server.fd()].ipaddr);
         else
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
+ hierarchyNote(&tunnelState->request->hier, (*(tunnelState->paths))[0]->peer_type,
                           tunnelState->host);
     } else
- debugs(26, 1, "tunnelConnectTimeout(): tunnelState->servers is NULL");
+ debugs(26, DBG_IMPORTANT, "tunnelConnectTimeout(): no forwarding destinations available.");
 
     err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
 
@@ -498,6 +502,7 @@
     errorSend(tunnelState->client.fd(), err);
     comm_close(fd);
 }
+#endif
 
 static void
 tunnelConnectedWriteDone(int fd, char *buf, size_t size, comm_err_t flag, int xerrno, void *data)
@@ -553,52 +558,63 @@
 
 
 static void
-tunnelConnectDone(int fdnotused, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
+tunnelConnectDone(Comm::ConnectionPointer unused, Comm::PathsPointer paths, comm_err_t status, int xerrno, void *data)
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     HttpRequest *request = tunnelState->request;
     ErrorState *err = NULL;
-
- request->recordLookup(dns);
-
- if (tunnelState->servers->_peer)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- tunnelState->servers->_peer->host);
+ Comm::ConnectionPointer conn = (*paths)[0];
+
+ assert(tunnelState->paths == paths);
+
+#if DELAY_POOLS
+ /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
+ if (conn->getPeer() && conn->getPeer()->options.no_delay)
+ tunnelState->server.setDelayId(DelayId());
+#endif
+
+ if (conn != NULL && conn->getPeer())
+ hierarchyNote(&tunnelState->request->hier, conn->peer_type, conn->getPeer()->host);
     else if (Config.onoff.log_ip_on_direct)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- fd_table[tunnelState->server.fd()].ipaddr);
+ hierarchyNote(&tunnelState->request->hier, conn->peer_type, fd_table[conn->fd].ipaddr);
     else
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- tunnelState->host);
+ hierarchyNote(&tunnelState->request->hier, conn->peer_type, tunnelState->host);
 
- if (status == COMM_ERR_DNS) {
- debugs(26, 4, "tunnelConnect: Unknown host: " << tunnelState->host);
- err = errorCon(ERR_DNS_FAIL, HTTP_NOT_FOUND, request);
- *tunnelState->status_ptr = HTTP_NOT_FOUND;
- err->dnsError = dns.error;
- err->callback = tunnelErrorComplete;
- err->callback_data = tunnelState;
- errorSend(tunnelState->client.fd(), err);
- } else if (status != COMM_OK) {
+ if (status != COMM_OK) {
         err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
         *tunnelState->status_ptr = HTTP_SERVICE_UNAVAILABLE;
         err->xerrno = xerrno;
- err->port = tunnelState->port;
+ // on timeout is this still: err->xerrno = ETIMEDOUT;
+ err->port = conn->remote.GetPort();
         err->callback = tunnelErrorComplete;
         err->callback_data = tunnelState;
         errorSend(tunnelState->client.fd(), err);
+ return;
+ }
+
+ tunnelState->server.fd(conn->fd);
+ comm_add_close_handler(tunnelState->server.fd(), tunnelServerClosed, tunnelState);
+
+ // TODO: hold the conn. drop these fields.
+ tunnelState->host = conn->getPeer() ? conn->getPeer()->host : xstrdup(request->GetHost());
+ request->peer_host = conn->getPeer() ? conn->getPeer()->host : NULL;
+ tunnelState->port = conn->remote.GetPort();
+
+ if (conn->getPeer()) {
+ tunnelState->request->peer_login = conn->getPeer()->login;
+ tunnelState->request->flags.proxying = 1;
     } else {
- if (tunnelState->servers->_peer)
- tunnelProxyConnected(tunnelState->server.fd(), tunnelState);
- else {
- tunnelConnected(tunnelState->server.fd(), tunnelState);
- }
-
- commSetTimeout(tunnelState->server.fd(),
- Config.Timeout.read,
- tunnelTimeout,
- tunnelState);
- }
+ tunnelState->request->peer_login = NULL;
+ tunnelState->request->flags.proxying = 0;
+ }
+
+ if (conn->getPeer())
+ tunnelProxyConnected(tunnelState->server.fd(), tunnelState);
+ else {
+ tunnelConnected(tunnelState->server.fd(), tunnelState);
+ }
+
+ commSetTimeout(tunnelState->server.fd(), Config.Timeout.read, tunnelTimeout, tunnelState);
 }
 
 void
@@ -606,7 +622,6 @@
 {
     /* Create state structure. */
     TunnelStateData *tunnelState = NULL;
- int sock;
     ErrorState *err = NULL;
     int answer;
     int fd = http->getConn()->fd;
@@ -639,43 +654,16 @@
     debugs(26, 3, "tunnelStart: '" << RequestMethodStr(request->method) << " " << url << "'");
     statCounter.server.all.requests++;
     statCounter.server.other.requests++;
- /* Create socket. */
- Ip::Address temp = getOutgoingAddr(request,NULL);
- int flags = COMM_NONBLOCKING;
- if (request->flags.spoof_client_ip) {
- flags |= COMM_TRANSPARENT;
- }
- sock = comm_openex(SOCK_STREAM,
- IPPROTO_TCP,
- temp,
- flags,
- getOutgoingTOS(request),
- url);
-
- if (sock == COMM_ERROR) {
- debugs(26, 4, "tunnelStart: Failed because we're out of sockets.");
- err = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
- *status_ptr = HTTP_INTERNAL_SERVER_ERROR;
- err->xerrno = errno;
- errorSend(fd, err);
- return;
- }
 
     tunnelState = new TunnelStateData;
 #if DELAY_POOLS
-
     tunnelState->server.setDelayId(DelayId::DelayClient(http));
 #endif
-
     tunnelState->url = xstrdup(url);
     tunnelState->request = HTTPMSGLOCK(request);
     tunnelState->server.size_ptr = size_ptr;
     tunnelState->status_ptr = status_ptr;
     tunnelState->client.fd(fd);
- tunnelState->server.fd(sock);
- comm_add_close_handler(tunnelState->server.fd(),
- tunnelServerClosed,
- tunnelState);
     comm_add_close_handler(tunnelState->client.fd(),
                            tunnelClientClosed,
                            tunnelState);
@@ -683,14 +671,12 @@
                    Config.Timeout.lifetime,
                    tunnelTimeout,
                    tunnelState);
- commSetTimeout(tunnelState->server.fd(),
- Config.Timeout.connect,
- tunnelConnectTimeout,
- tunnelState);
- peerSelect(request,
+
+ peerSelect(tunnelState->paths, request,
                NULL,
                tunnelPeerSelectComplete,
                tunnelState);
+
     /*
      * Disable the client read handler until peer selection is complete
      * Take control away from client_side.c.
@@ -727,13 +713,12 @@
 }
 
 static void
-tunnelPeerSelectComplete(FwdServer * fs, void *data)
+tunnelPeerSelectComplete(Comm::PathsPointer peer_paths, void *data)
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     HttpRequest *request = tunnelState->request;
- peer *g = NULL;
 
- if (fs == NULL) {
+ if (peer_paths == NULL || peer_paths->size() < 1) {
         ErrorState *err;
         err = errorCon(ERR_CANNOT_FORWARD, HTTP_SERVICE_UNAVAILABLE, request);
         *tunnelState->status_ptr = HTTP_SERVICE_UNAVAILABLE;
@@ -743,40 +728,11 @@
         return;
     }
 
- tunnelState->servers = fs;
- tunnelState->host = fs->_peer ? fs->_peer->host : xstrdup(request->GetHost());
- request->peer_host = fs->_peer ? fs->_peer->host : NULL;
-
- if (fs->_peer == NULL) {
- tunnelState->port = request->port;
- } else if (fs->_peer->http_port != 0) {
- tunnelState->port = fs->_peer->http_port;
- } else if ((g = peerFindByName(fs->_peer->host))) {
- tunnelState->port = g->http_port;
- } else {
- tunnelState->port = CACHE_HTTP_PORT;
- }
-
- if (fs->_peer) {
- tunnelState->request->peer_login = fs->_peer->login;
- tunnelState->request->flags.proxying = 1;
- } else {
- tunnelState->request->peer_login = NULL;
- tunnelState->request->flags.proxying = 0;
- }
-
-#if DELAY_POOLS
- /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
- if (g && g->options.no_delay)
- tunnelState->server.setDelayId(DelayId());
-
-#endif
-
- commConnectStart(tunnelState->server.fd(),
- tunnelState->host,
- tunnelState->port,
- tunnelConnectDone,
- tunnelState);
+ AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
+ ConnectStateData *cs = new ConnectStateData(tunnelState->paths, call);
+ cs->host = xstrdup(tunnelState->url);
+ cs->connect_timeout = Config.Timeout.connect;
+ cs->connect();
 }
 
 CBDATA_CLASS_INIT(TunnelStateData);

=== modified file 'src/typedefs.h'
--- src/typedefs.h 2010-04-11 09:02:42 +0000
+++ src/typedefs.h 2010-06-03 07:18:25 +0000
@@ -196,8 +196,6 @@
 typedef void IPH(const ipcache_addrs *, const DnsLookupDetails &details, void *);
 typedef void IRCB(struct peer *, peer_t, protocol_t, void *, void *data);
 
-class FwdServer;
-typedef void PSC(FwdServer *, void *);
 typedef void RH(void *data, char *);
 /* in wordlist.h */
 

=== modified file 'src/wccp.cc'
--- src/wccp.cc 2010-04-27 13:24:55 +0000
+++ src/wccp.cc 2010-06-08 14:03:24 +0000
@@ -33,11 +33,13 @@
  *
  */
 #include "squid.h"
+
+#if USE_WCCP
+
 #include "comm.h"
+#include "comm/Connection.h"
 #include "event.h"
 
-#if USE_WCCP
-
 #define WCCP_PORT 2048
 #define WCCP_REVISION 0
 #define WCCP_ACTIVE_CACHES 32

=== modified file 'src/wccp2.cc'
--- src/wccp2.cc 2010-05-25 10:53:13 +0000
+++ src/wccp2.cc 2010-06-08 14:03:24 +0000
@@ -35,6 +35,7 @@
 #if USE_WCCPv2
 
 #include "comm.h"
+#include "comm/Connection.h"
 #include "compat/strsep.h"
 #include "event.h"
 #include "ip/Address.h"

=== modified file 'src/whois.cc'
--- src/whois.cc 2010-02-06 06:32:11 +0000
+++ src/whois.cc 2010-05-19 11:28:21 +0000
@@ -81,7 +81,7 @@
 whoisStart(FwdState * fwd)
 {
     WhoisState *p;
- int fd = fwd->server_fd;
+ int fd = fwd->conn()->fd;
     char *buf;
     size_t l;
     CBDATA_INIT_TYPE(WhoisState);

# Begin bundle
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
a0qgqWk1YDJpuknkNWJblJOCMVJ8EthTYUqfVN7bstXNmz166dNkntNy7cNT9aE13woZBFyMii3L
vkc1lRqFMR+DTTQ5m5YyMzQ0HS9N/ldvvqxy0SSdtnp0k+Y3e22Odu57Xa1qPGDg1V0bCGN56aOT
NCk6VSZeMSZxrjMY+NHIia1uIOaxGpU0NCeA5ahHI2JHBQoXDmQpqTLjyS/DIYWy3YQ6lcHsq0ST
kqhloq5HMyNLRbhurE10zVto2Tw4JJsu2Uk3Z7XHIEjSdtOj8p2PeJFPcQGE8nvXlKg1EPOxZBcL
JLrsrGsfO5KLKZ4Zh4rXyI5VemDLh9/pEOYQrxDZI4eNmFnthZo3STXcvGyiTB01efTLhybOV74Z
ctnLdq7UTe27R06ejpdh/Ha8bpamyThJhJ4w2YKE3TCrCrzzx6bpPGXDZ4Xesuk6KHa6bl6TeJNT
hlq5WXYd99NG7+cSdMvTLdym7bKPHp0o9MMqt1mqizlu3btvHp0s6SXZaOWGy7Vo3XUbLqPu/P9I
R9Y8hXQoJmRgoeVwMruF9qInMGQBr5E2BSE1PiIWBHkIdbrp/L2gcInTz728M2OF7ZDKEVf1tF/O
+eEF6LErq8+9YmVQzwcyxj6wqoKzSW1AlwcNvzRtY8Ip4xIrBMjBwc4HcUQzHY+nTlX54BvpeWzg
NN+FlR13ysULDr01Pou9Oz1ToRLZE9BK5A283UuvbC54gzIUaQdWLtSrzknqJs1nr2uSwT6i/Kp9
WyDOep+95X7PdV2FY4rkXa7VqvN63TrYg7WYum5NeX1kCICEhEUQRRAuknYskGaCeIS/X3vt0jns
zlOozD4iS57C7JG0Gh0RooiNGn4psKITfVTms0m6XiQMuF1Un5Oi7hJRFHtZ+LrtZnerhxWKSpN5
Oc6rd6SkvEuIBw6TcLRvQhuRAonERNCWu98UVl+mDpdC35/nVRJq6ZdJsuXTto0XfC2yVmz21fNN
M46arRbe29pWrK/bF70xh9cRERhbV0nds88nDdKLcqJ6uFGyhiZUMCxicGhUuKCOiBN8LCJN4otm
mpJnInt1n6xW9zlt33VrSmWWvLBE1GzanylmIiOF8HKrtVrrZplKKS2ZeNGF0nWNONrMTnbdaOFM
aNmyTYnFtknDVss1btybRu1aIirRuw+FGiqzZ9XSSjpQWj3djbG/cp8ohOSJJFVK2TolWm1aWboi
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
XckU4UJCKhdk9A==
Received on Wed Jun 09 2010 - 11:44:36 MDT

This archive was generated by hypermail 2.2.0 : Fri Jun 25 2010 - 12:00:08 MDT