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

 

Introduction

Documentation

Support

Miscellaneous