]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
fb0c8d336f51a4905236c9e4ba6ba93a73be668b
2 * Copyright (C) 1996-2019 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 "StatCounters.h"
22 #include "SquidConfig.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 HttpHeader
hdr_out(hoRequest
);
130 Http::StateFlags flags
;
131 flags
.peering
= true;
132 // flags.tunneling = false; // the CONNECT request itself is not tunneled
133 // flags.toOrigin = false; // the next HTTP hop is a non-originserver peer
136 mb
.appendf("CONNECT %s HTTP/1.1\r\n", url
.c_str());
137 HttpStateData::httpBuildRequestHeader(request
.getRaw(),
138 nullptr, // StoreEntry
142 hdr_out
.packInto(&mb
);
144 mb
.append("\r\n", 2);
146 debugs(11, 2, "Tunnel Server REQUEST: " << connection
<<
147 ":\n----------\n" << mb
.buf
<< "\n----------");
148 fd_note(connection
->fd
, "Tunnel Server CONNECT");
150 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
151 writer
= JobCallback(5, 5, Dialer
, this, Http::Tunneler::handleWrittenRequest
);
152 Comm::Write(connection
, &mb
, writer
);
155 /// Called when we are done writing a CONNECT request header to a peer.
157 Http::Tunneler::handleWrittenRequest(const CommIoCbParams
&io
)
162 if (io
.flag
== Comm::ERR_CLOSING
)
165 request
->hier
.notePeerWrite();
167 if (io
.flag
!= Comm::OK
) {
168 const auto error
= new ErrorState(ERR_WRITE_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
169 error
->xerrno
= io
.xerrno
;
174 statCounter
.server
.all
.kbytes_out
+= io
.size
;
175 statCounter
.server
.other
.kbytes_out
+= io
.size
;
176 requestWritten
= true;
177 debugs(83, 5, status());
180 /// Called when we read [a part of] CONNECT response from the peer
182 Http::Tunneler::handleReadyRead(const CommIoCbParams
&io
)
187 if (io
.flag
== Comm::ERR_CLOSING
)
190 CommIoCbParams
rd(this);
193 rd
.size
= delayId
.bytesWanted(1, readBuf
.spaceSize());
195 rd
.size
= readBuf
.spaceSize();
198 switch (Comm::ReadNow(rd
, readBuf
)) {
199 case Comm::INPROGRESS
:
205 delayId
.bytesIn(rd
.size
);
207 statCounter
.server
.all
.kbytes_in
+= rd
.size
;
208 statCounter
.server
.other
.kbytes_in
+= rd
.size
; // TODO: other or http?
209 request
->hier
.notePeerRead();
210 handleResponse(false);
214 case Comm::ENDFILE
: {
215 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
216 handleResponse(true);
220 // case Comm::COMM_ERROR:
221 default: // no other flags should ever occur
223 const auto error
= new ErrorState(ERR_READ_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
224 error
->xerrno
= rd
.xerrno
;
230 assert(false); // not reached
234 Http::Tunneler::readMore()
236 Must(Comm::IsConnOpen(connection
));
237 Must(!fd_table
[connection
->fd
].closing());
240 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
241 reader
= JobCallback(93, 3, Dialer
, this, Http::Tunneler::handleReadyRead
);
242 Comm::Read(connection
, reader
);
244 AsyncCall::Pointer nil
;
245 const auto timeout
= Comm::MortalReadTimeout(startTime
, lifetimeLimit
);
246 commSetConnTimeout(connection
, timeout
, nil
);
249 /// Parses [possibly incomplete] CONNECT response and reacts to it.
251 Http::Tunneler::handleResponse(const bool eof
)
253 // mimic the basic parts of HttpStateData::processReplyHeader()
255 hp
= new Http1::ResponseParser
;
257 auto parsedOk
= hp
->parse(readBuf
); // may be refined below
258 readBuf
= hp
->remaining();
259 if (hp
->needsMoreData()) {
261 if (readBuf
.length() >= SQUID_TCP_SO_RCVBUF
) {
262 bailOnResponseError("huge CONNECT response from peer", nullptr);
269 //eof, handle truncated response
270 readBuf
.append("\r\n\r\n", 4);
271 parsedOk
= hp
->parse(readBuf
);
276 bailOnResponseError("malformed CONNECT response from peer", nullptr);
280 HttpReply::Pointer rep
= new HttpReply
;
281 rep
->sources
|= Http::Message::srcHttp
;
282 rep
->sline
.set(hp
->messageProtocol(), hp
->messageStatus());
283 if (!rep
->parseHeader(*hp
) && rep
->sline
.status() == Http::scOkay
) {
284 bailOnResponseError("malformed CONNECT response from peer", nullptr);
288 // CONNECT response was successfully parsed
289 auto &futureAnswer
= answer();
290 futureAnswer
.peerResponseStatus
= rep
->sline
.status();
291 request
->hier
.peer_reply_status
= rep
->sline
.status();
293 debugs(11, 2, "Tunnel Server " << connection
);
294 debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
295 Raw(nullptr, readBuf
.rawContent(), rep
->hdr_sz
).minLevel(2).gap(false) <<
298 // bail if we did not get an HTTP 200 (Connection Established) response
299 if (rep
->sline
.status() != Http::scOkay
) {
300 // TODO: To reuse the connection, extract the whole error response.
301 bailOnResponseError("unsupported CONNECT response status code", rep
.getRaw());
305 // preserve any bytes sent by the server after the CONNECT response
306 futureAnswer
.leftovers
= readBuf
;
308 tunnelEstablished
= true;
309 debugs(83, 5, status());
313 Http::Tunneler::bailOnResponseError(const char *error
, HttpReply
*errorReply
)
315 debugs(83, 3, error
<< status());
319 err
= new ErrorState(request
.getRaw(), errorReply
);
321 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
322 err
= new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
);
328 Http::Tunneler::bailWith(ErrorState
*error
)
331 answer().squidError
= error
;
336 Http::Tunneler::callBack()
338 debugs(83, 5, connection
<< status());
341 ScheduleCallHere(cb
);
345 Http::Tunneler::swanSong()
347 AsyncJob::swanSong();
350 if (requestWritten
&& tunnelEstablished
) {
351 assert(answer().positive());
352 callBack(); // success
354 // we should have bailed when we discovered the job-killing problem
355 debugs(83, DBG_IMPORTANT
, "BUG: Unexpected state while establishing a CONNECT tunnel " << connection
<< status());
356 bailWith(new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
));
362 comm_remove_close_handler(connection
->fd
, closer
);
367 Comm::ReadCancel(connection
->fd
, reader
);
373 Http::Tunneler::status() const
378 // TODO: redesign AsyncJob::status() API to avoid
379 // id and stop reason reporting duplication.
380 buf
.append(" [state:", 8);
381 if (requestWritten
) buf
.append("w", 1); // request sent
382 if (tunnelEstablished
) buf
.append("t", 1); // tunnel established
383 if (!callback
) buf
.append("x", 1); // caller informed
384 if (stopReason
!= nullptr) {
385 buf
.append(" stopped, reason:", 16);
386 buf
.appendf("%s",stopReason
);
388 if (connection
!= nullptr)
389 buf
.appendf(" FD %d", connection
->fd
);
390 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
393 return buf
.content();