]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
31b12d3e584ef1f19ba5319b9fa9c05eed1f8b68
[thirdparty/squid.git] / src / clients / HttpTunneler.cc
1 /*
2 * Copyright (C) 1996-2019 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
210 switch (Comm::ReadNow(rd, readBuf)) {
211 case Comm::INPROGRESS:
212 readMore();
213 return;
214
215 case Comm::OK: {
216 #if USE_DELAY_POOLS
217 delayId.bytesIn(rd.size);
218 #endif
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);
223 return;
224 }
225
226 case Comm::ENDFILE: {
227 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
228 handleResponse(true);
229 return;
230 }
231
232 // case Comm::COMM_ERROR:
233 default: // no other flags should ever occur
234 {
235 const auto error = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request.getRaw(), al);
236 error->xerrno = rd.xerrno;
237 bailWith(error);
238 return;
239 }
240 }
241
242 assert(false); // not reached
243 }
244
245 void
246 Http::Tunneler::readMore()
247 {
248 Must(Comm::IsConnOpen(connection));
249 Must(!fd_table[connection->fd].closing());
250 Must(!reader);
251
252 typedef CommCbMemFunT<Http::Tunneler, CommIoCbParams> Dialer;
253 reader = JobCallback(93, 3, Dialer, this, Http::Tunneler::handleReadyRead);
254 Comm::Read(connection, reader);
255
256 AsyncCall::Pointer nil;
257 const auto timeout = Comm::MortalReadTimeout(startTime, lifetimeLimit);
258 commSetConnTimeout(connection, timeout, nil);
259 }
260
261 /// Parses [possibly incomplete] CONNECT response and reacts to it.
262 void
263 Http::Tunneler::handleResponse(const bool eof)
264 {
265 // mimic the basic parts of HttpStateData::processReplyHeader()
266 if (hp == nullptr)
267 hp = new Http1::ResponseParser;
268
269 auto parsedOk = hp->parse(readBuf); // may be refined below
270 readBuf = hp->remaining();
271 if (hp->needsMoreData()) {
272 if (!eof) {
273 if (readBuf.length() >= SQUID_TCP_SO_RCVBUF) {
274 bailOnResponseError("huge CONNECT response from peer", nullptr);
275 return;
276 }
277 readMore();
278 return;
279 }
280
281 //eof, handle truncated response
282 readBuf.append("\r\n\r\n", 4);
283 parsedOk = hp->parse(readBuf);
284 readBuf.clear();
285 }
286
287 if (!parsedOk) {
288 bailOnResponseError("malformed CONNECT response from peer", nullptr);
289 return;
290 }
291
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);
297 return;
298 }
299
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();
304
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) <<
308 "----------");
309
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());
314 return;
315 }
316
317 // preserve any bytes sent by the server after the CONNECT response
318 futureAnswer.leftovers = readBuf;
319
320 tunnelEstablished = true;
321 debugs(83, 5, status());
322 }
323
324 void
325 Http::Tunneler::bailOnResponseError(const char *error, HttpReply *errorReply)
326 {
327 debugs(83, 3, error << status());
328
329 ErrorState *err;
330 if (errorReply) {
331 err = new ErrorState(request.getRaw(), errorReply);
332 } else {
333 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
334 err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
335 }
336 bailWith(err);
337 }
338
339 void
340 Http::Tunneler::bailWith(ErrorState *error)
341 {
342 Must(error);
343 answer().squidError = error;
344 callBack();
345 }
346
347 void
348 Http::Tunneler::callBack()
349 {
350 debugs(83, 5, connection << status());
351 auto cb = callback;
352 callback = nullptr;
353 ScheduleCallHere(cb);
354 }
355
356 void
357 Http::Tunneler::swanSong()
358 {
359 AsyncJob::swanSong();
360
361 if (callback) {
362 if (requestWritten && tunnelEstablished) {
363 assert(answer().positive());
364 callBack(); // success
365 } else {
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));
369 }
370 assert(!callback);
371 }
372
373 if (closer) {
374 comm_remove_close_handler(connection->fd, closer);
375 closer = nullptr;
376 }
377
378 if (reader) {
379 Comm::ReadCancel(connection->fd, reader);
380 reader = nullptr;
381 }
382 }
383
384 const char *
385 Http::Tunneler::status() const
386 {
387 static MemBuf buf;
388 buf.reset();
389
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);
399 }
400 if (connection != nullptr)
401 buf.appendf(" FD %d", connection->fd);
402 buf.appendf(" %s%u]", id.prefix(), id.value);
403 buf.terminate();
404
405 return buf.content();
406 }
407