HttpTunneler.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 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  connection->getPeer(),
154  flags);
155  hdr_out.packInto(&mb);
156  hdr_out.clean();
157  mb.append("\r\n", 2);
158 
159  request->masterXaction->generatingConnect = false;
160  } catch (...) {
161  // TODO: Add scope_guard; do not wait until it is in the C++ standard.
162  request->masterXaction->generatingConnect = false;
163  throw;
164  }
165 
166  debugs(11, 2, "Tunnel Server REQUEST: " << connection <<
167  ":\n----------\n" << mb.buf << "\n----------");
168  fd_note(connection->fd, "Tunnel Server CONNECT");
169 
171  writer = JobCallback(5, 5, Dialer, this, Http::Tunneler::handleWrittenRequest);
172  Comm::Write(connection, &mb, writer);
173 }
174 
176 void
178 {
179  Must(writer);
180  writer = nullptr;
181 
182  if (io.flag == Comm::ERR_CLOSING)
183  return;
184 
185  request->hier.notePeerWrite();
186 
187  if (io.flag != Comm::OK) {
188  const auto error = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, request.getRaw(), al);
189  error->xerrno = io.xerrno;
190  bailWith(error);
191  return;
192  }
193 
194  statCounter.server.all.kbytes_out += io.size;
195  statCounter.server.other.kbytes_out += io.size;
196  requestWritten = true;
197  debugs(83, 5, status());
198 }
199 
201 void
203 {
204  Must(reader);
205  reader = nullptr;
206 
207  if (io.flag == Comm::ERR_CLOSING)
208  return;
209 
210  CommIoCbParams rd(this);
211  rd.conn = io.conn;
212 #if USE_DELAY_POOLS
213  rd.size = delayId.bytesWanted(1, readBuf.spaceSize());
214 #else
215  rd.size = readBuf.spaceSize();
216 #endif
217  // XXX: defer read if rd.size <= 0
218 
219  switch (Comm::ReadNow(rd, readBuf)) {
220  case Comm::INPROGRESS:
221  readMore();
222  return;
223 
224  case Comm::OK: {
225 #if USE_DELAY_POOLS
226  delayId.bytesIn(rd.size);
227 #endif
228  statCounter.server.all.kbytes_in += rd.size;
229  statCounter.server.other.kbytes_in += rd.size; // TODO: other or http?
230  request->hier.notePeerRead();
231  handleResponse(false);
232  return;
233  }
234 
235  case Comm::ENDFILE: {
236  // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
237  handleResponse(true);
238  return;
239  }
240 
241  // case Comm::COMM_ERROR:
242  default: // no other flags should ever occur
243  {
244  const auto error = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request.getRaw(), al);
245  error->xerrno = rd.xerrno;
246  bailWith(error);
247  return;
248  }
249  }
250 
251  assert(false); // not reached
252 }
253 
254 void
256 {
257  Must(Comm::IsConnOpen(connection));
258  Must(!fd_table[connection->fd].closing());
259  Must(!reader);
260 
262  reader = JobCallback(93, 3, Dialer, this, Http::Tunneler::handleReadyRead);
263  Comm::Read(connection, reader);
264 
265  AsyncCall::Pointer nil;
267  AsyncCall::Pointer timeoutCall = JobCallback(93, 5,
268  TimeoutDialer, this, Http::Tunneler::handleTimeout);
269  const auto timeout = Comm::MortalReadTimeout(startTime, lifetimeLimit);
270  commSetConnTimeout(connection, timeout, timeoutCall);
271 }
272 
274 void
276 {
277  // mimic the basic parts of HttpStateData::processReplyHeader()
278  if (hp == nullptr)
279  hp = new Http1::ResponseParser;
280 
281  auto parsedOk = hp->parse(readBuf); // may be refined below
282  readBuf = hp->remaining();
283  if (hp->needsMoreData()) {
284  if (!eof) {
285  if (readBuf.length() >= SQUID_TCP_SO_RCVBUF) {
286  bailOnResponseError("huge CONNECT response from peer", nullptr);
287  return;
288  }
289  readMore();
290  return;
291  }
292 
293  //eof, handle truncated response
294  readBuf.append("\r\n\r\n", 4);
295  parsedOk = hp->parse(readBuf);
296  readBuf.clear();
297  }
298 
299  if (!parsedOk) {
300  // XXX: This code and Server RESPONSE reporting code below duplicate
301  // HttpStateData::processReplyHeader() reporting code, including its problems.
302  debugs(11, 3, "Non-HTTP-compliant header:\n---------\n" << readBuf << "\n----------");
303  bailOnResponseError("malformed CONNECT response from peer", nullptr);
304  return;
305  }
306 
307  /* We know the whole response is in parser now */
308  debugs(11, 2, "Tunnel Server " << connection);
309  debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
310  hp->messageProtocol() << " " << hp->messageStatus() << " " << hp->reasonPhrase() << "\n" <<
311  hp->mimeHeader() <<
312  "----------");
313 
314  HttpReply::Pointer rep = new HttpReply;
316  rep->sline.set(hp->messageProtocol(), hp->messageStatus());
317  if (!rep->parseHeader(*hp) && rep->sline.status() == Http::scOkay) {
318  bailOnResponseError("malformed CONNECT response headers mime block from peer", nullptr);
319  return;
320  }
321 
322  // CONNECT response was successfully parsed
323  auto &futureAnswer = callback.answer();
324  futureAnswer.peerResponseStatus = rep->sline.status();
325  request->hier.peer_reply_status = rep->sline.status();
326 
327  // bail if we did not get an HTTP 200 (Connection Established) response
328  if (rep->sline.status() != Http::scOkay) {
329  // TODO: To reuse the connection, extract the whole error response.
330  bailOnResponseError("unsupported CONNECT response status code", rep.getRaw());
331  return;
332  }
333 
334  // preserve any bytes sent by the server after the CONNECT response
335  futureAnswer.leftovers = readBuf;
336 
337  tunnelEstablished = true;
338  debugs(83, 5, status());
339 }
340 
341 void
343 {
344  debugs(83, 3, error << status());
345 
346  ErrorState *err;
347  if (errorReply) {
348  err = new ErrorState(request.getRaw(), errorReply, al);
349  } else {
350  // with no reply suitable for relaying, answer with 502 (Bad Gateway)
351  err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
352  }
353  bailWith(err);
354 }
355 
356 void
358 {
359  Must(error);
360  callback.answer().squidError = error;
361 
362  if (const auto failingConnection = connection) {
363  // TODO: Reuse to-peer connections after a CONNECT error response.
364  countFailingConnection();
365  disconnect();
366  failingConnection->close();
367  }
368 
369  callBack();
370 }
371 
372 void
374 {
375  assert(callback.answer().positive());
376  assert(Comm::IsConnOpen(connection));
377  callback.answer().conn = connection;
378  disconnect();
379  callBack();
380 }
381 
382 void
384 {
385  assert(connection);
386  // No NoteOutgoingConnectionFailure(connection->getPeer()) call here because
387  // we do not blame cache_peer for CONNECT failures (on top of a successfully
388  // established connection to that cache_peer).
389  if (noteFwdPconnUse && connection->isOpen())
390  fwdPconnPool->noteUses(fd_table[connection->fd].pconn.uses);
391 }
392 
393 void
395 {
396  const auto stillOpen = Comm::IsConnOpen(connection);
397 
398  if (closer) {
399  if (stillOpen)
400  comm_remove_close_handler(connection->fd, closer);
401  closer = nullptr;
402  }
403 
404  if (reader) {
405  if (stillOpen)
406  Comm::ReadCancel(connection->fd, reader);
407  reader = nullptr;
408  }
409 
410  if (stillOpen)
411  commUnsetConnTimeout(connection);
412 
413  connection = nullptr; // may still be open
414 }
415 
416 void
418 {
419  debugs(83, 5, callback.answer().conn << status());
420  assert(!connection); // returned inside callback.answer() or gone
421  ScheduleCallHere(callback.release());
422 }
423 
424 void
426 {
428 
429  if (callback) {
430  if (requestWritten && tunnelEstablished && Comm::IsConnOpen(connection)) {
431  sendSuccess();
432  } else {
433  // job-ending emergencies like handleStopRequest() or callException()
434  bailWith(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al));
435  }
436  assert(!callback);
437  }
438 }
439 
440 const char *
442 {
443  static MemBuf buf;
444  buf.reset();
445 
446  // TODO: redesign AsyncJob::status() API to avoid
447  // id and stop reason reporting duplication.
448  buf.append(" [state:", 8);
449  if (requestWritten) buf.append("w", 1); // request sent
450  if (tunnelEstablished) buf.append("t", 1); // tunnel established
451  if (!callback) buf.append("x", 1); // caller informed
452  if (stopReason != nullptr) {
453  buf.append(" stopped, reason:", 16);
454  buf.appendf("%s",stopReason);
455  }
456  if (connection != nullptr)
457  buf.appendf(" FD %d", connection->fd);
458  buf.appendf(" %s%u]", id.prefix(), id.value);
459  buf.terminate();
460 
461  return buf.content();
462 }
463 
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:939
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:559
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
static void httpBuildRequestHeader(HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &al, HttpHeader *hdr_out, const CachePeer *peer, const Http::StateFlags &flags)
Definition: http.cc:1908
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
@ 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:968
void watchForClosures()
make sure we quit if/when the connection is gone
StatCounters statCounter
Definition: StatCounters.cc:12

 

Introduction

Documentation

Support

Miscellaneous