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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors