]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
924d747ab94596623384b006c6a9c456b5633443
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 "SquidConfig.h"
22 #include "StatCounters.h"
24 CBDATA_NAMESPACED_CLASS_INIT(Http
, Tunneler
);
26 Http::Tunneler::Tunneler(const Comm::ConnectionPointer
&conn
, const HttpRequest::Pointer
&req
, AsyncCall::Pointer
&aCallback
, time_t timeout
, const AccessLogEntryPointer
&alp
):
27 AsyncJob("Http::Tunneler"),
31 lifetimeLimit(timeout
),
33 startTime(squid_curtime
),
34 requestWritten(false),
35 tunnelEstablished(false)
37 debugs(83, 5, "Http::Tunneler constructed, this=" << (void*)this);
38 // detect callers supplying cb dialers that are not our CbDialer
42 assert(dynamic_cast<Http::TunnelerAnswer
*>(callback
->getDialer()));
43 url
= request
->url
.authority();
46 Http::Tunneler::~Tunneler()
48 debugs(83, 5, "Http::Tunneler destructed, this=" << (void*)this);
52 Http::Tunneler::doneAll() const
54 return !callback
|| (requestWritten
&& tunnelEstablished
);
57 /// convenience method to get to the answer fields
58 Http::TunnelerAnswer
&
59 Http::Tunneler::answer()
62 const auto tunnelerAnswer
= dynamic_cast<Http::TunnelerAnswer
*>(callback
->getDialer());
64 return *tunnelerAnswer
;
68 Http::Tunneler::start()
74 Must(lifetimeLimit
>= 0);
76 const auto peer
= connection
->getPeer();
77 Must(peer
); // bail if our peer was reconfigured away
78 request
->prepForPeering(*peer
);
82 startReadingResponse();
86 Http::Tunneler::handleConnectionClosure(const CommCloseCbParams
¶ms
)
88 mustStop("server connection gone");
89 callback
= nullptr; // the caller must monitor closures
92 /// make sure we quit if/when the connection is gone
94 Http::Tunneler::watchForClosures()
96 Must(Comm::IsConnOpen(connection
));
97 Must(!fd_table
[connection
->fd
].closing());
99 debugs(83, 5, connection
);
102 typedef CommCbMemFunT
<Http::Tunneler
, CommCloseCbParams
> Dialer
;
103 closer
= JobCallback(9, 5, Dialer
, this, Http::Tunneler::handleConnectionClosure
);
104 comm_add_close_handler(connection
->fd
, closer
);
108 Http::Tunneler::handleException(const std::exception
& e
)
110 debugs(83, 2, e
.what() << status());
112 bailWith(new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
));
116 Http::Tunneler::startReadingResponse()
118 debugs(83, 5, connection
<< status());
120 readBuf
.reserveCapacity(SQUID_TCP_SO_RCVBUF
);
125 Http::Tunneler::writeRequest()
127 debugs(83, 5, connection
);
129 Http::StateFlags flags
;
130 flags
.peering
= true;
131 // flags.tunneling = false; // the CONNECT request itself is not tunneled
132 // flags.toOrigin = false; // the next HTTP hop is a non-originserver peer
137 request
->masterXaction
->generatingConnect
= true;
140 mb
.appendf("CONNECT %s HTTP/1.1\r\n", url
.c_str());
141 HttpHeader
hdr_out(hoRequest
);
142 HttpStateData::httpBuildRequestHeader(request
.getRaw(),
143 nullptr, // StoreEntry
147 hdr_out
.packInto(&mb
);
149 mb
.append("\r\n", 2);
151 request
->masterXaction
->generatingConnect
= false;
153 // TODO: Add scope_guard; do not wait until it is in the C++ standard.
154 request
->masterXaction
->generatingConnect
= false;
158 debugs(11, 2, "Tunnel Server REQUEST: " << connection
<<
159 ":\n----------\n" << mb
.buf
<< "\n----------");
160 fd_note(connection
->fd
, "Tunnel Server CONNECT");
162 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
163 writer
= JobCallback(5, 5, Dialer
, this, Http::Tunneler::handleWrittenRequest
);
164 Comm::Write(connection
, &mb
, writer
);
167 /// Called when we are done writing a CONNECT request header to a peer.
169 Http::Tunneler::handleWrittenRequest(const CommIoCbParams
&io
)
174 if (io
.flag
== Comm::ERR_CLOSING
)
177 request
->hier
.notePeerWrite();
179 if (io
.flag
!= Comm::OK
) {
180 const auto error
= new ErrorState(ERR_WRITE_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
181 error
->xerrno
= io
.xerrno
;
186 statCounter
.server
.all
.kbytes_out
+= io
.size
;
187 statCounter
.server
.other
.kbytes_out
+= io
.size
;
188 requestWritten
= true;
189 debugs(83, 5, status());
192 /// Called when we read [a part of] CONNECT response from the peer
194 Http::Tunneler::handleReadyRead(const CommIoCbParams
&io
)
199 if (io
.flag
== Comm::ERR_CLOSING
)
202 CommIoCbParams
rd(this);
205 rd
.size
= delayId
.bytesWanted(1, readBuf
.spaceSize());
207 rd
.size
= readBuf
.spaceSize();
210 switch (Comm::ReadNow(rd
, readBuf
)) {
211 case Comm::INPROGRESS
:
217 delayId
.bytesIn(rd
.size
);
219 statCounter
.server
.all
.kbytes_in
+= rd
.size
;
220 statCounter
.server
.other
.kbytes_in
+= rd
.size
; // TODO: other or http?
221 request
->hier
.notePeerRead();
222 handleResponse(false);
226 case Comm::ENDFILE
: {
227 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
228 handleResponse(true);
232 // case Comm::COMM_ERROR:
233 default: // no other flags should ever occur
235 const auto error
= new ErrorState(ERR_READ_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
236 error
->xerrno
= rd
.xerrno
;
242 assert(false); // not reached
246 Http::Tunneler::readMore()
248 Must(Comm::IsConnOpen(connection
));
249 Must(!fd_table
[connection
->fd
].closing());
252 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
253 reader
= JobCallback(93, 3, Dialer
, this, Http::Tunneler::handleReadyRead
);
254 Comm::Read(connection
, reader
);
256 AsyncCall::Pointer nil
;
257 const auto timeout
= Comm::MortalReadTimeout(startTime
, lifetimeLimit
);
258 commSetConnTimeout(connection
, timeout
, nil
);
261 /// Parses [possibly incomplete] CONNECT response and reacts to it.
263 Http::Tunneler::handleResponse(const bool eof
)
265 // mimic the basic parts of HttpStateData::processReplyHeader()
267 hp
= new Http1::ResponseParser
;
269 auto parsedOk
= hp
->parse(readBuf
); // may be refined below
270 readBuf
= hp
->remaining();
271 if (hp
->needsMoreData()) {
273 if (readBuf
.length() >= SQUID_TCP_SO_RCVBUF
) {
274 bailOnResponseError("huge CONNECT response from peer", nullptr);
281 //eof, handle truncated response
282 readBuf
.append("\r\n\r\n", 4);
283 parsedOk
= hp
->parse(readBuf
);
288 bailOnResponseError("malformed CONNECT response from peer", nullptr);
292 HttpReply::Pointer rep
= new HttpReply
;
293 rep
->sources
|= Http::Message::srcHttp
;
294 rep
->sline
.set(hp
->messageProtocol(), hp
->messageStatus());
295 if (!rep
->parseHeader(*hp
) && rep
->sline
.status() == Http::scOkay
) {
296 bailOnResponseError("malformed CONNECT response from peer", nullptr);
300 // CONNECT response was successfully parsed
301 auto &futureAnswer
= answer();
302 futureAnswer
.peerResponseStatus
= rep
->sline
.status();
303 request
->hier
.peer_reply_status
= rep
->sline
.status();
305 debugs(11, 2, "Tunnel Server " << connection
);
306 debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
307 Raw(nullptr, readBuf
.rawContent(), rep
->hdr_sz
).minLevel(2).gap(false) <<
310 // bail if we did not get an HTTP 200 (Connection Established) response
311 if (rep
->sline
.status() != Http::scOkay
) {
312 // TODO: To reuse the connection, extract the whole error response.
313 bailOnResponseError("unsupported CONNECT response status code", rep
.getRaw());
317 // preserve any bytes sent by the server after the CONNECT response
318 futureAnswer
.leftovers
= readBuf
;
320 tunnelEstablished
= true;
321 debugs(83, 5, status());
325 Http::Tunneler::bailOnResponseError(const char *error
, HttpReply
*errorReply
)
327 debugs(83, 3, error
<< status());
331 err
= new ErrorState(request
.getRaw(), errorReply
);
333 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
334 err
= new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
);
340 Http::Tunneler::bailWith(ErrorState
*error
)
343 answer().squidError
= error
;
348 Http::Tunneler::callBack()
350 debugs(83, 5, connection
<< status());
353 ScheduleCallHere(cb
);
357 Http::Tunneler::swanSong()
359 AsyncJob::swanSong();
362 if (requestWritten
&& tunnelEstablished
) {
363 assert(answer().positive());
364 callBack(); // success
366 // we should have bailed when we discovered the job-killing problem
367 debugs(83, DBG_IMPORTANT
, "BUG: Unexpected state while establishing a CONNECT tunnel " << connection
<< status());
368 bailWith(new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
));
374 comm_remove_close_handler(connection
->fd
, closer
);
379 Comm::ReadCancel(connection
->fd
, reader
);
385 Http::Tunneler::status() const
390 // TODO: redesign AsyncJob::status() API to avoid
391 // id and stop reason reporting duplication.
392 buf
.append(" [state:", 8);
393 if (requestWritten
) buf
.append("w", 1); // request sent
394 if (tunnelEstablished
) buf
.append("t", 1); // tunnel established
395 if (!callback
) buf
.append("x", 1); // caller informed
396 if (stopReason
!= nullptr) {
397 buf
.append(" stopped, reason:", 16);
398 buf
.appendf("%s",stopReason
);
400 if (connection
!= nullptr)
401 buf
.appendf(" FD %d", connection
->fd
);
402 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
405 return buf
.content();