]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
a4957f73f31a77e43fadfdd0b6491022f97c14a9
2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
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.
10 #include "CachePeer.h"
11 #include "clients/HttpTunneler.h"
12 #include "comm/Read.h"
13 #include "comm/Write.h"
14 #include "errorpage.h"
18 #include "http/one/ResponseParser.h"
19 #include "http/StateFlags.h"
20 #include "HttpRequest.h"
21 #include "neighbors.h"
23 #include "SquidConfig.h"
24 #include "StatCounters.h"
26 CBDATA_NAMESPACED_CLASS_INIT(Http
, Tunneler
);
28 Http::Tunneler::Tunneler(const Comm::ConnectionPointer
&conn
, const HttpRequest::Pointer
&req
, AsyncCall::Pointer
&aCallback
, time_t timeout
, const AccessLogEntryPointer
&alp
):
29 AsyncJob("Http::Tunneler"),
30 noteFwdPconnUse(false),
34 lifetimeLimit(timeout
),
36 startTime(squid_curtime
),
37 requestWritten(false),
38 tunnelEstablished(false)
40 debugs(83, 5, "Http::Tunneler constructed, this=" << (void*)this);
41 // detect callers supplying cb dialers that are not our CbDialer
45 assert(dynamic_cast<Http::TunnelerAnswer
*>(callback
->getDialer()));
46 url
= request
->url
.authority();
50 Http::Tunneler::~Tunneler()
52 debugs(83, 5, "Http::Tunneler destructed, this=" << (void*)this);
56 Http::Tunneler::doneAll() const
58 return !callback
|| (requestWritten
&& tunnelEstablished
);
61 /// convenience method to get to the answer fields
62 Http::TunnelerAnswer
&
63 Http::Tunneler::answer()
66 const auto tunnelerAnswer
= dynamic_cast<Http::TunnelerAnswer
*>(callback
->getDialer());
68 return *tunnelerAnswer
;
72 Http::Tunneler::start()
78 Must(lifetimeLimit
>= 0);
80 // we own this Comm::Connection object and its fd exclusively, but must bail
81 // if others started closing the socket while we were waiting to start()
82 assert(Comm::IsConnOpen(connection
));
83 if (fd_table
[connection
->fd
].closing()) {
84 bailWith(new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
));
88 const auto peer
= connection
->getPeer();
89 // bail if our peer was reconfigured away
91 bailWith(new ErrorState(ERR_CONNECT_FAIL
, Http::scInternalServerError
, request
.getRaw(), al
));
94 request
->prepForPeering(*peer
);
97 startReadingResponse();
101 Http::Tunneler::handleConnectionClosure(const CommCloseCbParams
¶ms
)
104 bailWith(new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
));
107 /// make sure we quit if/when the connection is gone
109 Http::Tunneler::watchForClosures()
111 Must(Comm::IsConnOpen(connection
));
112 Must(!fd_table
[connection
->fd
].closing());
114 debugs(83, 5, connection
);
117 typedef CommCbMemFunT
<Http::Tunneler
, CommCloseCbParams
> Dialer
;
118 closer
= JobCallback(9, 5, Dialer
, this, Http::Tunneler::handleConnectionClosure
);
119 comm_add_close_handler(connection
->fd
, closer
);
122 /// The connection read timeout callback handler.
124 Http::Tunneler::handleTimeout(const CommTimeoutCbParams
&)
126 bailWith(new ErrorState(ERR_CONNECT_FAIL
, Http::scGatewayTimeout
, request
.getRaw(), al
));
130 Http::Tunneler::startReadingResponse()
132 debugs(83, 5, connection
<< status());
134 readBuf
.reserveCapacity(SQUID_TCP_SO_RCVBUF
);
139 Http::Tunneler::writeRequest()
141 debugs(83, 5, connection
);
143 Http::StateFlags flags
;
144 flags
.peering
= true;
145 // flags.tunneling = false; // the CONNECT request itself is not tunneled
146 // flags.toOrigin = false; // the next HTTP hop is a non-originserver peer
151 request
->masterXaction
->generatingConnect
= true;
154 mb
.appendf("CONNECT %s HTTP/1.1\r\n", url
.c_str());
155 HttpHeader
hdr_out(hoRequest
);
156 HttpStateData::httpBuildRequestHeader(request
.getRaw(),
157 nullptr, // StoreEntry
161 hdr_out
.packInto(&mb
);
163 mb
.append("\r\n", 2);
165 request
->masterXaction
->generatingConnect
= false;
167 // TODO: Add scope_guard; do not wait until it is in the C++ standard.
168 request
->masterXaction
->generatingConnect
= false;
172 debugs(11, 2, "Tunnel Server REQUEST: " << connection
<<
173 ":\n----------\n" << mb
.buf
<< "\n----------");
174 fd_note(connection
->fd
, "Tunnel Server CONNECT");
176 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
177 writer
= JobCallback(5, 5, Dialer
, this, Http::Tunneler::handleWrittenRequest
);
178 Comm::Write(connection
, &mb
, writer
);
181 /// Called when we are done writing a CONNECT request header to a peer.
183 Http::Tunneler::handleWrittenRequest(const CommIoCbParams
&io
)
188 if (io
.flag
== Comm::ERR_CLOSING
)
191 request
->hier
.notePeerWrite();
193 if (io
.flag
!= Comm::OK
) {
194 const auto error
= new ErrorState(ERR_WRITE_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
195 error
->xerrno
= io
.xerrno
;
200 statCounter
.server
.all
.kbytes_out
+= io
.size
;
201 statCounter
.server
.other
.kbytes_out
+= io
.size
;
202 requestWritten
= true;
203 debugs(83, 5, status());
206 /// Called when we read [a part of] CONNECT response from the peer
208 Http::Tunneler::handleReadyRead(const CommIoCbParams
&io
)
213 if (io
.flag
== Comm::ERR_CLOSING
)
216 CommIoCbParams
rd(this);
219 rd
.size
= delayId
.bytesWanted(1, readBuf
.spaceSize());
221 rd
.size
= readBuf
.spaceSize();
223 // XXX: defer read if rd.size <= 0
225 switch (Comm::ReadNow(rd
, readBuf
)) {
226 case Comm::INPROGRESS
:
232 delayId
.bytesIn(rd
.size
);
234 statCounter
.server
.all
.kbytes_in
+= rd
.size
;
235 statCounter
.server
.other
.kbytes_in
+= rd
.size
; // TODO: other or http?
236 request
->hier
.notePeerRead();
237 handleResponse(false);
241 case Comm::ENDFILE
: {
242 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
243 handleResponse(true);
247 // case Comm::COMM_ERROR:
248 default: // no other flags should ever occur
250 const auto error
= new ErrorState(ERR_READ_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
251 error
->xerrno
= rd
.xerrno
;
257 assert(false); // not reached
261 Http::Tunneler::readMore()
263 Must(Comm::IsConnOpen(connection
));
264 Must(!fd_table
[connection
->fd
].closing());
267 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
268 reader
= JobCallback(93, 3, Dialer
, this, Http::Tunneler::handleReadyRead
);
269 Comm::Read(connection
, reader
);
271 AsyncCall::Pointer nil
;
272 typedef CommCbMemFunT
<Http::Tunneler
, CommTimeoutCbParams
> TimeoutDialer
;
273 AsyncCall::Pointer timeoutCall
= JobCallback(93, 5,
274 TimeoutDialer
, this, Http::Tunneler::handleTimeout
);
275 const auto timeout
= Comm::MortalReadTimeout(startTime
, lifetimeLimit
);
276 commSetConnTimeout(connection
, timeout
, timeoutCall
);
279 /// Parses [possibly incomplete] CONNECT response and reacts to it.
281 Http::Tunneler::handleResponse(const bool eof
)
283 // mimic the basic parts of HttpStateData::processReplyHeader()
285 hp
= new Http1::ResponseParser
;
287 auto parsedOk
= hp
->parse(readBuf
); // may be refined below
288 readBuf
= hp
->remaining();
289 if (hp
->needsMoreData()) {
291 if (readBuf
.length() >= SQUID_TCP_SO_RCVBUF
) {
292 bailOnResponseError("huge CONNECT response from peer", nullptr);
299 //eof, handle truncated response
300 readBuf
.append("\r\n\r\n", 4);
301 parsedOk
= hp
->parse(readBuf
);
306 bailOnResponseError("malformed CONNECT response from peer", nullptr);
310 HttpReply::Pointer rep
= new HttpReply
;
311 rep
->sources
|= Http::Message::srcHttp
;
312 rep
->sline
.set(hp
->messageProtocol(), hp
->messageStatus());
313 if (!rep
->parseHeader(*hp
) && rep
->sline
.status() == Http::scOkay
) {
314 bailOnResponseError("malformed CONNECT response from peer", nullptr);
318 // CONNECT response was successfully parsed
319 auto &futureAnswer
= answer();
320 futureAnswer
.peerResponseStatus
= rep
->sline
.status();
321 request
->hier
.peer_reply_status
= rep
->sline
.status();
323 debugs(11, 2, "Tunnel Server " << connection
);
324 debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
325 Raw(nullptr, readBuf
.rawContent(), rep
->hdr_sz
).minLevel(2).gap(false) <<
328 // bail if we did not get an HTTP 200 (Connection Established) response
329 if (rep
->sline
.status() != Http::scOkay
) {
330 // TODO: To reuse the connection, extract the whole error response.
331 bailOnResponseError("unsupported CONNECT response status code", rep
.getRaw());
335 // preserve any bytes sent by the server after the CONNECT response
336 futureAnswer
.leftovers
= readBuf
;
338 tunnelEstablished
= true;
339 debugs(83, 5, status());
343 Http::Tunneler::bailOnResponseError(const char *error
, HttpReply
*errorReply
)
345 debugs(83, 3, error
<< status());
349 err
= new ErrorState(request
.getRaw(), errorReply
);
351 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
352 err
= new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
);
358 Http::Tunneler::bailWith(ErrorState
*error
)
361 answer().squidError
= error
;
363 if (const auto p
= connection
->getPeer())
364 peerConnectFailed(p
);
370 fwdPconnPool
->noteUses(fd_table
[connection
->fd
].pconn
.uses
);
371 // TODO: Reuse to-peer connections after a CONNECT error response.
373 connection
= nullptr;
377 Http::Tunneler::sendSuccess()
379 assert(answer().positive());
385 Http::Tunneler::disconnect()
388 comm_remove_close_handler(connection
->fd
, closer
);
393 Comm::ReadCancel(connection
->fd
, reader
);
397 // remove connection timeout handler
398 commUnsetConnTimeout(connection
);
402 Http::Tunneler::callBack()
404 debugs(83, 5, connection
<< status());
405 if (answer().positive())
406 answer().conn
= connection
;
409 ScheduleCallHere(cb
);
413 Http::Tunneler::swanSong()
415 AsyncJob::swanSong();
418 if (requestWritten
&& tunnelEstablished
) {
421 // we should have bailed when we discovered the job-killing problem
422 debugs(83, DBG_IMPORTANT
, "BUG: Unexpected state while establishing a CONNECT tunnel " << connection
<< status());
423 bailWith(new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
));
430 Http::Tunneler::status() const
435 // TODO: redesign AsyncJob::status() API to avoid
436 // id and stop reason reporting duplication.
437 buf
.append(" [state:", 8);
438 if (requestWritten
) buf
.append("w", 1); // request sent
439 if (tunnelEstablished
) buf
.append("t", 1); // tunnel established
440 if (!callback
) buf
.append("x", 1); // caller informed
441 if (stopReason
!= nullptr) {
442 buf
.append(" stopped, reason:", 16);
443 buf
.appendf("%s",stopReason
);
445 if (connection
!= nullptr)
446 buf
.appendf(" FD %d", connection
->fd
);
447 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
450 return buf
.content();