]> git.ipfire.org Git - thirdparty/squid.git/blob - src/clients/HttpTunneler.cc
fb0c8d336f51a4905236c9e4ba6ba93a73be668b
[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 "StatCounters.h"
22 #include "SquidConfig.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 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
134 MemBuf mb;
135 mb.init();
136 mb.appendf("CONNECT %s HTTP/1.1\r\n", url.c_str());
137 HttpStateData::httpBuildRequestHeader(request.getRaw(),
138 nullptr, // StoreEntry
139 al,
140 &hdr_out,
141 flags);
142 hdr_out.packInto(&mb);
143 hdr_out.clean();
144 mb.append("\r\n", 2);
145
146 debugs(11, 2, "Tunnel Server REQUEST: " << connection <<
147 ":\n----------\n" << mb.buf << "\n----------");
148 fd_note(connection->fd, "Tunnel Server CONNECT");
149
150 typedef CommCbMemFunT<Http::Tunneler, CommIoCbParams> Dialer;
151 writer = JobCallback(5, 5, Dialer, this, Http::Tunneler::handleWrittenRequest);
152 Comm::Write(connection, &mb, writer);
153 }
154
155 /// Called when we are done writing a CONNECT request header to a peer.
156 void
157 Http::Tunneler::handleWrittenRequest(const CommIoCbParams &io)
158 {
159 Must(writer);
160 writer = nullptr;
161
162 if (io.flag == Comm::ERR_CLOSING)
163 return;
164
165 request->hier.notePeerWrite();
166
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;
170 bailWith(error);
171 return;
172 }
173
174 statCounter.server.all.kbytes_out += io.size;
175 statCounter.server.other.kbytes_out += io.size;
176 requestWritten = true;
177 debugs(83, 5, status());
178 }
179
180 /// Called when we read [a part of] CONNECT response from the peer
181 void
182 Http::Tunneler::handleReadyRead(const CommIoCbParams &io)
183 {
184 Must(reader);
185 reader = nullptr;
186
187 if (io.flag == Comm::ERR_CLOSING)
188 return;
189
190 CommIoCbParams rd(this);
191 rd.conn = io.conn;
192 #if USE_DELAY_POOLS
193 rd.size = delayId.bytesWanted(1, readBuf.spaceSize());
194 #else
195 rd.size = readBuf.spaceSize();
196 #endif
197
198 switch (Comm::ReadNow(rd, readBuf)) {
199 case Comm::INPROGRESS:
200 readMore();
201 return;
202
203 case Comm::OK: {
204 #if USE_DELAY_POOLS
205 delayId.bytesIn(rd.size);
206 #endif
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);
211 return;
212 }
213
214 case Comm::ENDFILE: {
215 // TODO: Should we (and everybody else) call request->hier.notePeerRead() on zero reads?
216 handleResponse(true);
217 return;
218 }
219
220 // case Comm::COMM_ERROR:
221 default: // no other flags should ever occur
222 {
223 const auto error = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request.getRaw(), al);
224 error->xerrno = rd.xerrno;
225 bailWith(error);
226 return;
227 }
228 }
229
230 assert(false); // not reached
231 }
232
233 void
234 Http::Tunneler::readMore()
235 {
236 Must(Comm::IsConnOpen(connection));
237 Must(!fd_table[connection->fd].closing());
238 Must(!reader);
239
240 typedef CommCbMemFunT<Http::Tunneler, CommIoCbParams> Dialer;
241 reader = JobCallback(93, 3, Dialer, this, Http::Tunneler::handleReadyRead);
242 Comm::Read(connection, reader);
243
244 AsyncCall::Pointer nil;
245 const auto timeout = Comm::MortalReadTimeout(startTime, lifetimeLimit);
246 commSetConnTimeout(connection, timeout, nil);
247 }
248
249 /// Parses [possibly incomplete] CONNECT response and reacts to it.
250 void
251 Http::Tunneler::handleResponse(const bool eof)
252 {
253 // mimic the basic parts of HttpStateData::processReplyHeader()
254 if (hp == nullptr)
255 hp = new Http1::ResponseParser;
256
257 auto parsedOk = hp->parse(readBuf); // may be refined below
258 readBuf = hp->remaining();
259 if (hp->needsMoreData()) {
260 if (!eof) {
261 if (readBuf.length() >= SQUID_TCP_SO_RCVBUF) {
262 bailOnResponseError("huge CONNECT response from peer", nullptr);
263 return;
264 }
265 readMore();
266 return;
267 }
268
269 //eof, handle truncated response
270 readBuf.append("\r\n\r\n", 4);
271 parsedOk = hp->parse(readBuf);
272 readBuf.clear();
273 }
274
275 if (!parsedOk) {
276 bailOnResponseError("malformed CONNECT response from peer", nullptr);
277 return;
278 }
279
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);
285 return;
286 }
287
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();
292
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) <<
296 "----------");
297
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());
302 return;
303 }
304
305 // preserve any bytes sent by the server after the CONNECT response
306 futureAnswer.leftovers = readBuf;
307
308 tunnelEstablished = true;
309 debugs(83, 5, status());
310 }
311
312 void
313 Http::Tunneler::bailOnResponseError(const char *error, HttpReply *errorReply)
314 {
315 debugs(83, 3, error << status());
316
317 ErrorState *err;
318 if (errorReply) {
319 err = new ErrorState(request.getRaw(), errorReply);
320 } else {
321 // with no reply suitable for relaying, answer with 502 (Bad Gateway)
322 err = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al);
323 }
324 bailWith(err);
325 }
326
327 void
328 Http::Tunneler::bailWith(ErrorState *error)
329 {
330 Must(error);
331 answer().squidError = error;
332 callBack();
333 }
334
335 void
336 Http::Tunneler::callBack()
337 {
338 debugs(83, 5, connection << status());
339 auto cb = callback;
340 callback = nullptr;
341 ScheduleCallHere(cb);
342 }
343
344 void
345 Http::Tunneler::swanSong()
346 {
347 AsyncJob::swanSong();
348
349 if (callback) {
350 if (requestWritten && tunnelEstablished) {
351 assert(answer().positive());
352 callBack(); // success
353 } else {
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));
357 }
358 assert(!callback);
359 }
360
361 if (closer) {
362 comm_remove_close_handler(connection->fd, closer);
363 closer = nullptr;
364 }
365
366 if (reader) {
367 Comm::ReadCancel(connection->fd, reader);
368 reader = nullptr;
369 }
370 }
371
372 const char *
373 Http::Tunneler::status() const
374 {
375 static MemBuf buf;
376 buf.reset();
377
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);
387 }
388 if (connection != nullptr)
389 buf.appendf(" FD %d", connection->fd);
390 buf.appendf(" %s%u]", id.prefix(), id.value);
391 buf.terminate();
392
393 return buf.content();
394 }
395