HttpTunneler.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 #include "squid.h"
10 #include "CachePeer.h"
11 #include "clients/HttpTunneler.h"
12 #include "comm/Read.h"
13 #include "comm/Write.h"
14 #include "errorpage.h"
15 #include "fd.h"
16 #include "fde.h"
17 #include "http.h"
19 #include "http/StateFlags.h"
20 #include "HttpRequest.h"
21 #include "neighbors.h"
22 #include "pconn.h"
23 #include "SquidConfig.h"
24 #include "StatCounters.h"
25 
27 
29  AsyncJob("Http::Tunneler"),
30  noteFwdPconnUse(false),
31  connection(conn),
32  request(req),
33  callback(aCallback),
34  lifetimeLimit(timeout),
35  al(alp),
36  startTime(squid_curtime),
37  requestWritten(false),
38  tunnelEstablished(false)
39 {
40  debugs(83, 5, "Http::Tunneler constructed, this=" << (void*)this);
41  // detect callers supplying cb dialers that are not our CbDialer
42  assert(request);
45  assert(dynamic_cast<Http::TunnelerAnswer *>(callback->getDialer()));
46  url = request->url.authority(true);
48 }
49 
51 {
52  debugs(83, 5, "Http::Tunneler destructed, this=" << (void*)this);
53 }
54 
55 bool
57 {
58  return !callback || (requestWritten && tunnelEstablished);
59 }
60 
64 {
65  Must(callback);
66  const auto tunnelerAnswer = dynamic_cast<Http::TunnelerAnswer *>(callback->getDialer());
67  Must(tunnelerAnswer);
68  return *tunnelerAnswer;
69 }
70 
71 void
73 {
75 
76  Must(al);
77  Must(url.length());
78  Must(lifetimeLimit >= 0);
79 
80  // we own this Comm::Connection object and its fd exclusively, but must bail
81  // if others started closing the socket while we were waiting to start()
82  assert(Comm::IsConnOpen(connection));
83  if (fd_table[connection->fd].closing()) {
84  bailWith(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
85  return;
86  }
87 
88  const auto peer = connection->getPeer();
89  // bail if our peer was reconfigured away
90  if (!peer) {
91  bailWith(new ErrorState(ERR_CONNECT_FAIL, Http::scInternalServerError, request.getRaw(), al));
92  return;
93  }
94  request->prepForPeering(*peer);
95 
96  writeRequest();
97  startReadingResponse();
98 }
99 
100 void
102 {
103  closer = nullptr;
104  bailWith(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
105 }
106 
108 void
110 {
111  Must(Comm::IsConnOpen(connection));
112  Must(!fd_table[connection->fd].closing());
113 
114  debugs(83, 5, connection);
115 
116  Must(!closer);
118  closer = JobCallback(9, 5, Dialer, this, Http::Tunneler::handleConnectionClosure);
119  comm_add_close_handler(connection->fd, closer);
120 }
121 
123 void
125 {
126  bailWith(new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al));
127 }
128 
129 void
131 {
132  debugs(83, 5, connection << status());
133 
134  readBuf.reserveCapacity(SQUID_TCP_SO_RCVBUF);
135  readMore();
136 }
137 
138 void
140 {
141  debugs(83, 5, connection);
142 
143  Http::StateFlags flags;
144  flags.peering = true;
145  // flags.tunneling = false; // the CONNECT request itself is not tunneled
146  // flags.toOrigin = false; // the next HTTP hop is a non-originserver peer
147 
148  MemBuf mb;
149 
150  try {
151  request->masterXaction->generatingConnect = true;
152 
153  mb.init();
154  mb.appendf("CONNECT %s HTTP/1.1\r\n", url.c_str());
155  HttpHeader hdr_out(hoRequest);
157  nullptr, // StoreEntry
158  al,
159  &hdr_out,
160  flags);
161  hdr_out.packInto(&mb);
162  hdr_out.clean();
163  mb.append("\r\n", 2);
164 
165  request->masterXaction->generatingConnect = false;
166  } catch (...) {
167  // TODO: Add scope_guard; do not wait until it is in the C++ standard.
168  request->masterXaction->generatingConnect = false;
169  throw;
170  }
171 
172  debugs(11, 2, "Tunnel Server REQUEST: " << connection <<
173  ":\n----------\n" << mb.buf << "\n----------");
174  fd_note(connection->fd, "Tunnel Server CONNECT");
175 
177  writer = JobCallback(5, 5, Dialer, this, Http::Tunneler::handleWrittenRequest);
178  Comm::Write(connection, &mb, writer);
179 }
180 
182 void
184 {
185  Must(writer);
186  writer = nullptr;
187 
188  if (io.flag == Comm::ERR_CLOSING)
189  return;
190 
191  request->hier.notePeerWrite();
192 
193  if (io.flag != Comm::OK) {
194  const auto error = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, request.getRaw(), al);
195  error->xerrno = io.xerrno;
196  bailWith(error);
197  return;
198  }
199 
200  statCounter.server.all.kbytes_out += io.size;
201  statCounter.server.other.kbytes_out += io.size;
202  requestWritten = true;
203  debugs(83, 5, status());
204 }
205 
207 void
209 {
210  Must(reader);
211  reader = nullptr;
212 
213  if (io.flag == Comm::ERR_CLOSING)
214  return;
215 
216  CommIoCbParams rd(this);
217  rd.conn = io.conn;
218 #if USE_DELAY_POOLS
219  rd.size = delayId.bytesWanted(1, readBuf.spaceSize());
220 #else
221  rd.size = readBuf.spaceSize();
222 #endif
223  // XXX: defer read if rd.size <= 0
224 
225  switch (Comm::ReadNow(rd, readBuf)) {
226  case Comm::INPROGRESS:
227  readMore();
228  return;
229 
230  case Comm::OK: {
231 #if USE_DELAY_POOLS
232  delayId.bytesIn(rd.size);
233 #endif
234  statCounter.server.all.kbytes_in += rd.size;
235  statCounter.server.other.kbytes_in += rd.size; // TODO: other or http?
236  request->hier.notePeerRead();
237  handleResponse(false);
238  return;
239  }
240 
241  case Comm::ENDFILE: {
242  // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
243  handleResponse(true);
244  return;
245  }
246 
247  // case Comm::COMM_ERROR:
248  default: // no other flags should ever occur
249  {
250  const auto error = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request.getRaw(), al);
251  error->xerrno = rd.xerrno;
252  bailWith(error);
253  return;
254  }
255  }
256 
257  assert(false); // not reached
258 }
259 
260 void
262 {
263  Must(Comm::IsConnOpen(connection));
264  Must(!fd_table[connection->fd].closing());
265  Must(!reader);
266 
268  reader = JobCallback(93, 3, Dialer, this, Http::Tunneler::handleReadyRead);
269  Comm::Read(connection, reader);
270 
273  AsyncCall::Pointer timeoutCall = JobCallback(93, 5,
274  TimeoutDialer, this, Http::Tunneler::handleTimeout);
275  const auto timeout = Comm::MortalReadTimeout(startTime, lifetimeLimit);
276  commSetConnTimeout(connection, timeout, timeoutCall);
277 }
278 
280 void
282 {
283  // mimic the basic parts of HttpStateData::processReplyHeader()
284  if (hp == nullptr)
285  hp = new Http1::ResponseParser;
286 
287  auto parsedOk = hp->parse(readBuf); // may be refined below
288  readBuf = hp->remaining();
289  if (hp->needsMoreData()) {
290  if (!eof) {
291  if (readBuf.length() >= SQUID_TCP_SO_RCVBUF) {
292  bailOnResponseError("huge CONNECT response from peer", nullptr);
293  return;
294  }
295  readMore();
296  return;
297  }
298 
299  //eof, handle truncated response
300  readBuf.append("\r\n\r\n", 4);
301  parsedOk = hp->parse(readBuf);
302  readBuf.clear();
303  }
304 
305  if (!parsedOk) {
306  bailOnResponseError("malformed CONNECT response from peer", nullptr);
307  return;
308  }
309 
310  HttpReply::Pointer rep = new HttpReply;
312  rep->sline.set(hp->messageProtocol(), hp->messageStatus());
313  if (!rep->parseHeader(*hp) && rep->sline.status() == Http::scOkay) {
314  bailOnResponseError("malformed CONNECT response from peer", nullptr);
315  return;
316  }
317 
318  // CONNECT response was successfully parsed
319  auto &futureAnswer = answer();
320  futureAnswer.peerResponseStatus = rep->sline.status();
321  request->hier.peer_reply_status = rep->sline.status();
322 
323  debugs(11, 2, "Tunnel Server " << connection);
324  debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
325  Raw(nullptr, readBuf.rawContent(), rep->hdr_sz).minLevel(2).gap(false) <<
326  "----------");
327 
328  // bail if we did not get an HTTP 200 (Connection Established) response
329  if (rep->sline.status() != Http::scOkay) {
330  // TODO: To reuse the connection, extract the whole error response.
331  bailOnResponseError("unsupported CONNECT response status code", rep.getRaw());
332  return;
333  }
334 
335  // preserve any bytes sent by the server after the CONNECT response
336  futureAnswer.leftovers = readBuf;
337 
338  tunnelEstablished = true;
339  debugs(83, 5, status());
340 }
341 
342 void
344 {
345  debugs(83, 3, error << status());
346 
347  ErrorState *err;
348  if (errorReply) {
349  err = new ErrorState(request.getRaw(), errorReply);
350  } else {
351  // with no reply suitable for relaying, answer with 502 (Bad Gateway)
352  err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
353  }
354  bailWith(err);
355 }
356 
357 void
359 {
360  Must(error);
361  answer().squidError = error;
362 
363  if (const auto p = connection->getPeer())
365 
366  callBack();
367  disconnect();
368 
369  if (noteFwdPconnUse)
370  fwdPconnPool->noteUses(fd_table[connection->fd].pconn.uses);
371  // TODO: Reuse to-peer connections after a CONNECT error response.
372  connection->close();
373  connection = nullptr;
374 }
375 
376 void
378 {
379  assert(answer().positive());
380  callBack();
381  disconnect();
382 }
383 
384 void
386 {
387  if (closer) {
388  comm_remove_close_handler(connection->fd, closer);
389  closer = nullptr;
390  }
391 
392  if (reader) {
393  Comm::ReadCancel(connection->fd, reader);
394  reader = nullptr;
395  }
396 
397  // remove connection timeout handler
398  commUnsetConnTimeout(connection);
399 }
400 
401 void
403 {
404  debugs(83, 5, connection << status());
405  if (answer().positive())
406  answer().conn = connection;
407  auto cb = callback;
408  callback = nullptr;
409  ScheduleCallHere(cb);
410 }
411 
412 void
414 {
416 
417  if (callback) {
418  if (requestWritten && tunnelEstablished) {
419  sendSuccess();
420  } else {
421  // we should have bailed when we discovered the job-killing problem
422  debugs(83, DBG_IMPORTANT, "BUG: Unexpected state while establishing a CONNECT tunnel " << connection << status());
424  }
425  assert(!callback);
426  }
427 }
428 
429 const char *
431 {
432  static MemBuf buf;
433  buf.reset();
434 
435  // TODO: redesign AsyncJob::status() API to avoid
436  // id and stop reason reporting duplication.
437  buf.append(" [state:", 8);
438  if (requestWritten) buf.append("w", 1); // request sent
439  if (tunnelEstablished) buf.append("t", 1); // tunnel established
440  if (!callback) buf.append("x", 1); // caller informed
441  if (stopReason != nullptr) {
442  buf.append(" stopped, reason:", 16);
443  buf.appendf("%s",stopReason);
444  }
445  if (connection != nullptr)
446  buf.appendf(" FD %d", connection->fd);
447  buf.appendf(" %s%u]", id.prefix(), id.value);
448  buf.terminate();
449 
450  return buf.content();
451 }
452 
void startReadingResponse()
void Read(const Comm::ConnectionPointer &conn, AsyncCall::Pointer &callback)
Definition: Read.cc:40
int hdr_sz
Definition: Message.h:82
char * buf
Definition: MemBuf.h:134
struct StatCounters::@130::@140 all
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:983
Raw & minLevel(const int aLevel)
limit data printing to at least the given debugging level
Definition: Debug.h:195
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
AnyP::Uri url
the request URI
Definition: HttpRequest.h:115
void peerConnectFailed(CachePeer *p)
Definition: neighbors.cc:1290
void sendSuccess()
sends the ready-to-use tunnel to the initiator
Comm::ConnectionPointer connection
TCP connection to the cache_peer.
Definition: HttpTunneler.h:109
void fd_note(int fd, const char *s)
Definition: fd.cc:246
virtual void swanSong()
Definition: AsyncJob.h:54
void disconnect()
a bailWith(), sendSuccess() helper: stops monitoring the connection
Comm::Flag ReadNow(CommIoCbParams &params, SBuf &buf)
Definition: Read.cc:81
void bailWith(ErrorState *)
sends the given error to the initiator
virtual bool doneAll() const
whether positive goal has been reached
Definition: HttpTunneler.cc:56
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
void handleTimeout(const CommTimeoutCbParams &)
The connection read timeout callback handler.
void error(char *format,...)
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:96
@ INPROGRESS
Definition: Flag.h:22
int commSetConnTimeout(const Comm::ConnectionPointer &conn, int timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:567
bool peering
Whether the next TCP hop is a cache_peer, including originserver.
Definition: StateFlags.h:40
PconnPool * fwdPconnPool
a collection of previously used persistent Squid-to-peer HTTP(S) connections
Definition: FwdState.cc:79
void noteUses(int uses)
Definition: pconn.cc:536
virtual CallDialer * getDialer()=0
C * getRaw() const
Definition: RefCount.h:80
virtual void append(const char *c, int sz)
Definition: MemBuf.cc:216
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:26
@ OK
Definition: Flag.h:16
Http::StatusLine sline
Definition: HttpReply.h:60
@ ENDFILE
Definition: Flag.h:27
Definition: forward.h:21
@ ERR_CLOSING
Definition: Flag.h:25
#define DBG_IMPORTANT
Definition: Debug.h:46
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition: CommCalls.h:88
void bailOnResponseError(const char *error, HttpReply *)
virtual void start()
called by AsyncStart; do not call directly
Definition: HttpTunneler.cc:72
void handleReadyRead(const CommIoCbParams &)
Called when we read [a part of] CONNECT response from the peer.
void ReadCancel(int fd, AsyncCall::Pointer &callback)
Cancel the read pending on FD. No action if none pending.
Definition: Read.cc:217
@ scGatewayTimeout
Definition: StatusCode.h:75
Definition: Debug.h:188
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:128
Http::StatusCode status() const
retrieve the status code for this status line
Definition: StatusLine.h:45
@ srcHttp
http_port or HTTP server
Definition: Message.h:39
@ scBadGateway
Definition: StatusCode.h:73
Definition: MemBuf.h:23
@ ERR_CONNECT_FAIL
Definition: err_type.h:28
struct StatCounters::@130::@140 other
Comm::ConnectionPointer conn
Definition: CommCalls.h:85
void handleResponse(const bool eof)
Parses [possibly incomplete] CONNECT response and reacts to it.
int conn
the current server connection FD
Definition: Transport.cc:26
#define assert(EX)
Definition: assert.h:19
Comm::Flag flag
comm layer result status.
Definition: CommCalls.h:87
@ ERR_READ_ERROR
Definition: err_type.h:26
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:69
@ scInternalServerError
Definition: StatusCode.h:71
void packInto(Packable *p, bool mask_sensitive_info=false) const
Definition: HttpHeader.cc:584
int commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition: comm.cc:593
void Write(const Comm::ConnectionPointer &conn, const char *buf, int size, AsyncCall::Pointer &callback, FREE *free_func)
Definition: Write.cc:35
SBuf & authority(bool requirePort=false) const
Definition: Uri.cc:574
virtual const char * status() const
internal cleanup; do not call directly
time_t squid_curtime
Definition: stub_time.cc:17
bool parseHeader(Http1::Parser &hp)
parses reply header using Parser
Definition: HttpReply.cc:467
void handleConnectionClosure(const CommCloseCbParams &)
uint32_t sources
The message sources.
Definition: Message.h:100
struct StatCounters::@130 server
#define fd_table
Definition: fde.h:189
HttpRequestPointer request
peer connection trigger or cause
Definition: HttpTunneler.h:110
void callBack()
a bailWith(), sendSuccess() helper: sends results to the initiator
SBuf url
request-target for the CONNECT request
Definition: HttpTunneler.h:112
void handleWrittenRequest(const CommIoCbParams &)
Called when we are done writing a CONNECT request header to a peer.
@ ERR_GATEWAY_FAILURE
Definition: err_type.h:65
AsyncCall::Pointer callback
we call this with the results
Definition: HttpTunneler.h:111
virtual void swanSong()
Tunneler(const Comm::ConnectionPointer &conn, const HttpRequestPointer &req, AsyncCall::Pointer &aCallback, time_t timeout, const AccessLogEntryPointer &alp)
Definition: HttpTunneler.cc:28
void set(const AnyP::ProtocolVersion &newVersion, Http::StatusCode newStatus, const char *newReason=NULL)
Definition: StatusLine.cc:30
virtual void start()
called by AsyncStart; do not call directly
Definition: AsyncJob.cc:43
#define Must(condition)
Like assert() but throws an exception instead of aborting the process.
Definition: TextException.h:69
void const char HLPCB * callback
Definition: stub_helper.cc:16
void Controller::create() STUB void Controller Controller nil
virtual ~Tunneler()
Definition: HttpTunneler.cc:50
@ ERR_WRITE_ERROR
Definition: err_type.h:27
time_t MortalReadTimeout(const time_t startTime, const time_t lifetimeLimit)
maximum read delay for readers with limited lifetime
Definition: Read.cc:246
static void httpBuildRequestHeader(HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &al, HttpHeader *hdr_out, const Http::StateFlags &flags)
Definition: http.cc:1865
@ scOkay
Definition: StatusCode.h:26
virtual bool parse(const SBuf &aBuf)
#define false
Definition: GnuRegex.c:233
Raw & gap(bool useGap=true)
Definition: Debug.h:200
TunnelerAnswer & answer()
convenience method to get to the answer fields
Definition: HttpTunneler.cc:63
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
@ hoRequest
Definition: HttpHeader.h:36
CBDATA_NAMESPACED_CLASS_INIT(Http, Tunneler)
void clean()
Definition: HttpHeader.cc:190
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:1010
void const char * buf
Definition: stub_helper.cc:16
void watchForClosures()
make sure we quit if/when the connection is gone
StatCounters statCounter
Definition: StatCounters.cc:12

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors