]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
fe05eb61aac15df48ce654d2956692fb6c412d42
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();
209 // XXX: defer read if rd.size <= 0
211 switch (Comm::ReadNow(rd
, readBuf
)) {
212 case Comm::INPROGRESS
:
218 delayId
.bytesIn(rd
.size
);
220 statCounter
.server
.all
.kbytes_in
+= rd
.size
;
221 statCounter
.server
.other
.kbytes_in
+= rd
.size
; // TODO: other or http?
222 request
->hier
.notePeerRead();
223 handleResponse(false);
227 case Comm::ENDFILE
: {
228 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
229 handleResponse(true);
233 // case Comm::COMM_ERROR:
234 default: // no other flags should ever occur
236 const auto error
= new ErrorState(ERR_READ_ERROR
, Http::scBadGateway
, request
.getRaw(), al
);
237 error
->xerrno
= rd
.xerrno
;
243 assert(false); // not reached
247 Http::Tunneler::readMore()
249 Must(Comm::IsConnOpen(connection
));
250 Must(!fd_table
[connection
->fd
].closing());
253 typedef CommCbMemFunT
<Http::Tunneler
, CommIoCbParams
> Dialer
;
254 reader
= JobCallback(93, 3, Dialer
, this, Http::Tunneler::handleReadyRead
);
255 Comm::Read(connection
, reader
);
257 AsyncCall::Pointer nil
;
258 const auto timeout
= Comm::MortalReadTimeout(startTime
, lifetimeLimit
);
259 commSetConnTimeout(connection
, timeout
, nil
);
262 /// Parses [possibly incomplete] CONNECT response and reacts to it.
264 Http::Tunneler::handleResponse(const bool eof
)
266 // mimic the basic parts of HttpStateData::processReplyHeader()
268 hp
= new Http1::ResponseParser
;
270 auto parsedOk
= hp
->parse(readBuf
); // may be refined below
271 readBuf
= hp
->remaining();
272 if (hp
->needsMoreData()) {
274 if (readBuf
.length() >= SQUID_TCP_SO_RCVBUF
) {
275 bailOnResponseError("huge CONNECT response from peer", nullptr);
282 //eof, handle truncated response
283 readBuf
.append("\r\n\r\n", 4);
284 parsedOk
= hp
->parse(readBuf
);
289 bailOnResponseError("malformed CONNECT response from peer", nullptr);
293 HttpReply::Pointer rep
= new HttpReply
;
294 rep
->sources
|= Http::Message::srcHttp
;
295 rep
->sline
.set(hp
->messageProtocol(), hp
->messageStatus());
296 if (!rep
->parseHeader(*hp
) && rep
->sline
.status() == Http::scOkay
) {
297 bailOnResponseError("malformed CONNECT response from peer", nullptr);
301 // CONNECT response was successfully parsed
302 auto &futureAnswer
= answer();
303 futureAnswer
.peerResponseStatus
= rep
->sline
.status();
304 request
->hier
.peer_reply_status
= rep
->sline
.status();
306 debugs(11, 2, "Tunnel Server " << connection
);
307 debugs(11, 2, "Tunnel Server RESPONSE:\n---------\n" <<
308 Raw(nullptr, readBuf
.rawContent(), rep
->hdr_sz
).minLevel(2).gap(false) <<
311 // bail if we did not get an HTTP 200 (Connection Established) response
312 if (rep
->sline
.status() != Http::scOkay
) {
313 // TODO: To reuse the connection, extract the whole error response.
314 bailOnResponseError("unsupported CONNECT response status code", rep
.getRaw());
318 // preserve any bytes sent by the server after the CONNECT response
319 futureAnswer
.leftovers
= readBuf
;
321 tunnelEstablished
= true;
322 debugs(83, 5, status());
326 Http::Tunneler::bailOnResponseError(const char *error
, HttpReply
*errorReply
)
328 debugs(83, 3, error
<< status());
332 err
= new ErrorState(request
.getRaw(), errorReply
);
334 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
335 err
= new ErrorState(ERR_CONNECT_FAIL
, Http::scBadGateway
, request
.getRaw(), al
);
341 Http::Tunneler::bailWith(ErrorState
*error
)
344 answer().squidError
= error
;
349 Http::Tunneler::callBack()
351 debugs(83, 5, connection
<< status());
354 ScheduleCallHere(cb
);
358 Http::Tunneler::swanSong()
360 AsyncJob::swanSong();
363 if (requestWritten
&& tunnelEstablished
) {
364 assert(answer().positive());
365 callBack(); // success
367 // we should have bailed when we discovered the job-killing problem
368 debugs(83, DBG_IMPORTANT
, "BUG: Unexpected state while establishing a CONNECT tunnel " << connection
<< status());
369 bailWith(new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
));
375 comm_remove_close_handler(connection
->fd
, closer
);
380 Comm::ReadCancel(connection
->fd
, reader
);
386 Http::Tunneler::status() const
391 // TODO: redesign AsyncJob::status() API to avoid
392 // id and stop reason reporting duplication.
393 buf
.append(" [state:", 8);
394 if (requestWritten
) buf
.append("w", 1); // request sent
395 if (tunnelEstablished
) buf
.append("t", 1); // tunnel established
396 if (!callback
) buf
.append("x", 1); // caller informed
397 if (stopReason
!= nullptr) {
398 buf
.append(" stopped, reason:", 16);
399 buf
.appendf("%s",stopReason
);
401 if (connection
!= nullptr)
402 buf
.appendf(" FD %d", connection
->fd
);
403 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
406 return buf
.content();