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"
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(nullptr);
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 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
175void
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
195 requestWritten = true;
196 debugs(83, 5, status());
197}
198
200void
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
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
253void
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
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
273void
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 bailOnResponseError("malformed CONNECT response from peer", nullptr);
300 return;
301 }
302
305 rep->sline.set(hp->messageProtocol(), hp->messageStatus());
306 if (!rep->parseHeader(*hp) && rep->sline.status() == Http::scOkay) {
307 bailOnResponseError("malformed CONNECT response from peer", nullptr);
308 return;
309 }
310
311 // CONNECT response was successfully parsed
312 auto &futureAnswer = callback.answer();
313 futureAnswer.peerResponseStatus = rep->sline.status();
314 request->hier.peer_reply_status = rep->sline.status();
315
316 debugs(11, 2, "Tunnel Server " << connection);
317 debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
318 Raw(nullptr, readBuf.rawContent(), rep->hdr_sz).minLevel(2).gap(false) <<
319 "----------");
320
321 // bail if we did not get an HTTP 200 (Connection Established) response
322 if (rep->sline.status() != Http::scOkay) {
323 // TODO: To reuse the connection, extract the whole error response.
324 bailOnResponseError("unsupported CONNECT response status code", rep.getRaw());
325 return;
326 }
327
328 // preserve any bytes sent by the server after the CONNECT response
329 futureAnswer.leftovers = readBuf;
330
331 tunnelEstablished = true;
332 debugs(83, 5, status());
333}
334
335void
337{
338 debugs(83, 3, error << status());
339
340 ErrorState *err;
341 if (errorReply) {
342 err = new ErrorState(request.getRaw(), errorReply, al);
343 } else {
344 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
345 err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
346 }
347 bailWith(err);
348}
349
350void
352{
353 Must(error);
354 callback.answer().squidError = error;
355
356 if (const auto failingConnection = connection) {
357 // TODO: Reuse to-peer connections after a CONNECT error response.
358 countFailingConnection(error);
359 disconnect();
360 failingConnection->close();
361 }
362
363 callBack();
364}
365
366void
368{
369 assert(callback.answer().positive());
370 assert(Comm::IsConnOpen(connection));
371 callback.answer().conn = connection;
372 disconnect();
373 callBack();
374}
375
376void
378{
379 assert(connection);
380 NoteOutgoingConnectionFailure(connection->getPeer(), error ? error->httpStatus : Http::scNone);
381 if (noteFwdPconnUse && connection->isOpen())
382 fwdPconnPool->noteUses(fd_table[connection->fd].pconn.uses);
383}
384
385void
387{
388 const auto stillOpen = Comm::IsConnOpen(connection);
389
390 if (closer) {
391 if (stillOpen)
392 comm_remove_close_handler(connection->fd, closer);
393 closer = nullptr;
394 }
395
396 if (reader) {
397 if (stillOpen)
398 Comm::ReadCancel(connection->fd, reader);
399 reader = nullptr;
400 }
401
402 if (stillOpen)
403 commUnsetConnTimeout(connection);
404
405 connection = nullptr; // may still be open
406}
407
408void
410{
411 debugs(83, 5, callback.answer().conn << status());
412 assert(!connection); // returned inside callback.answer() or gone
413 ScheduleCallHere(callback.release());
414}
415
416void
418{
420
421 if (callback) {
422 if (requestWritten && tunnelEstablished && Comm::IsConnOpen(connection)) {
423 sendSuccess();
424 } else {
425 // job-ending emergencies like handleStopRequest() or callException()
426 bailWith(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al));
427 }
428 assert(!callback);
429 }
430}
431
432const char *
434{
435 static MemBuf buf;
436 buf.reset();
437
438 // TODO: redesign AsyncJob::status() API to avoid
439 // id and stop reason reporting duplication.
440 buf.append(" [state:", 8);
441 if (requestWritten) buf.append("w", 1); // request sent
442 if (tunnelEstablished) buf.append("t", 1); // tunnel established
443 if (!callback) buf.append("x", 1); // caller informed
444 if (stopReason != nullptr) {
445 buf.append(" stopped, reason:", 16);
446 buf.appendf("%s",stopReason);
447 }
448 if (connection != nullptr)
449 buf.appendf(" FD %d", connection->fd);
450 buf.appendf(" %s%u]", id.prefix(), id.value);
451 buf.terminate();
452
453 return buf.content();
454}
455
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:69
void NoteOutgoingConnectionFailure(CachePeer *const peer, const Http::StatusCode code)
Definition: CachePeer.h:243
PconnPool * fwdPconnPool
a collection of previously used persistent Squid-to-peer HTTP(S) connections
Definition: FwdState.cc:78
@ 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:75
int conn
the current server connection FD
Definition: Transport.cc:26
void error(char *format,...)
#define assert(EX)
Definition: assert.h:17
SBuf & authority(bool requirePort=false) const
Definition: Uri.cc:646
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()
Definition: HttpHeader.cc:186
void packInto(Packable *p, bool mask_sensitive_info=false) const
Definition: HttpHeader.cc:540
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
Definition: HttpRequest.h:115
static void httpBuildRequestHeader(HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &al, HttpHeader *hdr_out, const Http::StateFlags &flags)
Definition: http.cc:1835
@ srcHttp
http_port or HTTP server
Definition: Message.h:39
uint32_t sources
The message sources.
Definition: Message.h:99
int hdr_sz
Definition: Message.h:81
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
Definition: HttpTunneler.cc:54
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 &)
Definition: HttpTunneler.cc:89
void start() override
called by AsyncStart; do not call directly
Definition: HttpTunneler.cc:60
~Tunneler() override
Definition: HttpTunneler.cc:48
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 &)
Definition: HttpTunneler.cc:29
SBuf url
request-target for the CONNECT request
Definition: HttpTunneler.h:92
HttpRequestPointer request
peer connection trigger or cause
Definition: HttpTunneler.h:90
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.
Definition: HttpTunneler.h:89
void countFailingConnection(const ErrorState *)
updates connection usage history before the connection is closed
Definition: MemBuf.h:24
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:536
Definition: Raw.h:21
Raw & gap(bool useGap=true)
Definition: Raw.h:32
Raw & minLevel(const int aLevel)
limit data printing to at least the given debugging level
Definition: Raw.h:27
C * getRaw() const
Definition: RefCount.h:89
struct StatCounters::@123::@133 all
struct StatCounters::@123::@133 other
ByteCounter kbytes_out
Definition: StatCounters.h:46
ByteCounter kbytes_in
Definition: StatCounters.h:45
struct StatCounters::@123 server
int commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:595
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:949
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:978
int commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition: comm.cc:621
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
@ 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: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:75
@ scInternalServerError
Definition: StatusCode.h:71
@ scNone
Definition: StatusCode.h:21
@ scOkay
Definition: StatusCode.h:26
@ scBadGateway
Definition: StatusCode.h:73
void Controller::create() STUB void Controller Controller nil

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors