This updated my previous submission for the comm connection opener model.
diff since last time:
- removed the multi-path pathways. They are now isolated at the upper
level. ConnOpener now handles only one Comm::Connection.
- implemented ConnOpener as an AsyncJob child.
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.
Forwarding has been extended to hold the vector<> and loop calling
ConnOpener jobs until a working connection is found. Limited by
forwarding_timeout and forward_max_tries.
Various connection openers have been converted to use the new ConnOpener
API and CommCalls (ConnOpener internally is native AsyncCall except the
part which needs to use a legacy CommCalls FuncPtr).
ConnOpener 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 to some
destination as described by a Comm::Connection.
Limited by connect_timeout in its running time.
Extended by connect_retries as number of attempts to open each path.
ConnOpener::connect() will go away and do some async work. Will come
back at some point by calling a handler with CommConnectCbParams
containing; flag of COMM_OK, COMM_ERR_CONNECT, COMM_TIMEOUT; xerrno if
an error is applicable; and conn pointing to the Comm::Connection it was
handling.
On COMM_OK the conn will be an open Comm::ConnectionPointer which we
can now use.
On COMM_ERR_CONNECT and COMM_TIMEOUT the conn will be !isOpen() or
NULL and should not be relied on.
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 ConnOpener.
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 remains the same, once this is stable in 3.HEAD is to (in no
particular order):
* 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.5
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: squid3_at_treenet.co.nz-20100718082943-zv6r769ibn3xoo21
# target_branch: http://www.squid-cache.org/bzr/squid3/trunk/
# testament_sha1: 18d23b39aa4ef95ce022cc8304fae99d096ea56d
# timestamp: 2010-07-18 20:30:18 +1200
# base_revision_id: rousskov_at_measurement-factory.com-20100716223742-\
# xiaw4wr97k3e8ttx
#
# Begin patch
=== modified file 'doc/release-notes/release-3.2.sgml'
--- doc/release-notes/release-3.2.sgml 2010-07-12 14:45:17 +0000
+++ doc/release-notes/release-3.2.sgml 2010-07-13 08:38:07 +0000
@@ -223,6 +223,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>else</tag>
<p>Part of conditional SMP support syyntax. see <em>if</em>
@@ -335,6 +339,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-07-18 08:29:43 +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,8 @@
CommConnectCbParams::print(std::ostream &os) const
{
CommCommonCbParams::print(os);
- os << ", " << dns;
+ if (conn != NULL)
+ os << ", conn.local=" << conn->local << ", conn.remote=" << conn->remote;
}
/* CommIoCbParams */
@@ -133,7 +136,7 @@
void
CommAcceptCbPtrFun::dial()
{
- handler(params.fd, params.nfd, ¶ms.details, params.flag, params.xerrno, params.data);
+ handler(params.fd, params.nfd, params.details, params.flag, params.xerrno, params.data);
}
void
@@ -157,7 +160,7 @@
void
CommConnectCbPtrFun::dial()
{
- handler(params.fd, params.dns, params.flag, params.xerrno, params.data);
+ handler(params.conn, params.flag, params.xerrno, params.data);
}
void
=== modified file 'src/CommCalls.h'
--- src/CommCalls.h 2010-07-14 01:00:21 +0000
+++ src/CommCalls.h 2010-07-15 10:04:21 +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_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,7 @@
void print(std::ostream &os) const;
public:
- DnsLookupDetails dns;
+ Comm::ConnectionPointer conn;
};
// read/write (I/O) parameters
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc 2010-07-13 16:43:00 +0000
+++ src/HttpRequest.cc 2010-07-15 10:04:21 +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-07-06 23:09:44 +0000
+++ src/Makefile.am 2010-07-13 08:38:07 +0000
@@ -290,7 +290,6 @@
ConfigOption.cc \
ConfigParser.cc \
ConfigParser.h \
- ConnectionDetail.h \
debug.cc \
Debug.h \
defines.h \
@@ -533,7 +532,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 \
@@ -1236,10 +1235,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) \
@@ -1422,7 +1421,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} \
@@ -1577,7 +1576,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} \
@@ -1727,7 +1726,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} \
@@ -1882,7 +1881,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} \
@@ -2254,7 +2253,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-27 11:31:31 +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::Paths *, void *);
+
+SQUIDCEXTERN void peerSelect(Comm::Paths *, 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::Paths *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
@@ -74,5 +105,4 @@
CBDATA_CLASS(ps_state);
};
-
#endif /* SQUID_PEERSELECTSTATE_H */
=== modified file 'src/adaptation/icap/ModXact.cc'
--- src/adaptation/icap/ModXact.cc 2010-06-15 07:21:57 +0000
+++ src/adaptation/icap/ModXact.cc 2010-06-27 11:34:40 +0000
@@ -16,6 +16,7 @@
#include "base/TextException.h"
#include "ChunkedCodingParser.h"
#include "comm.h"
+#include "comm/Connection.h"
#include "HttpMsg.h"
#include "HttpRequest.h"
#include "HttpReply.h"
@@ -463,7 +464,7 @@
void Adaptation::Icap::ModXact::startReading()
{
- Must(connection >= 0);
+ Must(haveConnection());
Must(!reader);
Must(!adapted.header);
Must(!adapted.body_pipe);
@@ -621,7 +622,7 @@
stopParsing();
stopWriting(true); // or should we force it?
- if (connection >= 0) {
+ if (haveConnection()) {
reuseConnection = false; // be conservative
cancelRead(); // may not work; and we cannot stop connecting either
if (!doneWithIo())
@@ -1550,7 +1551,7 @@
if (virgin.body_pipe != NULL)
buf.append("R", 1);
- if (connection > 0 && !doneReading())
+ if (haveConnection() && !doneReading())
buf.append("r", 1);
if (!state.doneWriting() && state.writing != State::writingInit)
=== 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-07-17 08:17:06 +0000
@@ -4,6 +4,8 @@
#include "squid.h"
#include "comm.h"
+#include "comm/Connection.h"
+#include "comm/ConnOpener.h"
#include "CommCalls.h"
#include "HttpMsg.h"
#include "adaptation/icap/Xaction.h"
@@ -29,7 +31,7 @@
icapRequest(NULL),
icapReply(NULL),
attempts(0),
- connection(-1),
+ connection(NULL),
theService(aService),
commBuf(NULL), commBufSize(0),
commEof(false),
@@ -89,23 +91,31 @@
{
Ip::Address client_addr;
- Must(connection < 0);
+ Must(!haveConnection());
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: Avoid 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,13 @@
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);
+
+ ConnOpener *cs = new ConnOpener(connection, connector, TheConfig.connect_timeout(service().cfg().bypass));
+ cs->setHost(s.cfg().host.termedBuf());
+ AsyncJob::AsyncStart(cs);
}
/*
@@ -159,10 +150,10 @@
void Adaptation::Icap::Xaction::closeConnection()
{
- if (connection >= 0) {
+ if (haveConnection()) {
if (closer != NULL) {
- comm_remove_close_handler(connection, closer);
+ comm_remove_close_handler(connection->fd, closer);
closer = NULL;
}
@@ -179,34 +170,44 @@
//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);
handleCommConnected();
}
@@ -220,12 +221,14 @@
void Adaptation::Icap::Xaction::scheduleWrite(MemBuf &buf)
{
+ Must(haveConnection());
+
// comm module will free the buffer
typedef CommCbMemFunT<Adaptation::Icap::Xaction, CommIoCbParams> Dialer;
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();
}
@@ -299,6 +302,8 @@
void Adaptation::Icap::Xaction::updateTimeout()
{
+ Must(haveConnection());
+
if (reader != NULL || writer != NULL) {
// restart the timeout before each I/O
// XXX: why does Config.Timeout lacks a write timeout?
@@ -307,19 +312,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(haveConnection());
Must(!reader);
Must(readBuf.hasSpace());
@@ -331,7 +336,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 +374,8 @@
void Adaptation::Icap::Xaction::cancelRead()
{
if (reader != NULL) {
- comm_read_cancel(connection, reader);
+ Must(haveConnection());
+ comm_read_cancel(connection->fd, reader);
reader = NULL;
}
}
@@ -410,11 +416,16 @@
bool Adaptation::Icap::Xaction::doneWithIo() const
{
- return connection >= 0 && // or we could still be waiting to open it
+ return haveConnection() &&
!connector && !reader && !writer && // fast checks, some redundant
doneReading() && doneWriting();
}
+bool Adaptation::Icap::Xaction::haveConnection() const
+{
+ return connection != NULL && connection->isOpen();
+}
+
// initiator aborted
void Adaptation::Icap::Xaction::noteInitiatorAborted()
{
@@ -525,8 +536,8 @@
void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf &buf) const
{
- if (connection >= 0) {
- buf.Printf("FD %d", connection);
+ if (haveConnection()) {
+ buf.Printf("FD %d", connection->fd);
if (writer != NULL)
buf.append("w", 1);
@@ -540,8 +551,8 @@
void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf &buf) const
{
- if (connection >= 0 && commEof)
- buf.Printf("Comm(%d)", connection);
+ if (haveConnection() && 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-27 11:31:31 +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"
@@ -99,6 +99,7 @@
void openConnection();
void closeConnection();
void dieOnConnectionFailure();
+ bool haveConnection() const;
void scheduleRead();
void scheduleWrite(MemBuf &buf);
@@ -140,7 +141,7 @@
void maybeLog();
protected:
- int connection; // FD of the ICAP server connection
+ Comm::ConnectionPointer connection; ///< ICAP server connection
Adaptation::Icap::ServiceRep::Pointer theService;
/*
=== modified file 'src/base/AsyncJob.cc'
--- src/base/AsyncJob.cc 2010-05-26 03:06:02 +0000
+++ src/base/AsyncJob.cc 2010-07-17 08:15:08 +0000
@@ -27,6 +27,8 @@
AsyncJob::~AsyncJob()
{
+ debugs(93,3, "AsyncJob of type " << typeName << " destructed, this=" << this <<
+ " [async" << id << ']');
}
void AsyncJob::noteStart()
=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc 2010-07-07 16:41:03 +0000
+++ src/cache_cf.cc 2010-07-13 08:38:07 +0000
@@ -650,12 +650,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);
@@ -2053,7 +2050,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-07-06 23:09:44 +0000
+++ src/cf.data.pre 2010-07-13 08:38:07 +0000
@@ -2269,6 +2269,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
@@ -6855,21 +6858,24 @@
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 made for each
+ TCP connection. The connect_retries attempts must all still
+ complete within the connection timeout period.
+
+ 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.
DOC_END
NAME: retry_on_error
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2010-07-06 23:09:44 +0000
+++ src/client_side.cc 2010-07-17 06:08:57 +0000
@@ -85,6 +85,7 @@
#include "acl/FilledChecklist.h"
#include "auth/UserRequest.h"
+#include "base/TextException.h"
#include "ChunkedCodingParser.h"
#include "client_side.h"
#include "client_side_reply.h"
@@ -92,9 +93,8 @@
#include "ClientRequestContext.h"
#include "clientStream.h"
#include "comm.h"
+#include "comm/Connection.h"
#include "comm/ListenStateData.h"
-#include "base/TextException.h"
-#include "ConnectionDetail.h"
#include "eui/Config.h"
#include "fde.h"
#include "HttpHdrContRange.h"
@@ -3085,7 +3085,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;
@@ -3098,7 +3098,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",
@@ -3106,7 +3106,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",
@@ -3116,19 +3116,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
@@ -3139,7 +3139,7 @@
connState->readSomeData();
- clientdbEstablished(details->peer, 1);
+ clientdbEstablished(details->remote, 1);
incoming_sockets_accepted++;
}
@@ -3148,7 +3148,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);
@@ -3291,7 +3291,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;
@@ -3309,7 +3309,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",
@@ -3317,7 +3317,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",
@@ -3327,10 +3327,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
@@ -3340,7 +3340,7 @@
commSetSelect(newfd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
- clientdbEstablished(details->peer, 1);
+ clientdbEstablished(details->remote, 1);
incoming_sockets_accepted++;
}
@@ -3357,10 +3357,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/client_side_request.cc'
--- src/client_side_request.cc 2010-07-13 16:26:20 +0000
+++ src/client_side_request.cc 2010-07-15 10:04:21 +0000
@@ -541,7 +541,7 @@
acl_checklist = clientAclChecklistCreate(Config.accessList.adapted_http, http);
acl_checklist->nonBlockingCheck(clientAccessCheckDoneWrapper, this);
} else {
- debugs(85, 2, HERE << "No adapted_http_access configuration.");
+ debugs(85, 2, HERE << "No adapted_http_access configuration. default: ALLOW");
clientAccessCheckDone(ACCESS_ALLOWED);
}
}
=== modified file 'src/comm.cc'
--- src/comm.cc 2010-07-06 23:09:44 +0000
+++ src/comm.cc 2010-07-18 08:21:22 +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"
@@ -197,38 +198,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
@@ -243,7 +212,6 @@
static void commSetTcpNoDelay(int);
#endif
static void commSetTcpRcvbuf(int, int);
-static PF commConnectFree;
static PF commHandleWrite;
static IPH commConnectDnsHandle;
@@ -553,16 +521,12 @@
temp.FreeAddrInfo(addr);
- F->local_addr.SetPort(temp.GetPort());
-
-#if 0 // seems to undo comm_open actions on the FD ...
- // grab default socket information for this address
- temp.GetAddrInfo(addr);
-
- F->sock_family = addr->ai_family;
-
- temp.FreeAddrInfo(addr);
-#endif
+ if (F->local_addr.IsAnyAddr()) {
+ /* save the whole local address, not just the port. */
+ F->local_addr = temp;
+ } else {
+ F->local_addr.SetPort(temp.GetPort());
+ }
debugs(5, 6, "comm_local_port: FD " << fd << ": port " << F->local_addr.GetPort() << "(family=" << F->sock_family << ")");
return F->local_addr.GetPort();
@@ -896,111 +860,6 @@
*/
}
-
-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 ¶ms = 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)
{
@@ -1021,198 +880,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)
@@ -1254,7 +921,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);
@@ -2457,10 +2125,6 @@
cancelled = true;
}
-ConnectionDetail::ConnectionDetail() : me(), peer()
-{
-}
-
int
CommSelectEngine::checkEvents(int timeout)
{
=== modified file 'src/comm.h'
--- src/comm.h 2010-07-06 23:09:44 +0000
+++ src/comm.h 2010-07-13 08:38:07 +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);
@@ -101,8 +81,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/ConnOpener.cc'
--- src/comm/ConnOpener.cc 1970-01-01 00:00:00 +0000
+++ src/comm/ConnOpener.cc 2010-07-18 08:19:31 +0000
@@ -0,0 +1,266 @@
+/*
+ * DEBUG: section 05 Socket Connection Opener
+ */
+
+#include "config.h"
+#include "base/TextException.h"
+#include "comm/ConnOpener.h"
+#include "comm/Connection.h"
+#include "comm.h"
+#include "fde.h"
+#include "icmp/net_db.h"
+#include "SquidTime.h"
+
+CBDATA_CLASS_INIT(ConnOpener);
+
+ConnOpener::ConnOpener(Comm::ConnectionPointer &c, AsyncCall::Pointer &handler, time_t ctimeout) :
+ AsyncJob("ConnOpener"),
+ connect_timeout(ctimeout),
+ host(NULL),
+ solo(c),
+ callback(handler),
+ total_tries(0),
+ fail_retries(0),
+ connstart(0)
+{
+ memset(&calls, 0, sizeof(calls));
+}
+
+ConnOpener::~ConnOpener()
+{
+ safe_free(host);
+ solo = NULL;
+ calls.earlyabort = NULL;
+ calls.timeout = NULL;
+}
+
+bool
+ConnOpener::doneAll() const
+{
+ // is the conn to be opened still waiting?
+ if (solo != NULL) {
+ debugs(5, 6, HERE << " ConnOpener::doneAll() ? NO. 'solo' is still set");
+ return false;
+ }
+
+ // is the callback still to be called?
+ if (callback != NULL) {
+ debugs(5, 6, HERE << " ConnOpener::doneAll() ? NO. callback is still set");
+ return false;
+ }
+
+ debugs(5, 6, HERE << " ConnOpener::doneAll() ? YES.");
+ return true;
+}
+
+void
+ConnOpener::swanSong()
+{
+ // cancel any event watchers
+ if (calls.earlyabort != NULL) {
+ calls.earlyabort->cancel("ConnOpener::swanSong");
+ calls.earlyabort = NULL;
+ }
+ if (calls.timeout != NULL) {
+ calls.timeout->cancel("ConnOpener::swanSong");
+ calls.timeout = NULL;
+ }
+
+ // recover what we can from the job
+ if (solo != NULL && solo->fd > -1) {
+ // it never reached fully open, so abort the FD
+ close(solo->fd);
+ fd_table[solo->fd].flags.open = 0;
+ // inform the caller
+ callCallback(COMM_ERR_CONNECT, 0);
+ }
+}
+
+void
+ConnOpener::setHost(const char * new_host)
+{
+ // unset and erase if already set.
+ if (host != NULL)
+ safe_free(host);
+
+ // set the new one if given.
+ if (new_host != NULL)
+ host = xstrdup(new_host);
+}
+
+const char *
+ConnOpener::getHost() const
+{
+ return host;
+}
+
+void
+ConnOpener::callCallback(comm_err_t status, int xerrno)
+{
+ /* remove handlers we don't want to happen anymore */
+ if (solo != NULL && solo->fd > 0) {
+ if (calls.earlyabort != NULL) {
+ comm_remove_close_handler(solo->fd, calls.earlyabort);
+ calls.earlyabort->cancel("ConnOpener completed.");
+ calls.earlyabort = NULL;
+ }
+ if (calls.timeout != NULL) {
+ commSetTimeout(solo->fd, -1, NULL, NULL);
+ calls.timeout->cancel("ConnOpener completed.");
+ calls.timeout = NULL;
+ }
+ }
+
+ if (callback != NULL) {
+ typedef CommConnectCbParams Params;
+ Params ¶ms = GetCommParams<Params>(callback);
+ params.conn = solo;
+ params.flag = status;
+ params.xerrno = xerrno;
+ ScheduleCallHere(callback);
+ callback = NULL;
+ }
+
+ /* ensure cleared local state, we are done. */
+ solo = NULL;
+
+ assert(doneAll());
+}
+
+void
+ConnOpener::start()
+{
+ Must(solo != NULL);
+
+ /* handle connecting to one single path */
+ if (solo->fd < 0) {
+#if USE_IPV6
+ /* outbound sockets have no need to be protocol agnostic. */
+ if (solo->local.IsIPv6() && solo->local.IsIPv4()) {
+ solo->local.SetIPv4();
+ }
+#endif
+ solo->fd = comm_openex(SOCK_STREAM, IPPROTO_TCP, solo->local, solo->flags, solo->tos, host);
+ if (solo->fd < 0) {
+ callCallback(COMM_ERR_CONNECT, 0);
+ return;
+ }
+
+ if (calls.earlyabort == NULL) {
+ typedef CommCbMemFunT<ConnOpener, CommConnectCbParams> Dialer;
+ calls.earlyabort = asyncCall(5, 4, "ConnOpener::earlyAbort",
+ Dialer(this, &ConnOpener::earlyAbort));
+ }
+ comm_add_close_handler(solo->fd, calls.earlyabort);
+
+ if (calls.timeout == NULL) {
+ typedef CommCbMemFunT<ConnOpener, CommTimeoutCbParams> Dialer;
+ calls.timeout = asyncCall(5, 4, "ConnOpener::timeout",
+ Dialer(this, &ConnOpener::timeout));
+ }
+ debugs(5, 3, HERE << "FD " << solo->fd << " timeout " << connect_timeout);
+ commSetTimeout(solo->fd, connect_timeout, calls.timeout);
+
+ if (connstart == 0) {
+ connstart = squid_curtime;
+ }
+ }
+
+ total_tries++;
+
+ switch (comm_connect_addr(solo->fd, solo->remote) ) {
+
+ case COMM_INPROGRESS:
+ // check for timeout FIRST.
+ if(squid_curtime - connstart > connect_timeout) {
+ debugs(5, 5, HERE << "FD " << solo->fd << ": * - ERR took too long already.");
+ callCallback(COMM_TIMEOUT, errno);
+ return;
+ } else {
+ debugs(5, 5, HERE << "FD " << solo->fd << ": COMM_INPROGRESS");
+ commSetSelect(solo->fd, COMM_SELECT_WRITE, ConnOpener::ConnectRetry, this, 0);
+ }
+ break;
+
+ case COMM_OK:
+ debugs(5, 5, HERE << "FD " << solo->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 (solo->getPeer())
+ solo->getPeer()->stats.conn_open++;
+
+ /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to
+ * indicate the state of a raw fd object being passed around.
+ * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection
+ * when those are done comm_local_port can become one of our member functions to do the below.
+ */
+ fd_table[solo->fd].flags.open = 1;
+ solo->local.SetPort(comm_local_port(solo->fd));
+ if (solo->local.IsAnyAddr()) {
+ solo->local = fd_table[solo->fd].local_addr;
+ }
+
+ if (host != NULL)
+ ipcacheMarkGoodAddr(host, solo->remote);
+ callCallback(COMM_OK, 0);
+ break;
+
+ default:
+ debugs(5, 5, HERE "FD " << solo->fd << ": * - try again");
+ fail_retries++;
+ if (host != NULL)
+ ipcacheMarkBadAddr(host, solo->remote);
+#if USE_ICMP
+ if (Config.onoff.test_reachability)
+ netdbDeleteAddrNetwork(solo->remote);
+#endif
+
+ // check for timeout FIRST.
+ if(squid_curtime - connstart > connect_timeout) {
+ debugs(5, 5, HERE << "FD " << solo->fd << ": * - ERR took too long already.");
+ callCallback(COMM_TIMEOUT, errno);
+ } else if (fail_retries < Config.connect_retries) {
+ start();
+ } else {
+ // send ERROR back to the upper layer.
+ debugs(5, 5, HERE << "FD " << solo->fd << ": * - ERR tried too many times already.");
+ callCallback(COMM_ERR_CONNECT, errno);
+ }
+ }
+}
+
+void
+ConnOpener::earlyAbort(const CommConnectCbParams &io)
+{
+ debugs(5, 3, HERE << "FD " << io.conn->fd);
+ callCallback(COMM_ERR_CLOSING, io.xerrno); // NP: is closing or shutdown better?
+}
+
+void
+ConnOpener::connect(const CommConnectCbParams &unused)
+{
+ start();
+}
+
+void
+ConnOpener::timeout(const CommTimeoutCbParams &unused)
+{
+ start();
+}
+
+void
+ConnOpener::ConnectRetry(int fd, void *data)
+{
+ ConnOpener *cs = static_cast<ConnOpener *>(data);
+ cs->start();
+
+ // see if its done and delete ConnOpener? comm Writes are not yet a Job call.
+ // so the automatic cleanup on call completion does not seem to happen
+ if (cs->doneAll());
+ cs->deleteThis("Done after ConnOpener::ConnectRetry()");
+}
=== added file 'src/comm/ConnOpener.h'
--- src/comm/ConnOpener.h 1970-01-01 00:00:00 +0000
+++ src/comm/ConnOpener.h 2010-07-17 08:17:06 +0000
@@ -0,0 +1,92 @@
+#ifndef _SQUID_SRC_COMM_OPENERSTATEDATA_H
+#define _SQUID_SRC_COMM_OPENERSTATEDATA_H
+
+#include "base/AsyncCall.h"
+#include "base/AsyncJob.h"
+#include "cbdata.h"
+#include "CommCalls.h"
+#include "comm/comm_err_t.h"
+#include "comm/forward.h"
+
+/**
+ * Async-opener of a Comm connection.
+ */
+class ConnOpener : public AsyncJob
+{
+public:
+ // ****** AsynJob API implementation ******
+
+ /** Actual start opening a TCP connection. */
+ void start();
+
+ virtual bool doneAll() const;
+ virtual void swanSong();
+
+public:
+ // ****** ConnOpener API iplementation ******
+
+ /** attempt to open a connection. */
+ ConnOpener(Comm::ConnectionPointer &, AsyncCall::Pointer &handler, time_t connect_timeout);
+ ~ConnOpener();
+
+ void setHost(const char *); ///< set the hostname note for this connection
+ const char * getHost() const; ///< get the hostname noted for this connection
+
+private:
+ /* These objects may NOT be created without connections to act on. Do not define this operator. */
+ ConnOpener(const ConnOpener &);
+ /* These objects may NOT be copied. Do not define this operator. */
+ ConnOpener operator =(const ConnOpener &c);
+
+ /** Make an FD connection attempt.
+ * Handles the case(s) when a partially setup connection gets closed early.
+ */
+ void connect(const CommConnectCbParams &unused);
+
+ /** Abort connection attempt.
+ * Handles the case(s) when a partially setup connection gets closed early.
+ */
+ void earlyAbort(const CommConnectCbParams &);
+
+ /**
+ * Handles the case(s) when a partially setup connection gets timed out.
+ * NP: When commSetTimeout accepts generic CommCommonCbParams this can die.
+ */
+ void timeout(const CommTimeoutCbParams &unused);
+
+ /**
+ * 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);
+
+ // Legacy Wrapper for the retry event after COMM_INPROGRESS
+ // As soon as comm IO accepts Async calls we can use a ConnOpener::connect call
+ static void ConnectRetry(int fd, void *data);
+
+private:
+ /**
+ * time at which to abandon the connection.
+ * the connection-done callback will be passed COMM_TIMEOUT
+ */
+ time_t connect_timeout;
+
+ char *host; ///< domain name we are trying to connect to.
+
+ 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.
+
+ /// handles to calls which we may need to cancel.
+ struct _calls {
+ AsyncCall::Pointer earlyabort;
+ AsyncCall::Pointer timeout;
+ } calls;
+
+ CBDATA_CLASS2(ConnOpener);
+};
+
+#endif /* _SQUID_SRC_COMM_CONNOPENER_H */
=== added file 'src/comm/Connection.cc'
--- src/comm/Connection.cc 1970-01-01 00:00:00 +0000
+++ src/comm/Connection.cc 2010-06-27 11:31:31 +0000
@@ -0,0 +1,69 @@
+#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()
+{
+ close();
+ if (_peer) {
+ cbdataReferenceDone(_peer);
+ }
+}
+
+Comm::ConnectionPointer &
+Comm::Connection::copyDetails() const
+{
+ ConnectionPointer c = new Comm::Connection;
+
+ c->local = local;
+ c->remote = remote;
+ c->peer_type = peer_type;
+ c->tos = tos;
+ c->flags = flags;
+
+ // ensure FD is not open in the new copy.
+ c->fd = -1;
+
+ // ensure we have a cbdata reference to _peer not a straight ptr copy.
+ c->_peer = cbdataReference(_peer);
+
+ return c;
+}
+
+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-27 11:31:31 +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,106 @@
*
*
* 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();
+
+ /** These objects may not be exactly duplicated. Use copyDetails() instead. */
+ Connection(const Connection &c);
+
+ /** Clear the connection properties and close any open socket. */
+ ~Connection();
+
+ /** Copy an existing connections IP and properties.
+ * This excludes the FD. The new copy will be a closed connection.
+ */
+ ConnectionPointer & copyDetails() const;
+
+ /** These objects may not be exactly duplicated. Use clone() instead. */
+ Connection & operator =(const Connection &c);
+
+ /** Close any open socket. */
+ void close();
+
+ /** determine whether this object describes an active connection or not. */
+ bool isOpen() const { 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-07-07 00:45:34 +0000
+++ src/comm/ListenStateData.cc 2010-07-13 08:38:07 +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) {
@@ -172,7 +172,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);
}
@@ -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-07-07 00:45:34 +0000
+++ src/comm/ListenStateData.h 2010-07-13 08:38:07 +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);
void 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-27 11:31:31 +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
+ ConnOpener.cc \
+ ConnOpener.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-27 11:31:31 +0000
@@ -0,0 +1,17 @@
+#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;
+
+}; // 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-06-15 21:46:43 +0000
+++ src/dns_internal.cc 2010-07-17 08:17:06 +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/ConnOpener.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_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));
+
+ ConnOpener *cs = new ConnOpener(conn, call, Config.Timeout.connect);
+ cs->setHost("DNS TCP Socket");
+ AsyncJob::AsyncStart(cs);
}
static void
=== modified file 'src/forward.cc'
--- src/forward.cc 2010-07-13 16:49:48 +0000
+++ src/forward.cc 2010-07-18 08:18:50 +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/ConnOpener.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,11 @@
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;
+ if (fwd->paths.size() > 0 && fwd->paths[0]->fd > -1) {
+ comm_remove_close_handler(fwd->paths[0]->fd, fwdServerClosedWrapper, fwd);
+ fwd->paths[0]->close();
}
-
+ fwd->paths.clean();
fwd->self = NULL;
}
@@ -92,7 +94,6 @@
{
entry = e;
client_fd = fd;
- server_fd = -1;
request = HTTPMSGLOCK(r);
start_t = squid_curtime;
@@ -113,10 +114,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 +160,6 @@
if (! flags.forward_completed)
completed();
- serversFree(&servers);
-
HTTPMSGUNLOCK(request);
if (err)
@@ -175,15 +171,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.size() > 0 && 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 +221,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 +255,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 +263,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 +299,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 +313,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 +323,23 @@
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.
+ */
+ connectStart();
} 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 +350,10 @@
/**** CALLBACK WRAPPERS ************************************************************/
static void
-fwdStartCompleteWrapper(FwdServer * servers, void *data)
+fwdStartCompleteWrapper(Comm::Paths * unused, void *data)
{
FwdState *fwd = (FwdState *) data;
- fwd->startComplete(servers);
+ fwd->startComplete();
}
static void
@@ -358,12 +363,14 @@
fwd->serverClosed(fd);
}
+#if 0
static void
fwdConnectStartWrapper(void *data)
{
FwdState *fwd = (FwdState *) data;
fwd->connectStart();
}
+#endif
#if USE_SSL
static void
@@ -372,31 +379,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_err_t status, int xerrno, void *data)
+{
+ FwdState *fwd = (FwdState *) data;
+ fwd->connectDone(conn, status, xerrno);
}
/**** PRIVATE *****************************************************************/
@@ -487,9 +476,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,39 +496,22 @@
}
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 */
+ if (err) {
+ errorStateFree(err);
+ err = NULL;
}
- }
-
- /* Ditch error page if it was created before.
- * A new one will be created if there's another problem */
- if (err) {
- errorStateFree(err);
- 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;
+
+ connectStart();
+ return;
+ }
+ // else bail. no more paths possible to try.
}
if (!err && shutting_down) {
@@ -550,9 +525,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();
}
@@ -560,7 +534,6 @@
void
FwdState::negotiateSSL(int fd)
{
- FwdServer *fs = servers;
SSL *ssl = fd_table[fd].ssl;
int ret;
@@ -592,21 +565,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();
@@ -615,11 +588,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);
@@ -679,189 +651,162 @@
#endif
void
-FwdState::connectDone(int aServerFD, const DnsLookupDetails &dns, comm_err_t status, int xerrno)
+FwdState::connectDone(Comm::ConnectionPointer &conn, 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);
+ 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();
+ }
+ retryOrBail();
+ 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());
+
+ 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.
+ * And whenever forwarding needs to attempt a new connection (routing failover)
+ * We have a vector of possible localIP->remoteIP 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, 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);
-
- // TODO: Avoid this if %<lp is not used? F->local_port is often cached.
- request->hier.peer_local_port = comm_local_port(fd);
+ comm_add_close_handler(conn->fd, fwdServerClosedWrapper, this);
dispatch();
-
return;
}
@@ -869,98 +814,26 @@
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));
+ ConnOpener *cs = new ConnOpener(paths[0], call, ctimeout);
+ cs->setHost(host);
+ AsyncJob::AsyncStart(cs);
}
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);
@@ -983,10 +856,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.
@@ -1005,18 +878,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;
@@ -1071,7 +944,7 @@
* transient (network) error; its a bug.
*/
flags.dont_retry = 1;
- comm_close(server_fd);
+ paths[0]->close();
break;
}
}
@@ -1089,7 +962,6 @@
FwdState::reforward()
{
StoreEntry *e = entry;
- FwdServer *fs = servers;
http_status s;
assert(e->store_status == STORE_PENDING);
assert(e->mem_obj);
@@ -1098,10 +970,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;
}
@@ -1114,19 +986,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);
}
@@ -1195,22 +1063,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)
@@ -1238,9 +1110,7 @@
if (status > HTTP_INVALID_HEADER)
return;
- assert(tries);
-
- tries--;
+ assert(tries >= 0);
if (tries > MAX_FWD_STATS_IDX)
tries = MAX_FWD_STATS_IDX;
@@ -1248,17 +1118,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
@@ -1277,54 +1136,30 @@
{
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);
+ request->hier.peer_local_port = paths[0]->local.GetPort();
+
+ 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
@@ -1342,27 +1177,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
@@ -1374,7 +1221,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-27 11:31:31 +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_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-06-10 21:38:03 +0000
+++ src/ftp.cc 2010-07-17 08:17:06 +0000
@@ -34,9 +34,9 @@
#include "squid.h"
#include "comm.h"
+#include "comm/ConnOpener.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));
+ ConnOpener *cs = new ConnOpener(conn, call, Config.Timeout.connect);
+ cs->setHost(ftpState->data.host);
+ AsyncJob::AsyncStart(cs);
}
/** \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,22 @@
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));
+ ConnOpener *cs = new ConnOpener(conn, call, Config.Timeout.connect);
+ cs->setHost(ftpState->data.host);
+ AsyncJob::AsyncStart(cs);
}
void
-FtpStateData::ftpPasvCallback(int fd, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
+FtpStateData::ftpPasvCallback(Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
{
FtpStateData *ftpState = (FtpStateData *)data;
debugs(9, 3, HERE);
- ftpState->request->recordLookup(dns);
if (status != COMM_OK) {
debugs(9, 2, HERE << "Failed to connect. Retrying without PASV.");
@@ -2937,16 +2952,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 +2984,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-06-28 05:13:07 +0000
+++ src/http.cc 2010-07-13 08:38:07 +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);
@@ -1361,7 +1360,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-07-06 23:09:44 +0000
+++ src/icp_v2.cc 2010-07-13 08:38:07 +0000
@@ -36,20 +36,20 @@
*/
#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 "ipc/StartListening.h"
#include "rfc1738.h"
+#include "Store.h"
+#include "SquidTime.h"
+#include "SwapDir.h"
+#include "wordlist.h"
/// dials icpIncomingConnectionOpened call
class IcpListeningStartedDialer: public CallDialer,
=== modified file 'src/ident/AclIdent.cc'
--- src/ident/AclIdent.cc 2009-06-02 15:37:40 +0000
+++ src/ident/AclIdent.cc 2010-06-27 11:31:31 +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,12 @@
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;
+ Comm::ConnectionPointer ccp = &cc;
+ Ident::Start(ccp, 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-07-17 08:17:06 +0000
@@ -37,6 +37,9 @@
#if USE_IDENT
#include "comm.h"
+#include "comm/Connection.h"
+#include "comm/ConnOpener.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::ConnectionPointer 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_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,20 @@
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;
+
+ // copy the conn details. We dont want the original FD to be re-used by IDENT.
+ state->conn = conn->copyDetails();
+ // NP: use random port for secure outbound to IDENT_PORT
+ state->conn->local.SetPort(0);
+
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));
+ AsyncJob::AsyncStart(new ConnOpener(state->conn, call, Ident::TheConfig.timeout));
}
void
=== modified file 'src/ident/Ident.h'
--- src/ident/Ident.h 2010-05-02 18:52:45 +0000
+++ src/ident/Ident.h 2010-06-27 11:31:31 +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/ipc/Port.cc'
--- src/ipc/Port.cc 2010-07-06 18:58:38 +0000
+++ src/ipc/Port.cc 2010-07-15 11:50:37 +0000
@@ -5,9 +5,10 @@
*
*/
-
#include "config.h"
+#include "comm.h"
#include "CommCalls.h"
+#include "comm/Connection.h"
#include "ipc/Port.h"
const char Ipc::coordinatorAddr[] = DEFAULT_PREFIX "/var/run/coordinator.ipc";
=== modified file 'src/ipc/UdsOp.cc'
--- src/ipc/UdsOp.cc 2010-07-06 23:09:44 +0000
+++ src/ipc/UdsOp.cc 2010-07-15 11:50:37 +0000
@@ -4,12 +4,11 @@
* DEBUG: section 54 Interprocess Communication
*
*/
-
-
#include "config.h"
+#include "base/TextException.h"
#include "comm.h"
#include "CommCalls.h"
-#include "base/TextException.h"
+#include "comm/Connection.h"
#include "ipc/UdsOp.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-07-07 16:41:03 +0000
+++ src/main.cc 2010-07-13 08:38:07 +0000
@@ -77,7 +77,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-07-17 08:17:06 +0000
@@ -33,8 +33,10 @@
#include "squid.h"
#include "ProtoPort.h"
#include "acl/FilledChecklist.h"
+#include "comm/Connection.h"
+#include "comm/ConnOpener.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,43 @@
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 > 0)
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. */
+ 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);
+
+ p->testing_now++;
+
+ AsyncCall::Pointer call = commCbCall(15,3, "peerProbeConnectDone", CommConnectCbPtrFun(peerProbeConnectDone, p));
+ ConnOpener *cs = new ConnOpener(conn, call, ctimeout);
+ cs->setHost(p->host);
+ AsyncJob::AsyncStart(cs);
+ }
p->stats.last_connect_probe = squid_curtime;
- commConnectStart(p->test_fd,
- p->host,
- p->http_port,
- peerProbeConnectDone,
- p);
-
return ret;
}
static void
-peerProbeConnectDone(int fd, const DnsLookupDetails &, comm_err_t status, int xerrno, void *data)
+peerProbeConnectDone(Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
{
peer *p = (peer*)data;
@@ -1413,9 +1390,8 @@
peerConnectFailedSilent(p);
}
- comm_remove_close_handler(fd, &peerProbeClosed, p);
- comm_close(fd);
- p->test_fd = -1;
+ conn->close();
+ p->testing_now--;
return;
}
=== modified file 'src/peer_select.cc'
--- src/peer_select.cc 2010-01-30 00:30:56 +0000
+++ src/peer_select.cc 2010-06-27 11:31:31 +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::Paths * 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() < (unsigned int)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->size() >= (unsigned int)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-07-06 23:09:44 +0000
+++ src/protos.h 2010-07-13 08:38:07 +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-07-06 23:09:44 +0000
+++ src/snmp_core.cc 2010-07-13 08:38:07 +0000
@@ -33,6 +33,7 @@
#include "acl/FilledChecklist.h"
#include "cache_snmp.h"
#include "comm.h"
+#include "comm/Connection.h"
#include "ipc/StartListening.h"
#include "compat/strsep.h"
#include "ip/Address.h"
=== modified file 'src/structs.h'
--- src/structs.h 2010-07-06 23:09:44 +0000
+++ src/structs.h 2010-07-13 08:38:07 +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;
@@ -912,7 +912,7 @@
int n_addresses;
int rr_count;
peer *next;
- int test_fd;
+ int 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-07-17 08:17:06 +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/ConnOpener.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::Paths paths;
class Connection
{
@@ -173,7 +176,7 @@
assert(tunnelState != NULL);
assert(tunnelState->noConnections());
safe_free(tunnelState->url);
- FwdState::serversFree(&tunnelState->servers);
+ 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
@@ -464,42 +467,6 @@
}
static void
-tunnelConnectTimeout(int fd, void *data)
-{
- TunnelStateData *tunnelState = (TunnelStateData *)data;
- 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);
- else if (Config.onoff.log_ip_on_direct)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- fd_table[tunnelState->server.fd()].ipaddr);
- else
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- tunnelState->host);
- } else
- debugs(26, 1, "tunnelConnectTimeout(): tunnelState->servers is NULL");
-
- err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
-
- *tunnelState->status_ptr = HTTP_SERVICE_UNAVAILABLE;
-
- err->xerrno = ETIMEDOUT;
-
- err->port = tunnelState->port;
-
- err->callback = tunnelErrorComplete;
-
- err->callback_data = tunnelState;
-
- errorSend(tunnelState->client.fd(), err);
- comm_close(fd);
-}
-
-static void
tunnelConnectedWriteDone(int fd, char *buf, size_t size, comm_err_t flag, int xerrno, void *data)
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
@@ -553,52 +520,72 @@
static void
-tunnelConnectDone(int fdnotused, const DnsLookupDetails &dns, comm_err_t status, int xerrno, void *data)
+tunnelConnectDone(Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
HttpRequest *request = tunnelState->request;
ErrorState *err = NULL;
- request->recordLookup(dns);
+#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 (tunnelState->servers->_peer)
- hierarchyNote(&tunnelState->request->hier, tunnelState->servers->code,
- tunnelState->servers->_peer->host);
+ 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);
-
- 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) {
- err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
- *tunnelState->status_ptr = HTTP_SERVICE_UNAVAILABLE;
- err->xerrno = xerrno;
- err->port = tunnelState->port;
- err->callback = tunnelErrorComplete;
- err->callback_data = tunnelState;
- errorSend(tunnelState->client.fd(), err);
+ hierarchyNote(&tunnelState->request->hier, conn->peer_type, tunnelState->host);
+
+ if (status != COMM_OK) {
+ /* At this point only the TCP handshake has failed. no data has been passed.
+ * we are allowed to re-try the TCP-level connection to alternate IPs for CONNECT.
+ */
+ tunnelState->paths.shift();
+ if (status != COMM_TIMEOUT && tunnelState->paths.size() > 0) {
+ /* Try another IP of this destination host */
+ AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
+ ConnOpener *cs = new ConnOpener(tunnelState->paths[0], call, Config.Timeout.connect);
+ cs->setHost(tunnelState->url);
+ AsyncJob::AsyncStart(cs);
+ } else {
+ err = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
+ *tunnelState->status_ptr = HTTP_SERVICE_UNAVAILABLE;
+ err->xerrno = xerrno;
+ // 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 +593,6 @@
{
/* Create state structure. */
TunnelStateData *tunnelState = NULL;
- int sock;
ErrorState *err = NULL;
int answer;
int fd = http->getConn()->fd;
@@ -639,43 +625,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 +642,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 +684,12 @@
}
static void
-tunnelPeerSelectComplete(FwdServer * fs, void *data)
+tunnelPeerSelectComplete(Comm::Paths *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 +699,10 @@
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));
+ ConnOpener *cs = new ConnOpener(tunnelState->paths[0], call, Config.Timeout.connect);
+ cs->setHost(tunnelState->url);
+ AsyncJob::AsyncStart(cs);
}
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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
2yp1aTt8rDuiw0IC9klTZwNJvRzrFw7b1eSecbXueFYaHliTE0AYhpMHCChSSDGQAIEVemN4Z4Bv
yubK2bNWm5wyFIIcpjIQeLaJQEkQpPTIbwZbh9GtKiCBZEGkaI0RAPj9b9zl9n92z8iy7Ci6i57n
xGILiDudiCYcEFhyp8DEcYkYF5eeQ5B8D7ERA+aB4oDCY2mK2JmWMjYbSJ460tA2kr/LfXzbGvSW
XaM+dvR7trXeTS6dPXR/fX7/fr/Zxb4/BcOvQvO5yO4MdzmMeAMZlCp6FDmLAqniVC4mMUKsNWyb
9rhVVco2f73+dGrKTZo/Rsspq7TSdrv2uu0vHbLdq4atmjV3301YaJNHSb9EF13pN0u2fyhGnrX3
/JGGq1bX2dtmz4883WdMuHtRMq+Ny7lg0bsmi7dy2WfPiVHXUklnbhuu2VcKNlWrRVoq118bOGre
mNVnbd2xfHS7DDwk6alirdq6JEvhtzL9I5cZeXbTtfT250Ld8PWMHumbLz+E9Iwpc2Hm+O9qyml3
t4PtxT5XYY/Tppx4rTbLbPXt687XYW5LpjnpjWUj48mzac+awvpI7fO9csr6dcMsV2tll8Xa0+r5
LZFJVfw5tXzW9fl1pTlqeFvC4h5aEKLn685uq82521zlrg2N3b4a4adPW/ivW+hpff8te3Xpm7W5
dmtHh04rza6sNUx6ZvOt7efWPO/jxim2HXTO7uvhlLDVTMpSPSN9qqd/Suvt2uksSw9otLzzlhz0
+F+CP8Sp5HsSNBy84FPI8z1Jlip8HGGIFSRR+T1R/P+d1njpyy7cOXSzRy4aLKJuntuw3SbN1lEn
Ca7dJZlwo2bsMP1RpjK6ta/ds0SSUdPPJrJOWrtRI8O03KRNnOzVJXxlhTGMJz7ZeP3o3fmqKvT0
m1XbsvpV7KsPbxo1LmXbR40VeeVUVVrlZwuy1WdGrhZsh+pA4vR8dLrW2KLqvjxu9LLMMOlGWy6r
Lkmuw9euCy6tdWp6SK18VZeiZpG/WvSjRSdZu1GXbh9kRhVdVoys3SVbOm6jY7SWKKGRMgsXCyF6
iJy7sdjxxViULDS+TKoopMhjuVQOhRIqLegKHOKJxV3HQ4mKG6ATGEhDFBiKIxSCRGJFWDJFCCqK
Cikik4PIyV/KT5QPQjYAAvOkOxLGEYBvUaCWvpssCMzI3IGS50ozKFwoAa0VEwLgm0b2jYmiRngc
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
Wn+Wquf7xdyRThQkAlJ1d4A=
Received on Sun Jul 18 2010 - 10:52:42 MDT
This archive was generated by hypermail 2.2.0 : Mon Jul 19 2010 - 12:00:08 MDT