]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
fe05eb61aac15df48ce654d2956692fb6c412d42
[thirdparty/squid.git] / src / clients / HttpTunneler.cc
1 /*
2 * Copyright (C) 1996-2020 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 "CachePeer.h"
11 #include "clients/HttpTunneler.h"
12 #include "comm/Read.h"
13 #include "comm/Write.h"
14 #include "errorpage.h"
15 #include "fd.h"
16 #include "fde.h"
17 #include "http.h"
18 #include "http/one/ResponseParser.h"
19 #include "http/StateFlags.h"
20 #include "HttpRequest.h"
21 #include "SquidConfig.h"
22 #include "StatCounters.h"
23
24 CBDATA_NAMESPACED_CLASS_INIT(Http, Tunneler);
25
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"),
28 connection(conn),
29 request(req),
30 callback(aCallback),
31 lifetimeLimit(timeout),
32 al(alp),
33 startTime(squid_curtime),
34 requestWritten(false),
35 tunnelEstablished(false)
36 {
37 debugs(83, 5, "Http::Tunneler constructed, this=" << (void*)this);
38 // detect callers supplying cb dialers that are not our CbDialer
39 assert(request);
40 assert(connection);
41 assert(callback);
42 assert(dynamic_cast<Http::TunnelerAnswer *>(callback->getDialer()));
43 url = request->url.authority();
44 }
45
46 Http::Tunneler::~Tunneler()
47 {
48 debugs(83, 5, "Http::Tunneler destructed, this=" << (void*)this);
49 }
50
51 bool
52 Http::Tunneler::doneAll() const
53 {
54 return !callback || (requestWritten && tunnelEstablished);
55 }
56
57 /// convenience method to get to the answer fields
58 Http::TunnelerAnswer &
59 Http::Tunneler::answer()
60 {
61 Must(callback);
62 const auto tunnelerAnswer = dynamic_cast<Http::TunnelerAnswer *>(callback->getDialer());
63 Must(tunnelerAnswer);
64 return *tunnelerAnswer;
65 }
66
67 void
68 Http::Tunneler::start()
69 {
70 AsyncJob::start();
71
72 Must(al);
73 Must(url.length());
74 Must(lifetimeLimit >= 0);
75
76 const auto peer = connection->getPeer();
77 Must(peer); // bail if our peer was reconfigured away
78 request->prepForPeering(*peer);
79
80 watchForClosures();
81 writeRequest();
82 startReadingResponse();
83 }
84
85 void
86 Http::Tunneler::handleConnectionClosure(const CommCloseCbParams &params)
87 {
88 mustStop("server connection gone");
89 callback = nullptr; // the caller must monitor closures
90 }
91
92 /// make sure we quit if/when the connection is gone
93 void
94 Http::Tunneler::watchForClosures()
95 {
96 Must(Comm::IsConnOpen(connection));
97 Must(!fd_table[connection->fd].closing());
98
99 debugs(83, 5, connection);
100
101 Must(!closer);
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);
105 }
106
107 void
108 Http::Tunneler::handleException(const std::exception& e)
109 {
110 debugs(83, 2, e.what() << status());
111 connection->close();
112 bailWith(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al));
113 }
114
115 void
116 Http::Tunneler::startReadingResponse()
117 {
118 debugs(83, 5, connection << status());
119
120 readBuf.reserveCapacity(SQUID_TCP_SO_RCVBUF);
121 readMore();
122 }
123
124 void
125 Http::Tunneler::writeRequest()
126 {
127 debugs(83, 5, connection);
128
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
133
134 MemBuf mb;
135
136 try {
137 request->masterXaction->generatingConnect = true;
138
139 mb.init();
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
144 al,
145 &hdr_out,
146 flags);
147 hdr_out.packInto(&mb);
148 hdr_out.clean();
149 mb.append("\r\n", 2);
150
151 request->masterXaction->generatingConnect = false;
152 } catch (...) {
153 // TODO: Add scope_guard; do not wait until it is in the C++ standard.
154 request->masterXaction->generatingConnect = false;
155 throw;
156 }
157
158 debugs(11, 2, "Tunnel Server REQUEST: " << connection <<
159 ":\n----------\n" << mb.buf << "\n----------");
160 fd_note(connection->fd, "Tunnel Server CONNECT");
161
162 typedef CommCbMemFunT<Http::Tunneler, CommIoCbParams> Dialer;
163 writer = JobCallback(5, 5, Dialer, this, Http::Tunneler::handleWrittenRequest);
164 Comm::Write(connection, &mb, writer);
165 }
166
167 /// Called when we are done writing a CONNECT request header to a peer.
168 void
169 Http::Tunneler::handleWrittenRequest(const CommIoCbParams &io)
170 {
171 Must(writer);
172 writer = nullptr;
173
174 if (io.flag == Comm::ERR_CLOSING)
175 return;
176
177 request->hier.notePeerWrite();
178
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;
182 bailWith(error);
183 return;
184 }
185
186 statCounter.server.all.kbytes_out += io.size;
187 statCounter.server.other.kbytes_out += io.size;
188 requestWritten = true;
189 debugs(83, 5, status());
190 }
191
192 /// Called when we read [a part of] CONNECT response from the peer
193 void
194 Http::Tunneler::handleReadyRead(const CommIoCbParams &io)
195 {
196 Must(reader);
197 reader = nullptr;
198
199 if (io.flag == Comm::ERR_CLOSING)
200 return;
201
202 CommIoCbParams rd(this);
203 rd.conn = io.conn;
204 #if USE_DELAY_POOLS
205 rd.size = delayId.bytesWanted(1, readBuf.spaceSize());
206 #else
207 rd.size = readBuf.spaceSize();
208 #endif
209 // XXX: defer read if rd.size <= 0
210
211 switch (Comm::ReadNow(rd, readBuf)) {
212 case Comm::INPROGRESS:
213 readMore();
214 return;
215
216 case Comm::OK: {
217 #if USE_DELAY_POOLS
218 delayId.bytesIn(rd.size);
219 #endif
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);
224 return;
225 }
226
227 case Comm::ENDFILE: {
228 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
229 handleResponse(true);
230 return;
231 }
232
233 // case Comm::COMM_ERROR:
234 default: // no other flags should ever occur
235 {
236 const auto error = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request.getRaw(), al);
237 error->xerrno = rd.xerrno;
238 bailWith(error);
239 return;
240 }
241 }
242
243 assert(false); // not reached
244 }
245
246 void
247 Http::Tunneler::readMore()
248 {
249 Must(Comm::IsConnOpen(connection));
250 Must(!fd_table[connection->fd].closing());
251 Must(!reader);
252
253 typedef CommCbMemFunT<Http::Tunneler, CommIoCbParams> Dialer;
254 reader = JobCallback(93, 3, Dialer, this, Http::Tunneler::handleReadyRead);
255 Comm::Read(connection, reader);
256
257 AsyncCall::Pointer nil;
258 const auto timeout = Comm::MortalReadTimeout(startTime, lifetimeLimit);
259 commSetConnTimeout(connection, timeout, nil);
260 }
261
262 /// Parses [possibly incomplete] CONNECT response and reacts to it.
263 void
264 Http::Tunneler::handleResponse(const bool eof)
265 {
266 // mimic the basic parts of HttpStateData::processReplyHeader()
267 if (hp == nullptr)
268 hp = new Http1::ResponseParser;
269
270 auto parsedOk = hp->parse(readBuf); // may be refined below
271 readBuf = hp->remaining();
272 if (hp->needsMoreData()) {
273 if (!eof) {
274 if (readBuf.length() >= SQUID_TCP_SO_RCVBUF) {
275 bailOnResponseError("huge CONNECT response from peer", nullptr);
276 return;
277 }
278 readMore();
279 return;
280 }
281
282 //eof, handle truncated response
283 readBuf.append("\r\n\r\n", 4);
284 parsedOk = hp->parse(readBuf);
285 readBuf.clear();
286 }
287
288 if (!parsedOk) {
289 bailOnResponseError("malformed CONNECT response from peer", nullptr);
290 return;
291 }
292
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);
298 return;
299 }
300
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();
305
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) <<
309 "----------");
310
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());
315 return;
316 }
317
318 // preserve any bytes sent by the server after the CONNECT response
319 futureAnswer.leftovers = readBuf;
320
321 tunnelEstablished = true;
322 debugs(83, 5, status());
323 }
324
325 void
326 Http::Tunneler::bailOnResponseError(const char *error, HttpReply *errorReply)
327 {
328 debugs(83, 3, error << status());
329
330 ErrorState *err;
331 if (errorReply) {
332 err = new ErrorState(request.getRaw(), errorReply);
333 } else {
334 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
335 err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
336 }
337 bailWith(err);
338 }
339
340 void
341 Http::Tunneler::bailWith(ErrorState *error)
342 {
343 Must(error);
344 answer().squidError = error;
345 callBack();
346 }
347
348 void
349 Http::Tunneler::callBack()
350 {
351 debugs(83, 5, connection << status());
352 auto cb = callback;
353 callback = nullptr;
354 ScheduleCallHere(cb);
355 }
356
357 void
358 Http::Tunneler::swanSong()
359 {
360 AsyncJob::swanSong();
361
362 if (callback) {
363 if (requestWritten && tunnelEstablished) {
364 assert(answer().positive());
365 callBack(); // success
366 } else {
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));
370 }
371 assert(!callback);
372 }
373
374 if (closer) {
375 comm_remove_close_handler(connection->fd, closer);
376 closer = nullptr;
377 }
378
379 if (reader) {
380 Comm::ReadCancel(connection->fd, reader);
381 reader = nullptr;
382 }
383 }
384
385 const char *
386 Http::Tunneler::status() const
387 {
388 static MemBuf buf;
389 buf.reset();
390
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);
400 }
401 if (connection != nullptr)
402 buf.appendf(" FD %d", connection->fd);
403 buf.appendf(" %s%u]", id.prefix(), id.value);
404 buf.terminate();
405
406 return buf.content();
407 }
408