]> git.ipfire.org Git - thirdparty/squid.git/blame - src/tunnel.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / tunnel.cc
CommitLineData
95d659f0 1
983061ed 2/*
30a4f2a8 3 * DEBUG: section 26 Secure Sockets Layer Proxy
4 * AUTHOR: Duane Wessels
5 *
2b6662ba 6 * SQUID Web Proxy Cache http://www.squid-cache.org/
e25c139f 7 * ----------------------------------------------------------
30a4f2a8 8 *
2b6662ba 9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
30a4f2a8 17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
26ac0430 22 *
30a4f2a8 23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
26ac0430 27 *
30a4f2a8 28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
cbdec147 30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
e25c139f 31 *
983061ed 32 */
983061ed 33
582c2af2 34#include "squid.h"
a011edee 35#include "acl/FilledChecklist.h"
08f774de 36#include "base/Vector.h"
a011edee 37#include "CachePeer.h"
a011edee 38#include "client_side.h"
602d9612 39#include "client_side_request.h"
9f518b4a 40#include "comm.h"
cfd66529 41#include "comm/Connection.h"
aed188fd 42#include "comm/ConnOpener.h"
ec41b64c 43#include "comm/Write.h"
a011edee
FC
44#include "errorpage.h"
45#include "fde.h"
e5ee81f0 46#include "http.h"
a011edee 47#include "HttpRequest.h"
46f4b111 48#include "HttpStateFlags.h"
6dc6127b 49#include "ip/QosConfig.h"
a011edee 50#include "MemBuf.h"
cfd66529 51#include "PeerSelectState.h"
4d5904f7 52#include "SquidConfig.h"
e4f1fdae 53#include "StatCounters.h"
4e540555 54#include "tools.h"
582c2af2
FC
55#if USE_DELAY_POOLS
56#include "DelayId.h"
57#endif
58
59#if HAVE_LIMITS_H
60#include <limits.h>
61#endif
21d845b1
FC
62#if HAVE_ERRNO_H
63#include <errno.h>
64#endif
582c2af2 65
8ca1d33d
AJ
66/**
67 * TunnelStateData is the state engine performing the tasks for
68 * setup of a TCP tunnel from an existing open client FD to a server
69 * then shuffling binary data between the resulting FD pair.
70 */
71/*
72 * TODO 1: implement a read/write API on ConnStateData to send/receive blocks
73 * of pre-formatted data. Then we can use that as the client side of the tunnel
74 * instead of re-implementing it here and occasionally getting the ConnStateData
75 * read/write state wrong.
76 *
77 * TODO 2: then convert this into a AsyncJob, possibly a child of 'Server'
78 */
fa34dd97 79class TunnelStateData
62e76326 80{
a46d2c0e 81
82public:
8ca1d33d
AJ
83 TunnelStateData();
84 ~TunnelStateData();
85 TunnelStateData(const TunnelStateData &); // do not implement
86 TunnelStateData &operator =(const TunnelStateData &); // do not implement
a46d2c0e 87
88 class Connection;
e0d28505
AJ
89 static void ReadClient(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data);
90 static void ReadServer(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data);
91 static void WriteClientDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t flag, int xerrno, void *data);
92 static void WriteServerDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t flag, int xerrno, void *data);
a46d2c0e 93
3ed5793b
AR
94 /// Starts reading peer response to our CONNECT request.
95 void readConnectResponse();
96
97 /// Called when we may be done handling a CONNECT exchange with the peer.
98 void connectExchangeCheckpoint();
99
a46d2c0e 100 bool noConnections() const;
983061ed 101 char *url;
8a70cdbb 102 HttpRequest::Pointer request;
06521a10 103 AccessLogEntryPointer al;
00ae51e4 104 Comm::ConnectionList serverDestinations;
62e76326 105
fb046c1b
AJ
106 const char * getHost() const {
107 return (server.conn != NULL && server.conn->getPeer() ? server.conn->getPeer()->host : request->GetHost());
108 };
109
3ed5793b
AR
110 /// Whether we are writing a CONNECT request to a peer.
111 bool waitingForConnectRequest() const { return connectReqWriting; }
112 /// Whether we are reading a CONNECT response from a peer.
113 bool waitingForConnectResponse() const { return connectRespBuf; }
114 /// Whether we are waiting for the CONNECT request/response exchange with the peer.
115 bool waitingForConnectExchange() const { return waitingForConnectRequest() || waitingForConnectResponse(); }
116
117 /// Whether the client sent a CONNECT request to us.
1c8fc082
A
118 bool clientExpectsConnectResponse() const {
119 return !(request != NULL &&
120 (request->flags.interceptTproxy || request->flags.intercepted));
121 }
3ed5793b 122
a46d2c0e 123 class Connection
62e76326 124 {
a46d2c0e 125
126 public:
8a467c4b 127 Connection() : len (0), buf ((char *)xmalloc(SQUID_TCP_SO_RCVBUF)), size_ptr(NULL) {}
a46d2c0e 128
8a467c4b
AJ
129 ~Connection();
130
131 int bytesWanted(int lower=0, int upper = INT_MAX) const;
a46d2c0e 132 void bytesIn(int const &);
9a0a18de 133#if USE_DELAY_POOLS
a46d2c0e 134
135 void setDelayId(DelayId const &);
136#endif
137
138 void error(int const xerrno);
5c926411 139 int debugLevelForError(int const xerrno) const;
3ed5793b
AR
140 /// handles a non-I/O error associated with this Connection
141 void logicError(const char *errMsg);
a46d2c0e 142 void closeIfOpen();
8a467c4b 143 void dataSent (size_t amount);
62e76326 144 int len;
8a467c4b 145 char *buf;
47f6e231 146 int64_t *size_ptr; /* pointer to size in an ConnStateData for logging */
62e76326 147
fb046c1b 148 Comm::ConnectionPointer conn; ///< The currently connected connection.
fb046c1b 149
a46d2c0e 150 private:
9a0a18de 151#if USE_DELAY_POOLS
62e76326 152
a46d2c0e 153 DelayId delayId;
59715b38 154#endif
62e76326 155
a46d2c0e 156 };
157
158 Connection client, server;
159 int *status_ptr; /* pointer to status for logging */
3ed5793b
AR
160 MemBuf *connectRespBuf; ///< accumulates peer CONNECT response when we need it
161 bool connectReqWriting; ///< whether we are writing a CONNECT request to a peer
162
a46d2c0e 163 void copyRead(Connection &from, IOCB *completion);
164
165private:
83564ee7 166 CBDATA_CLASS2(TunnelStateData);
3ed5793b
AR
167 bool keepGoingAfterRead(size_t len, comm_err_t errcode, int xerrno, Connection &from, Connection &to);
168 void copy(size_t len, Connection &from, Connection &to, IOCB *);
169 void handleConnectResponse(const size_t chunkSize);
a46d2c0e 170 void readServer(char *buf, size_t len, comm_err_t errcode, int xerrno);
171 void readClient(char *buf, size_t len, comm_err_t errcode, int xerrno);
172 void writeClientDone(char *buf, size_t len, comm_err_t flag, int xerrno);
173 void writeServerDone(char *buf, size_t len, comm_err_t flag, int xerrno);
3ed5793b
AR
174
175 static void ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data);
176 void readConnectResponseDone(char *buf, size_t len, comm_err_t errcode, int xerrno);
a46d2c0e 177};
983061ed 178
3c4fcf0f 179static const char *const conn_established = "HTTP/1.1 200 Connection established\r\n\r\n";
983061ed 180
11007d4b 181static CNCB tunnelConnectDone;
182static ERCB tunnelErrorComplete;
575d05c4
AJ
183static CLCB tunnelServerClosed;
184static CLCB tunnelClientClosed;
8d77a37c 185static CTCB tunnelTimeout;
11007d4b 186static PSC tunnelPeerSelectComplete;
f01d4b80
AJ
187static void tunnelConnected(const Comm::ConnectionPointer &server, void *);
188static void tunnelRelayConnectRequest(const Comm::ConnectionPointer &server, void *);
30a4f2a8 189
b8d8561b 190static void
575d05c4 191tunnelServerClosed(const CommCloseCbParams &params)
30a4f2a8 192{
575d05c4
AJ
193 TunnelStateData *tunnelState = (TunnelStateData *)params.data;
194 debugs(26, 3, HERE << tunnelState->server.conn);
fb046c1b 195 tunnelState->server.conn = NULL;
62e76326 196
71a2ced6 197 if (tunnelState->noConnections()) {
8ca1d33d 198 delete tunnelState;
26ac0430 199 return;
71a2ced6 200 }
26ac0430 201
71a2ced6 202 if (!tunnelState->server.len) {
fb046c1b 203 tunnelState->client.conn->close();
26ac0430 204 return;
71a2ced6 205 }
30a4f2a8 206}
207
b177367b 208static void
575d05c4 209tunnelClientClosed(const CommCloseCbParams &params)
30a4f2a8 210{
575d05c4
AJ
211 TunnelStateData *tunnelState = (TunnelStateData *)params.data;
212 debugs(26, 3, HERE << tunnelState->client.conn);
fb046c1b 213 tunnelState->client.conn = NULL;
62e76326 214
71a2ced6 215 if (tunnelState->noConnections()) {
8ca1d33d 216 delete tunnelState;
26ac0430 217 return;
71a2ced6 218 }
26ac0430 219
71a2ced6 220 if (!tunnelState->client.len) {
fb046c1b 221 tunnelState->server.conn->close();
26ac0430 222 return;
71a2ced6 223 }
30a4f2a8 224}
983061ed 225
8ca1d33d
AJ
226TunnelStateData::TunnelStateData() :
227 url(NULL),
228 request(NULL),
3ed5793b
AR
229 status_ptr(NULL),
230 connectRespBuf(NULL),
231 connectReqWriting(false)
8ca1d33d
AJ
232{
233 debugs(26, 3, "TunnelStateData constructed this=" << this);
234}
235
236TunnelStateData::~TunnelStateData()
983061ed 237{
8ca1d33d
AJ
238 debugs(26, 3, "TunnelStateData destructed this=" << this);
239 assert(noConnections());
240 xfree(url);
241 serverDestinations.clean();
3ed5793b 242 delete connectRespBuf;
983061ed 243}
244
8a467c4b
AJ
245TunnelStateData::Connection::~Connection()
246{
247 safe_free(buf);
248}
249
a46d2c0e 250int
8a467c4b 251TunnelStateData::Connection::bytesWanted(int lowerbound, int upperbound) const
447e176b 252{
9a0a18de 253#if USE_DELAY_POOLS
8a467c4b 254 return delayId.bytesWanted(lowerbound, upperbound);
a46d2c0e 255#else
8a467c4b
AJ
256
257 return upperbound;
a46d2c0e 258#endif
259}
62e76326 260
a46d2c0e 261void
fa34dd97 262TunnelStateData::Connection::bytesIn(int const &count)
a46d2c0e 263{
fd54d9b2 264 debugs(26, 3, HERE << "len=" << len << " + count=" << count);
9a0a18de 265#if USE_DELAY_POOLS
a46d2c0e 266 delayId.bytesIn(count);
267#endif
62e76326 268
a46d2c0e 269 len += count;
447e176b 270}
62e76326 271
a46d2c0e 272int
fa34dd97 273TunnelStateData::Connection::debugLevelForError(int const xerrno) const
a46d2c0e 274{
275#ifdef ECONNRESET
276
277 if (xerrno == ECONNRESET)
278 return 2;
279
447e176b 280#endif
281
a46d2c0e 282 if (ignoreErrno(xerrno))
283 return 3;
284
285 return 1;
286}
adb78bd4 287
983061ed 288/* Read from server side and queue it for writing to the client */
a46d2c0e 289void
fd54d9b2 290TunnelStateData::ReadServer(const Comm::ConnectionPointer &c, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data)
983061ed 291{
fa34dd97 292 TunnelStateData *tunnelState = (TunnelStateData *)data;
e0d28505 293 assert(cbdataReferenceValid(tunnelState));
fd54d9b2 294 debugs(26, 3, HERE << c);
c4b7a5a9 295
11007d4b 296 tunnelState->readServer(buf, len, errcode, xerrno);
a46d2c0e 297}
62e76326 298
a46d2c0e 299void
fa34dd97 300TunnelStateData::readServer(char *buf, size_t len, comm_err_t errcode, int xerrno)
a46d2c0e 301{
fd54d9b2 302 debugs(26, 3, HERE << server.conn << ", read " << len << " bytes, err=" << errcode);
d01053a2 303
a46d2c0e 304 /*
305 * Bail out early on COMM_ERR_CLOSING
26ac0430 306 * - close handlers will tidy up for us
a46d2c0e 307 */
a55f4cea 308
a46d2c0e 309 if (errcode == COMM_ERR_CLOSING)
310 return;
62e76326 311
ee1679df 312 if (len > 0) {
a46d2c0e 313 server.bytesIn(len);
e4f1fdae
FC
314 kb_incr(&(statCounter.server.all.kbytes_in), len);
315 kb_incr(&(statCounter.server.other.kbytes_in), len);
ee1679df 316 }
62e76326 317
3ed5793b
AR
318 if (keepGoingAfterRead(len, errcode, xerrno, server, client))
319 copy(len, server, client, WriteClientDone);
320}
321
322/// Called when we read [a part of] CONNECT response from the peer
323void
324TunnelStateData::readConnectResponseDone(char *buf, size_t len, comm_err_t errcode, int xerrno)
325{
326 debugs(26, 3, server.conn << ", read " << len << " bytes, err=" << errcode);
327 assert(waitingForConnectResponse());
328
329 if (errcode == COMM_ERR_CLOSING)
330 return;
331
332 if (len > 0) {
333 connectRespBuf->appended(len);
334 server.bytesIn(len);
335 kb_incr(&(statCounter.server.all.kbytes_in), len);
336 kb_incr(&(statCounter.server.other.kbytes_in), len);
337 }
338
339 if (keepGoingAfterRead(len, errcode, xerrno, server, client))
340 handleConnectResponse(len);
341}
342
343/* Read from client side and queue it for writing to the server */
344void
345TunnelStateData::ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data)
346{
347 TunnelStateData *tunnelState = (TunnelStateData *)data;
348 assert (cbdataReferenceValid (tunnelState));
349
350 tunnelState->readConnectResponseDone(buf, len, errcode, xerrno);
351}
352
353/// Parses [possibly incomplete] CONNECT response and reacts to it.
354/// If the tunnel is being closed or more response data is needed, returns false.
355/// Otherwise, the caller should handle the remaining read data, if any.
356void
357TunnelStateData::handleConnectResponse(const size_t chunkSize)
358{
359 assert(waitingForConnectResponse());
360
361 // Ideally, client and server should use MemBuf or better, but current code
362 // never accumulates more than one read when shoveling data (XXX) so it does
1c8fc082 363 // not need to deal with MemBuf complexity. To keep it simple, we use a
3ed5793b
AR
364 // dedicated MemBuf for accumulating CONNECT responses. TODO: When shoveling
365 // is optimized, reuse server.buf for CONNEC response accumulation instead.
366
367 /* mimic the basic parts of HttpStateData::processReplyHeader() */
368 HttpReply rep;
369 Http::StatusCode parseErr = Http::scNone;
370 const bool eof = !chunkSize;
371 const bool parsed = rep.parse(connectRespBuf, eof, &parseErr);
372 if (!parsed) {
373 if (parseErr > 0) { // unrecoverable parsing error
374 server.logicError("malformed CONNECT response from peer");
375 return;
376 }
377
378 // need more data
379 assert(!eof);
380 assert(!parseErr);
381
382 if (!connectRespBuf->hasSpace()) {
383 server.logicError("huge CONNECT response from peer");
384 return;
385 }
386
387 // keep reading
388 readConnectResponse();
389 return;
390 }
391
392 // CONNECT response was successfully parsed
393 *status_ptr = rep.sline.status();
394
395 // bail if we did not get an HTTP 200 (Connection Established) response
396 if (rep.sline.status() != Http::scOkay) {
397 server.logicError("unsupported CONNECT response status code");
398 return;
399 }
400
401 if (rep.hdr_sz < connectRespBuf->contentSize()) {
402 // preserve bytes that the server already sent after the CONNECT response
403 server.len = connectRespBuf->contentSize() - rep.hdr_sz;
404 memcpy(server.buf, connectRespBuf->content()+rep.hdr_sz, server.len);
405 } else {
406 // reset; delay pools were using this field to throttle CONNECT response
407 server.len = 0;
408 }
409
410 delete connectRespBuf;
411 connectRespBuf = NULL;
412 connectExchangeCheckpoint();
413}
414
415void
416TunnelStateData::Connection::logicError(const char *errMsg)
417{
418 debugs(50, 3, conn << " closing on error: " << errMsg);
419 conn->close();
a46d2c0e 420}
421
422void
fa34dd97 423TunnelStateData::Connection::error(int const xerrno)
a46d2c0e 424{
425 /* XXX fixme xstrerror and xerrno... */
426 errno = xerrno;
427
e0d28505 428 debugs(50, debugLevelForError(xerrno), HERE << conn << ": read/write failure: " << xstrerror());
62e76326 429
a46d2c0e 430 if (!ignoreErrno(xerrno))
fb046c1b 431 conn->close();
983061ed 432}
433
434/* Read from client side and queue it for writing to the server */
a46d2c0e 435void
e0d28505 436TunnelStateData::ReadClient(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data)
983061ed 437{
fa34dd97 438 TunnelStateData *tunnelState = (TunnelStateData *)data;
11007d4b 439 assert (cbdataReferenceValid (tunnelState));
62e76326 440
11007d4b 441 tunnelState->readClient(buf, len, errcode, xerrno);
a46d2c0e 442}
62e76326 443
a46d2c0e 444void
fa34dd97 445TunnelStateData::readClient(char *buf, size_t len, comm_err_t errcode, int xerrno)
a46d2c0e 446{
fd54d9b2 447 debugs(26, 3, HERE << client.conn << ", read " << len << " bytes, err=" << errcode);
d01053a2 448
a46d2c0e 449 /*
450 * Bail out early on COMM_ERR_CLOSING
26ac0430 451 * - close handlers will tidy up for us
a46d2c0e 452 */
a55f4cea 453
a46d2c0e 454 if (errcode == COMM_ERR_CLOSING)
455 return;
a55f4cea 456
a46d2c0e 457 if (len > 0) {
458 client.bytesIn(len);
e4f1fdae 459 kb_incr(&(statCounter.client_http.kbytes_in), len);
a46d2c0e 460 }
62e76326 461
3ed5793b
AR
462 if (keepGoingAfterRead(len, errcode, xerrno, client, server))
463 copy(len, client, server, WriteServerDone);
a46d2c0e 464}
62e76326 465
3ed5793b
AR
466/// Updates state after reading from client or server.
467/// Returns whether the caller should use the data just read.
468bool
469TunnelStateData::keepGoingAfterRead(size_t len, comm_err_t errcode, int xerrno, Connection &from, Connection &to)
a46d2c0e 470{
fd54d9b2
AJ
471 debugs(26, 3, HERE << "from={" << from.conn << "}, to={" << to.conn << "}");
472
a46d2c0e 473 /* I think this is to prevent free-while-in-a-callback behaviour
26ac0430 474 * - RBC 20030229
fb046c1b 475 * from.conn->close() / to.conn->close() done here trigger close callbacks which may free TunnelStateData
a46d2c0e 476 */
83564ee7 477 const CbcPointer<TunnelStateData> safetyLock(this);
62e76326 478
60dafd5e 479 /* Bump the source connection read timeout on any activity */
8d77a37c
AJ
480 if (Comm::IsConnOpen(from.conn)) {
481 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
dc49061a 482 CommTimeoutCbPtrFun(tunnelTimeout, this));
8d77a37c
AJ
483 commSetConnTimeout(from.conn, Config.Timeout.read, timeoutCall);
484 }
99c02c10 485
2677c3b1
JPM
486 /* Bump the dest connection read timeout on any activity */
487 /* see Bug 3659: tunnels can be weird, with very long one-way transfers */
488 if (Comm::IsConnOpen(to.conn)) {
489 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
490 CommTimeoutCbPtrFun(tunnelTimeout, this));
491 commSetConnTimeout(to.conn, Config.Timeout.read, timeoutCall);
492 }
493
58c0b17d 494 if (errcode)
a46d2c0e 495 from.error (xerrno);
60dafd5e 496 else if (len == 0 || !Comm::IsConnOpen(to.conn)) {
fd54d9b2 497 debugs(26, 3, HERE << "Nothing to write or client gone. Terminate the tunnel.");
fb046c1b 498 from.conn->close();
62e76326 499
60dafd5e 500 /* Only close the remote end if we've finished queueing data to it */
97c81191 501 if (from.len == 0 && Comm::IsConnOpen(to.conn) ) {
fb046c1b 502 to.conn->close();
c4b7a5a9 503 }
ec41b64c 504 } else if (cbdataReferenceValid(this)) {
3ed5793b
AR
505 return true;
506 }
507
508 return false;
509}
510
511void
512TunnelStateData::copy(size_t len, Connection &from, Connection &to, IOCB *completion)
513{
1c8fc082
A
514 debugs(26, 3, HERE << "Schedule Write");
515 AsyncCall::Pointer call = commCbCall(5,5, "TunnelBlindCopyWriteHandler",
516 CommIoCbPtrFun(completion, this));
517 Comm::Write(to.conn, from.buf, len, call, NULL);
983061ed 518}
519
520/* Writes data from the client buffer to the server side */
a46d2c0e 521void
e0d28505 522TunnelStateData::WriteServerDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
983061ed 523{
fa34dd97 524 TunnelStateData *tunnelState = (TunnelStateData *)data;
11007d4b 525 assert (cbdataReferenceValid (tunnelState));
a46d2c0e 526
11007d4b 527 tunnelState->writeServerDone(buf, len, flag, xerrno);
a46d2c0e 528}
a55f4cea 529
a46d2c0e 530void
fa34dd97 531TunnelStateData::writeServerDone(char *buf, size_t len, comm_err_t flag, int xerrno)
a46d2c0e 532{
fd54d9b2 533 debugs(26, 3, HERE << server.conn << ", " << len << " bytes written, flag=" << flag);
62e76326 534
5dacdf3f 535 /* Error? */
58c0b17d 536 if (flag != COMM_OK) {
fd54d9b2
AJ
537 if (flag != COMM_ERR_CLOSING) {
538 debugs(26, 4, HERE << "calling TunnelStateData::server.error(" << xerrno <<")");
58c0b17d 539 server.error(xerrno); // may call comm_close
fd54d9b2 540 }
5dacdf3f 541 return;
c4b7a5a9 542 }
62e76326 543
5dacdf3f 544 /* EOF? */
a46d2c0e 545 if (len == 0) {
fd54d9b2 546 debugs(26, 4, HERE << "No read input. Closing server connection.");
fb046c1b 547 server.conn->close();
a46d2c0e 548 return;
549 }
62e76326 550
5dacdf3f 551 /* Valid data */
e4f1fdae
FC
552 kb_incr(&(statCounter.server.all.kbytes_out), len);
553 kb_incr(&(statCounter.server.other.kbytes_out), len);
5dacdf3f 554 client.dataSent(len);
555
a46d2c0e 556 /* If the other end has closed, so should we */
97c81191 557 if (!Comm::IsConnOpen(client.conn)) {
fd54d9b2 558 debugs(26, 4, HERE << "Client gone away. Shutting down server connection.");
fb046c1b 559 server.conn->close();
a55f4cea 560 return;
c4b7a5a9 561 }
62e76326 562
83564ee7 563 const CbcPointer<TunnelStateData> safetyLock(this); /* ??? should be locked by the caller... */
a46d2c0e 564
5dacdf3f 565 if (cbdataReferenceValid(this))
a46d2c0e 566 copyRead(client, ReadClient);
983061ed 567}
568
569/* Writes data from the server buffer to the client side */
a46d2c0e 570void
e0d28505 571TunnelStateData::WriteClientDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
983061ed 572{
fa34dd97 573 TunnelStateData *tunnelState = (TunnelStateData *)data;
11007d4b 574 assert (cbdataReferenceValid (tunnelState));
a46d2c0e 575
11007d4b 576 tunnelState->writeClientDone(buf, len, flag, xerrno);
a46d2c0e 577}
578
579void
e0d28505 580TunnelStateData::Connection::dataSent(size_t amount)
a46d2c0e 581{
fd54d9b2 582 debugs(26, 3, HERE << "len=" << len << " - amount=" << amount);
a46d2c0e 583 assert(amount == (size_t)len);
584 len =0;
585 /* increment total object size */
586
587 if (size_ptr)
588 *size_ptr += amount;
589}
590
591void
fa34dd97 592TunnelStateData::writeClientDone(char *buf, size_t len, comm_err_t flag, int xerrno)
a46d2c0e 593{
fd54d9b2 594 debugs(26, 3, HERE << client.conn << ", " << len << " bytes written, flag=" << flag);
62e76326 595
5dacdf3f 596 /* Error? */
58c0b17d 597 if (flag != COMM_OK) {
fd54d9b2
AJ
598 if (flag != COMM_ERR_CLOSING) {
599 debugs(26, 4, HERE << "Closing client connection due to comm flags.");
58c0b17d 600 client.error(xerrno); // may call comm_close
fd54d9b2 601 }
5dacdf3f 602 return;
c4b7a5a9 603 }
62e76326 604
5dacdf3f 605 /* EOF? */
a46d2c0e 606 if (len == 0) {
fd54d9b2 607 debugs(26, 4, HERE << "Closing client connection due to 0 byte read.");
fb046c1b 608 client.conn->close();
62e76326 609 return;
983061ed 610 }
62e76326 611
5dacdf3f 612 /* Valid data */
e4f1fdae 613 kb_incr(&(statCounter.client_http.kbytes_out), len);
5dacdf3f 614 server.dataSent(len);
615
a46d2c0e 616 /* If the other end has closed, so should we */
97c81191 617 if (!Comm::IsConnOpen(server.conn)) {
fd54d9b2 618 debugs(26, 4, HERE << "Server has gone away. Terminating client connection.");
fb046c1b 619 client.conn->close();
a55f4cea 620 return;
983061ed 621 }
62e76326 622
83564ee7 623 CbcPointer<TunnelStateData> safetyLock(this); /* ??? should be locked by the caller... */
a55f4cea 624
5dacdf3f 625 if (cbdataReferenceValid(this))
a46d2c0e 626 copyRead(server, ReadServer);
983061ed 627}
628
b8d8561b 629static void
8d77a37c 630tunnelTimeout(const CommTimeoutCbParams &io)
983061ed 631{
8d77a37c
AJ
632 TunnelStateData *tunnelState = static_cast<TunnelStateData *>(io.data);
633 debugs(26, 3, HERE << io.conn);
11007d4b 634 /* Temporary lock to protect our own feets (comm_close -> tunnelClientClosed -> Free) */
83564ee7 635 CbcPointer<TunnelStateData> safetyLock(tunnelState);
a55f4cea 636
11007d4b 637 tunnelState->client.closeIfOpen();
638 tunnelState->server.closeIfOpen();
a46d2c0e 639}
62e76326 640
a46d2c0e 641void
fa34dd97 642TunnelStateData::Connection::closeIfOpen()
a46d2c0e 643{
97c81191 644 if (Comm::IsConnOpen(conn))
fb046c1b 645 conn->close();
a46d2c0e 646}
647
648void
fa34dd97 649TunnelStateData::copyRead(Connection &from, IOCB *completion)
a46d2c0e 650{
651 assert(from.len == 0);
fd54d9b2 652 AsyncCall::Pointer call = commCbCall(5,4, "TunnelBlindCopyReadHandler",
abd8f140 653 CommIoCbPtrFun(completion, this));
8a467c4b 654 comm_read(from.conn, from.buf, from.bytesWanted(1, SQUID_TCP_SO_RCVBUF), call);
983061ed 655}
656
3ed5793b
AR
657void
658TunnelStateData::readConnectResponse()
659{
660 assert(waitingForConnectResponse());
661
662 AsyncCall::Pointer call = commCbCall(5,4, "readConnectResponseDone",
663 CommIoCbPtrFun(ReadConnectResponseDone, this));
664 comm_read(server.conn, connectRespBuf->space(),
665 server.bytesWanted(1, connectRespBuf->spaceSize()), call);
666}
667
379e8c1c 668/**
87f237a9 669 * Set the HTTP status for this request and sets the read handlers for client
379e8c1c
AR
670 * and server side connections.
671 */
672static void
673tunnelStartShoveling(TunnelStateData *tunnelState)
674{
3ed5793b 675 assert(!tunnelState->waitingForConnectExchange());
955394ce 676 *tunnelState->status_ptr = Http::scOkay;
379e8c1c 677 if (cbdataReferenceValid(tunnelState)) {
3ed5793b 678 if (!tunnelState->server.len)
1c8fc082 679 tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer);
3ed5793b
AR
680 else
681 tunnelState->copy(tunnelState->server.len, tunnelState->server, tunnelState->client, TunnelStateData::WriteClientDone);
379e8c1c
AR
682 tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient);
683 }
684}
685
b0388924
AJ
686/**
687 * All the pieces we need to write to client and/or server connection
1b76e6c1 688 * have been written.
379e8c1c 689 * Call the tunnelStartShoveling to start the blind pump.
b0388924 690 */
c4b7a5a9 691static void
e0d28505 692tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data)
c4b7a5a9 693{
fa34dd97 694 TunnelStateData *tunnelState = (TunnelStateData *)data;
fd54d9b2 695 debugs(26, 3, HERE << conn << ", flag=" << flag);
62e76326 696
697 if (flag != COMM_OK) {
955394ce 698 *tunnelState->status_ptr = Http::scInternalServerError;
e0d28505 699 tunnelErrorComplete(conn->fd, data, 0);
62e76326 700 return;
701 }
702
379e8c1c 703 tunnelStartShoveling(tunnelState);
c4b7a5a9 704}
705
3ed5793b
AR
706/// Called when we are done writing CONNECT request to a peer.
707static void
708tunnelConnectReqWriteDone(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data)
709{
710 TunnelStateData *tunnelState = (TunnelStateData *)data;
711 debugs(26, 3, conn << ", flag=" << flag);
712 assert(tunnelState->waitingForConnectRequest());
713
714 if (flag != COMM_OK) {
715 *tunnelState->status_ptr = Http::scInternalServerError;
716 tunnelErrorComplete(conn->fd, data, 0);
717 return;
718 }
719
720 tunnelState->connectReqWriting = false;
721 tunnelState->connectExchangeCheckpoint();
722}
723
724void
725TunnelStateData::connectExchangeCheckpoint()
726{
727 if (waitingForConnectResponse()) {
728 debugs(26, 5, "still reading CONNECT response on " << server.conn);
729 } else if (waitingForConnectRequest()) {
730 debugs(26, 5, "still writing CONNECT request on " << server.conn);
731 } else {
732 assert(!waitingForConnectExchange());
733 debugs(26, 3, "done with CONNECT exchange on " << server.conn);
734 tunnelConnected(server.conn, this);
735 }
736}
737
c4b7a5a9 738/*
1b76e6c1 739 * handle the write completion from a proxy request to an upstream origin
c4b7a5a9 740 */
b8d8561b 741static void
f01d4b80 742tunnelConnected(const Comm::ConnectionPointer &server, void *data)
983061ed 743{
fa34dd97 744 TunnelStateData *tunnelState = (TunnelStateData *)data;
f01d4b80 745 debugs(26, 3, HERE << server << ", tunnelState=" << tunnelState);
379e8c1c 746
3ed5793b 747 if (!tunnelState->clientExpectsConnectResponse())
379e8c1c
AR
748 tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet
749 else {
750 AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
751 CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
752 Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL);
753 }
983061ed 754}
755
b8d8561b 756static void
fd54d9b2 757tunnelErrorComplete(int fd/*const Comm::ConnectionPointer &*/, void *data, size_t)
30a4f2a8 758{
fa34dd97 759 TunnelStateData *tunnelState = (TunnelStateData *)data;
fd54d9b2 760 debugs(26, 3, HERE << "FD " << fd);
11007d4b 761 assert(tunnelState != NULL);
762 /* temporary lock to save our own feets (comm_close -> tunnelClientClosed -> Free) */
83564ee7 763 CbcPointer<TunnelStateData> safetyLock(tunnelState);
62e76326 764
97c81191 765 if (Comm::IsConnOpen(tunnelState->client.conn))
fb046c1b 766 tunnelState->client.conn->close();
62e76326 767
97c81191 768 if (Comm::IsConnOpen(tunnelState->server.conn))
fb046c1b 769 tunnelState->server.conn->close();
30a4f2a8 770}
771
b8d8561b 772static void
f01d4b80 773tunnelConnectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
983061ed 774{
fa34dd97 775 TunnelStateData *tunnelState = (TunnelStateData *)data;
cfd66529
AJ
776
777 if (status != COMM_OK) {
fd54d9b2 778 debugs(26, 4, HERE << conn << ", comm failure recovery.");
aed188fd
AJ
779 /* At this point only the TCP handshake has failed. no data has been passed.
780 * we are allowed to re-try the TCP-level connection to alternate IPs for CONNECT.
781 */
00ae51e4
AJ
782 tunnelState->serverDestinations.shift();
783 if (status != COMM_TIMEOUT && tunnelState->serverDestinations.size() > 0) {
aed188fd 784 /* Try another IP of this destination host */
6dc6127b
SW
785
786 if (Ip::Qos::TheConfig.isAclTosActive()) {
8a70cdbb 787 tunnelState->serverDestinations[0]->tos = GetTosToServer(tunnelState->request.getRaw());
6dc6127b
SW
788 }
789
790#if SO_MARK && USE_LIBCAP
8a70cdbb 791 tunnelState->serverDestinations[0]->nfmark = GetNfmarkToServer(tunnelState->request.getRaw());
6dc6127b
SW
792#endif
793
fd54d9b2 794 debugs(26, 4, HERE << "retry with : " << tunnelState->serverDestinations[0]);
aed188fd 795 AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
00ae51e4 796 Comm::ConnOpener *cs = new Comm::ConnOpener(tunnelState->serverDestinations[0], call, Config.Timeout.connect);
aed188fd 797 cs->setHost(tunnelState->url);
855150a4 798 AsyncJob::Start(cs);
aed188fd 799 } else {
fd54d9b2 800 debugs(26, 4, HERE << "terminate with error.");
8a70cdbb 801 ErrorState *err = new ErrorState(ERR_CONNECT_FAIL, Http::scServiceUnavailable, tunnelState->request.getRaw());
955394ce 802 *tunnelState->status_ptr = Http::scServiceUnavailable;
aed188fd
AJ
803 err->xerrno = xerrno;
804 // on timeout is this still: err->xerrno = ETIMEDOUT;
4dd643d5 805 err->port = conn->remote.port();
aed188fd
AJ
806 err->callback = tunnelErrorComplete;
807 err->callback_data = tunnelState;
e0d28505 808 errorSend(tunnelState->client.conn, err);
aed188fd 809 }
cfd66529
AJ
810 return;
811 }
812
4beb4bab
AJ
813#if USE_DELAY_POOLS
814 /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
815 if (conn->getPeer() && conn->getPeer()->options.no_delay)
816 tunnelState->server.setDelayId(DelayId());
817#endif
818
a14f38d0 819 tunnelState->request->hier.note(conn, tunnelState->getHost());
4beb4bab 820
fb046c1b 821 tunnelState->server.conn = conn;
4beb4bab 822 tunnelState->request->peer_host = conn->getPeer() ? conn->getPeer()->host : NULL;
fb046c1b 823 comm_add_close_handler(conn->fd, tunnelServerClosed, tunnelState);
cfd66529 824
fd54d9b2 825 debugs(26, 4, HERE << "determine post-connect handling pathway.");
739b352a
AJ
826 if (conn->getPeer()) {
827 tunnelState->request->peer_login = conn->getPeer()->login;
e857372a 828 tunnelState->request->flags.proxying = !(conn->getPeer()->options.originserver);
fe40a877 829 } else {
cfd66529 830 tunnelState->request->peer_login = NULL;
e857372a 831 tunnelState->request->flags.proxying = false;
cfd66529 832 }
62e76326 833
45e5102d 834 if (tunnelState->request->flags.proxying)
fb046c1b 835 tunnelRelayConnectRequest(conn, tunnelState);
cfd66529 836 else {
fb046c1b 837 tunnelConnected(conn, tunnelState);
983061ed 838 }
cfd66529 839
8d77a37c 840 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
dc49061a 841 CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
8d77a37c 842 commSetConnTimeout(conn, Config.Timeout.read, timeoutCall);
983061ed 843}
30a4f2a8 844
82afb125
FC
845tos_t GetTosToServer(HttpRequest * request);
846nfmark_t GetNfmarkToServer(HttpRequest * request);
425de4c8 847
770f051d 848void
06521a10 849tunnelStart(ClientHttpRequest * http, int64_t * size_ptr, int *status_ptr, const AccessLogEntryPointer &al)
30a4f2a8 850{
fd54d9b2 851 debugs(26, 3, HERE);
30a4f2a8 852 /* Create state structure. */
fa34dd97 853 TunnelStateData *tunnelState = NULL;
9b312a19 854 ErrorState *err = NULL;
190154cf 855 HttpRequest *request = http->request;
d5964d58 856 char *url = http->uri;
fb046c1b 857
f1003989 858 /*
4dd643d5 859 * client_addr.isNoAddr() indicates this is an "internal" request
a4b8110e 860 * from peer_digest.c, asn.c, netdb.c, etc and should always
861 * be allowed. yuck, I know.
862 */
62e76326 863
4dd643d5 864 if (Config.accessList.miss && !request->client_addr.isNoAddr()) {
62e76326 865 /*
866 * Check if this host is allowed to fetch MISSES from us (miss_access)
b50e327b 867 * default is to allow.
62e76326 868 */
c0941a6a 869 ACLFilledChecklist ch(Config.accessList.miss, request, NULL);
62e76326 870 ch.src_addr = request->client_addr;
871 ch.my_addr = request->my_addr;
2efeb0b7 872 if (ch.fastCheck() == ACCESS_DENIED) {
fd54d9b2 873 debugs(26, 4, HERE << "MISS access forbidden.");
955394ce
AJ
874 err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request);
875 *status_ptr = Http::scForbidden;
73c36fd9 876 errorSend(http->getConn()->clientConnection, err);
62e76326 877 return;
878 }
f1003989 879 }
62e76326 880
c9fd01b4 881 debugs(26, 3, HERE << "'" << RequestMethodStr(request->method) << " " << url << " " << request->http_ver << "'");
5db6bf73
FC
882 ++statCounter.server.all.requests;
883 ++statCounter.server.other.requests;
62e76326 884
fa34dd97 885 tunnelState = new TunnelStateData;
9a0a18de 886#if USE_DELAY_POOLS
11007d4b 887 tunnelState->server.setDelayId(DelayId::DelayClient(http));
59715b38 888#endif
11007d4b 889 tunnelState->url = xstrdup(url);
b248c2a3 890 tunnelState->request = request;
11007d4b 891 tunnelState->server.size_ptr = size_ptr;
892 tunnelState->status_ptr = status_ptr;
73c36fd9 893 tunnelState->client.conn = http->getConn()->clientConnection;
06521a10 894 tunnelState->al = al;
fb046c1b
AJ
895
896 comm_add_close_handler(tunnelState->client.conn->fd,
11007d4b 897 tunnelClientClosed,
898 tunnelState);
8d77a37c
AJ
899
900 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
dc49061a 901 CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
8d77a37c 902 commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
cfd66529 903
00ae51e4 904 peerSelect(&(tunnelState->serverDestinations), request,
62e76326 905 NULL,
11007d4b 906 tunnelPeerSelectComplete,
907 tunnelState);
30a4f2a8 908}
98ffb7e4 909
b8d8561b 910static void
b0388924 911tunnelRelayConnectRequest(const Comm::ConnectionPointer &srv, void *data)
98ffb7e4 912{
fa34dd97 913 TunnelStateData *tunnelState = (TunnelStateData *)data;
3ed5793b 914 assert(!tunnelState->waitingForConnectExchange());
75faaa7a 915 HttpHeader hdr_out(hoRequest);
e1e72f06 916 Packer p;
46f4b111 917 HttpStateFlags flags;
b0388924 918 debugs(26, 3, HERE << srv << ", tunnelState=" << tunnelState);
b4b5fd95 919 memset(&flags, '\0', sizeof(flags));
45e5102d 920 flags.proxying = tunnelState->request->flags.proxying;
032785bf 921 MemBuf mb;
2fe7eff9 922 mb.init();
3872be7c 923 mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url);
8a70cdbb 924 HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(),
e5ee81f0 925 NULL, /* StoreEntry */
06521a10 926 tunnelState->al, /* AccessLogEntry */
e5ee81f0 927 &hdr_out,
928 flags); /* flags */
e1e72f06 929 packerToMemInit(&p, &mb);
a9925b40 930 hdr_out.packInto(&p);
519e0948 931 hdr_out.clean();
e1e72f06 932 packerClean(&p);
2fe7eff9 933 mb.append("\r\n", 2);
c4b7a5a9 934
3ed5793b 935 if (tunnelState->clientExpectsConnectResponse()) {
1c8fc082
A
936 // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours.
937 AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone",
938 CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
939 Comm::Write(srv, &mb, writeCall);
3ed5793b
AR
940 } else {
941 // we have to eat the connect response from the peer (so that the client
942 // does not see it) and only then start shoveling data to the client
943 AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
944 CommIoCbPtrFun(tunnelConnectReqWriteDone,
1c8fc082 945 tunnelState));
3ed5793b
AR
946 Comm::Write(srv, &mb, writeCall);
947 tunnelState->connectReqWriting = true;
948
949 tunnelState->connectRespBuf = new MemBuf;
950 // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
951 // can hold since any CONNECT response leftovers have to fit into server.buf.
952 // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
953 tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
954 tunnelState->readConnectResponse();
955
956 assert(tunnelState->waitingForConnectExchange());
957 }
8d77a37c
AJ
958
959 AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
dc49061a 960 CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
8d77a37c 961 commSetConnTimeout(srv, Config.Timeout.read, timeoutCall);
98ffb7e4 962}
33ea9fff 963
33ea9fff 964static void
a37fdd8a 965tunnelPeerSelectComplete(Comm::ConnectionList *peer_paths, ErrorState *err, void *data)
33ea9fff 966{
fa34dd97 967 TunnelStateData *tunnelState = (TunnelStateData *)data;
62e76326 968
cfd66529 969 if (peer_paths == NULL || peer_paths->size() < 1) {
fd54d9b2 970 debugs(26, 3, HERE << "No paths found. Aborting CONNECT");
a37fdd8a 971 if (!err) {
8a70cdbb 972 err = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, tunnelState->request.getRaw());
a37fdd8a
AJ
973 }
974 *tunnelState->status_ptr = err->httpStatus;
11007d4b 975 err->callback = tunnelErrorComplete;
976 err->callback_data = tunnelState;
e0d28505 977 errorSend(tunnelState->client.conn, err);
62e76326 978 return;
db1cd23c 979 }
a37fdd8a
AJ
980 delete err;
981
6dc6127b 982 if (Ip::Qos::TheConfig.isAclTosActive()) {
8a70cdbb 983 tunnelState->serverDestinations[0]->tos = GetTosToServer(tunnelState->request.getRaw());
6dc6127b
SW
984 }
985
986#if SO_MARK && USE_LIBCAP
8a70cdbb 987 tunnelState->serverDestinations[0]->nfmark = GetNfmarkToServer(tunnelState->request.getRaw());
6dc6127b
SW
988#endif
989
fd54d9b2
AJ
990 debugs(26, 3, HERE << "paths=" << peer_paths->size() << ", p[0]={" << (*peer_paths)[0] << "}, serverDest[0]={" <<
991 tunnelState->serverDestinations[0] << "}");
62e76326 992
cfd66529 993 AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
00ae51e4 994 Comm::ConnOpener *cs = new Comm::ConnOpener(tunnelState->serverDestinations[0], call, Config.Timeout.connect);
aed188fd 995 cs->setHost(tunnelState->url);
855150a4 996 AsyncJob::Start(cs);
33ea9fff 997}
a46d2c0e 998
fa34dd97 999CBDATA_CLASS_INIT(TunnelStateData);
a46d2c0e 1000
a46d2c0e 1001bool
fa34dd97 1002TunnelStateData::noConnections() const
a46d2c0e 1003{
97c81191 1004 return !Comm::IsConnOpen(server.conn) && !Comm::IsConnOpen(client.conn);
a46d2c0e 1005}
1006
9a0a18de 1007#if USE_DELAY_POOLS
a46d2c0e 1008void
fa34dd97 1009TunnelStateData::Connection::setDelayId(DelayId const &newDelay)
a46d2c0e 1010{
1011 delayId = newDelay;
1012}
1013
1014#endif