Squid Web Cache master
Loading...
Searching...
No Matches
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"
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
29Http::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);
44 url = request->url.authority(true);
46}
47
49{
50 debugs(83, 5, "Http::Tunneler destructed, this=" << (void*)this);
51}
52
53bool
55{
56 return !callback || (requestWritten && tunnelEstablished);
57}
58
59void
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
88void
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
101void
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
116void
118{
119 bailWith(new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al));
120}
121
122void
124{
125 debugs(83, 5, connection << status());
126
127 readBuf.reserveCapacity(SQUID_TCP_SO_RCVBUF);
128 readMore();
129}
130
131void
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);
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
176void
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
196 requestWritten = true;
197 debugs(83, 5, status());
198}
199
201void
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
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
254void
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
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
274void
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
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
341void
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
356void
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
372void
374{
375 assert(callback.answer().positive());
376 assert(Comm::IsConnOpen(connection));
377 callback.answer().conn = connection;
378 disconnect();
379 callBack();
380}
381
382void
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
393void
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
416void
418{
419 debugs(83, 5, callback.answer().conn << status());
420 assert(!connection); // returned inside callback.answer() or gone
421 ScheduleCallHere(callback.release());
422}
423
424void
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
440const 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
#define ScheduleCallHere(call)
Definition AsyncCall.h:166
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
PconnPool * fwdPconnPool
a collection of previously used persistent Squid-to-peer HTTP(S) connections
Definition FwdState.cc:78
@ hoRequest
Definition HttpHeader.h:36
time_t squid_curtime
StatCounters statCounter
#define Must(condition)
void error(char *format,...)
#define assert(EX)
Definition assert.h:17
#define SQUID_TCP_SO_RCVBUF
Definition autoconf.h:1458
#define CBDATA_NAMESPACED_CLASS_INIT(namespace, type)
Definition cbdata.h:333
SBuf & authority(bool requirePort=false) const
Definition Uri.cc:721
a smart AsyncCall pointer for delivery of future results
virtual void start()
called by AsyncStart; do not call directly
Definition AsyncJob.cc:59
virtual void swanSong()
Definition AsyncJob.h:61
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition CommCalls.h:83
Comm::Flag flag
comm layer result status.
Definition CommCalls.h:82
Comm::ConnectionPointer conn
Definition CommCalls.h:80
void clean()
void packInto(Packable *p, bool mask_sensitive_info=false) const
Http::StatusLine sline
Definition HttpReply.h:56
bool parseHeader(Http1::Parser &hp)
parses reply header using Parser
Definition HttpReply.cc:507
AnyP::Uri url
the request URI
static void httpBuildRequestHeader(HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &al, HttpHeader *hdr_out, const CachePeer *peer, const Http::StateFlags &flags)
Definition http.cc:1909
@ srcHttp
http_port or HTTP server
Definition Message.h:39
uint32_t sources
The message sources.
Definition Message.h:99
bool parse(const SBuf &aBuf) override
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=nullptr)
Definition StatusLine.cc:35
Http::StatusCode status() const
retrieve the status code for this status line
Definition StatusLine.h:45
bool doneAll() const override
whether positive goal has been reached
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 swanSong() override
void callBack()
a bailWith(), sendSuccess() helper: sends results to the initiator
void bailWith(ErrorState *)
sends the given error to the initiator
const char * status() const override
internal cleanup; do not call directly
void handleConnectionClosure(const CommCloseCbParams &)
void start() override
called by AsyncStart; do not call directly
~Tunneler() override
void handleWrittenRequest(const CommIoCbParams &)
Called when we are done writing a CONNECT request header to a peer.
void watchForClosures()
make sure we quit if/when the connection is gone
void sendSuccess()
sends the ready-to-use tunnel to the initiator
void startReadingResponse()
Tunneler(const Comm::ConnectionPointer &, const HttpRequestPointer &, const AsyncCallback< Answer > &, time_t timeout, const AccessLogEntryPointer &)
SBuf url
request-target for the CONNECT request
HttpRequestPointer request
peer connection trigger or cause
void handleTimeout(const CommTimeoutCbParams &)
The connection read timeout callback handler.
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.
void append(const char *c, int sz) override
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:559
C * getRaw() const
Definition RefCount.h:89
ByteCounter kbytes_out
struct StatCounters::@105 server
ByteCounter kbytes_in
struct StatCounters::@105::@115 other
struct StatCounters::@105::@115 all
void commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition comm.cc:618
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition comm.cc:942
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition comm.cc:971
void commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
Definition comm.cc:594
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
@ 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:211
#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:26
@ ERR_CLOSING
Definition Flag.h:24
@ INPROGRESS
Definition Flag.h:21
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:18
@ scGatewayTimeout
Definition StatusCode.h:77
@ scInternalServerError
Definition StatusCode.h:73
@ scOkay
Definition StatusCode.h:27
@ scBadGateway
Definition StatusCode.h:75