]> git.ipfire.org Git - thirdparty/squid.git/blob - src/adaptation/icap/ServiceRep.cc
Support libecap::host::xaction::blockVirgin() API, serving ERR_ACCESS_DENIED.
[thirdparty/squid.git] / src / adaptation / icap / ServiceRep.cc
1 /*
2 * DEBUG: section 93 ICAP (RFC 3507) Client
3 */
4
5 #include "squid.h"
6 #include "adaptation/icap/Config.h"
7 #include "adaptation/icap/ModXact.h"
8 #include "adaptation/icap/Options.h"
9 #include "adaptation/icap/OptXact.h"
10 #include "adaptation/icap/ServiceRep.h"
11 #include "base/TextException.h"
12 #include "ConfigParser.h"
13 #include "HttpReply.h"
14 #include "SquidTime.h"
15
16 CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Icap, ServiceRep);
17
18 Adaptation::Icap::ServiceRep::ServiceRep(const Adaptation::ServiceConfig &svcCfg):
19 AsyncJob("Adaptation::Icap::ServiceRep"), Adaptation::Service(svcCfg),
20 theOptions(NULL), theOptionsFetcher(0), theLastUpdate(0),
21 isSuspended(0), notifying(false),
22 updateScheduled(false),
23 wasAnnouncedUp(true), // do not announce an "up" service at startup
24 isDetached(false)
25 {}
26
27 Adaptation::Icap::ServiceRep::~ServiceRep()
28 {
29 Must(!theOptionsFetcher);
30 delete theOptions;
31 }
32
33 void
34 Adaptation::Icap::ServiceRep::finalize()
35 {
36 Adaptation::Service::finalize();
37
38 // use /etc/services or default port if needed
39 const bool have_port = cfg().port >= 0;
40 if (!have_port) {
41 struct servent *serv = getservbyname("icap", "tcp");
42
43 if (serv) {
44 writeableCfg().port = htons(serv->s_port);
45 } else {
46 writeableCfg().port = 1344;
47 }
48 }
49
50 theSessionFailures.configure(TheConfig.oldest_service_failure > 0 ?
51 TheConfig.oldest_service_failure : -1);
52 }
53
54 void Adaptation::Icap::ServiceRep::noteFailure()
55 {
56 const int failures = theSessionFailures.count(1);
57 debugs(93,4, HERE << " failure " << failures << " out of " <<
58 TheConfig.service_failure_limit << " allowed in " <<
59 TheConfig.oldest_service_failure << "sec " << status());
60
61 if (isSuspended)
62 return;
63
64 if (TheConfig.service_failure_limit >= 0 &&
65 failures > TheConfig.service_failure_limit)
66 suspend("too many failures");
67
68 // TODO: Should bypass setting affect how much Squid tries to talk to
69 // the ICAP service that is currently unusable and is likely to remain
70 // so for some time? The current code says "no". Perhaps the answer
71 // should be configurable.
72 }
73
74 void Adaptation::Icap::ServiceRep::suspend(const char *reason)
75 {
76 if (isSuspended) {
77 debugs(93,4, HERE << "keeping suspended, also for " << reason);
78 } else {
79 isSuspended = reason;
80 debugs(93,1, "suspending ICAP service for " << reason);
81 scheduleUpdate(squid_curtime + TheConfig.service_revival_delay);
82 announceStatusChange("suspended", true);
83 }
84 }
85
86 bool Adaptation::Icap::ServiceRep::probed() const
87 {
88 return theLastUpdate != 0;
89 }
90
91 bool Adaptation::Icap::ServiceRep::hasOptions() const
92 {
93 return theOptions && theOptions->valid() && theOptions->fresh();
94 }
95
96 bool Adaptation::Icap::ServiceRep::up() const
97 {
98 return !isSuspended && hasOptions();
99 }
100
101 bool Adaptation::Icap::ServiceRep::wantsUrl(const String &urlPath) const
102 {
103 Must(hasOptions());
104 return theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferIgnore;
105 }
106
107 bool Adaptation::Icap::ServiceRep::wantsPreview(const String &urlPath, size_t &wantedSize) const
108 {
109 Must(hasOptions());
110
111 if (theOptions->preview < 0)
112 return false;
113
114 if (theOptions->transferKind(urlPath) != Adaptation::Icap::Options::xferPreview)
115 return false;
116
117 wantedSize = theOptions->preview;
118
119 return true;
120 }
121
122 bool Adaptation::Icap::ServiceRep::allows204() const
123 {
124 Must(hasOptions());
125 return true; // in the future, we may have ACLs to prevent 204s
126 }
127
128 bool Adaptation::Icap::ServiceRep::allows206() const
129 {
130 Must(hasOptions());
131 if (theOptions->allow206)
132 return true; // in the future, we may have ACLs to prevent 206s
133 return false;
134 }
135
136
137 static
138 void ServiceRep_noteTimeToUpdate(void *data)
139 {
140 Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data);
141 Must(service);
142 service->noteTimeToUpdate();
143 }
144
145 void Adaptation::Icap::ServiceRep::noteTimeToUpdate()
146 {
147 if (!detached())
148 updateScheduled = false;
149
150 if (detached() || theOptionsFetcher.set()) {
151 debugs(93,5, HERE << "ignores options update " << status());
152 return;
153 }
154
155 debugs(93,5, HERE << "performs a regular options update " << status());
156 startGettingOptions();
157 }
158
159 #if 0
160 static
161 void Adaptation::Icap::ServiceRep_noteTimeToNotify(void *data)
162 {
163 Adaptation::Icap::ServiceRep *service = static_cast<Adaptation::Icap::ServiceRep*>(data);
164 Must(service);
165 service->noteTimeToNotify();
166 }
167 #endif
168
169 void Adaptation::Icap::ServiceRep::noteTimeToNotify()
170 {
171 Must(!notifying);
172 notifying = true;
173 debugs(93,7, HERE << "notifies " << theClients.size() << " clients " <<
174 status());
175
176 // note: we must notify even if we are invalidated
177
178 Pointer us = NULL;
179
180 while (!theClients.empty()) {
181 Client i = theClients.pop_back();
182 ScheduleCallHere(i.callback);
183 i.callback = 0;
184 }
185
186 notifying = false;
187 }
188
189 void Adaptation::Icap::ServiceRep::callWhenReady(AsyncCall::Pointer &cb)
190 {
191 Must(cb!=NULL);
192
193 debugs(93,5, HERE << "Adaptation::Icap::Service is asked to call " << *cb <<
194 " when ready " << status());
195
196 Must(!broken()); // we do not wait for a broken service
197
198 Client i;
199 i.service = Pointer(this); // TODO: is this really needed?
200 i.callback = cb;
201 theClients.push_back(i);
202
203 if (theOptionsFetcher.set() || notifying)
204 return; // do nothing, we will be picked up in noteTimeToNotify()
205
206 if (needNewOptions())
207 startGettingOptions();
208 else
209 scheduleNotification();
210 }
211
212 void Adaptation::Icap::ServiceRep::scheduleNotification()
213 {
214 debugs(93,7, HERE << "will notify " << theClients.size() << " clients");
215 CallJobHere(93, 5, this, Adaptation::Icap::ServiceRep, noteTimeToNotify);
216 }
217
218 bool Adaptation::Icap::ServiceRep::needNewOptions() const
219 {
220 return !detached() && !up();
221 }
222
223 void Adaptation::Icap::ServiceRep::changeOptions(Adaptation::Icap::Options *newOptions)
224 {
225 debugs(93,8, HERE << "changes options from " << theOptions << " to " <<
226 newOptions << ' ' << status());
227
228 delete theOptions;
229 theOptions = newOptions;
230 theSessionFailures.clear();
231 isSuspended = 0;
232 theLastUpdate = squid_curtime;
233
234 checkOptions();
235 announceStatusChange("down after an options fetch failure", true);
236 }
237
238 void Adaptation::Icap::ServiceRep::checkOptions()
239 {
240 if (theOptions == NULL)
241 return;
242
243 if (!theOptions->valid()) {
244 debugs(93,1, "WARNING: Squid got an invalid ICAP OPTIONS response " <<
245 "from service " << cfg().uri << "; error: " << theOptions->error);
246 return;
247 }
248
249 /*
250 * Issue a warning if the ICAP server returned methods in the
251 * options response that don't match the method from squid.conf.
252 */
253
254 if (!theOptions->methods.empty()) {
255 bool method_found = false;
256 String method_list;
257 Vector <ICAP::Method>::iterator iter = theOptions->methods.begin();
258
259 while (iter != theOptions->methods.end()) {
260
261 if (*iter == cfg().method) {
262 method_found = true;
263 break;
264 }
265
266 method_list.append(ICAP::methodStr(*iter));
267 method_list.append(" ", 1);
268 iter++;
269 }
270
271 if (!method_found) {
272 debugs(93,1, "WARNING: Squid is configured to use ICAP method " <<
273 cfg().methodStr() <<
274 " for service " << cfg().uri <<
275 " but OPTIONS response declares the methods are " << method_list);
276 }
277 }
278
279
280 /*
281 * Check the ICAP server's date header for clock skew
282 */
283 const int skew = (int)(theOptions->timestamp() - squid_curtime);
284 if (abs(skew) > theOptions->ttl()) {
285 // TODO: If skew is negative, the option will be considered down
286 // because of stale options. We should probably change this.
287 debugs(93, 1, "ICAP service's clock is skewed by " << skew <<
288 " seconds: " << cfg().uri);
289 }
290 }
291
292 void Adaptation::Icap::ServiceRep::announceStatusChange(const char *downPhrase, bool important) const
293 {
294 if (wasAnnouncedUp == up()) // no significant changes to announce
295 return;
296
297 const char *what = cfg().bypass ? "optional" : "essential";
298 const char *state = wasAnnouncedUp ? downPhrase : "up";
299 const int level = important ? 1 :2;
300 debugs(93,level, what << " ICAP service is " << state << ": " <<
301 cfg().uri << ' ' << status());
302
303 wasAnnouncedUp = !wasAnnouncedUp;
304 }
305
306 // we are receiving ICAP OPTIONS response headers here or NULL on failures
307 void Adaptation::Icap::ServiceRep::noteAdaptationAnswer(const Answer &answer)
308 {
309 Must(initiated(theOptionsFetcher));
310 clearAdaptation(theOptionsFetcher);
311
312 if (answer.kind == Answer::akError) {
313 debugs(93,3, HERE << "failed to fetch options " << status());
314 handleNewOptions(0);
315 return;
316 }
317
318 Must(answer.kind == Answer::akForward); // no akBlock for OPTIONS requests
319 HttpMsg *msg = answer.message;
320 Must(msg);
321
322 debugs(93,5, HERE << "is interpreting new options " << status());
323
324 Adaptation::Icap::Options *newOptions = NULL;
325 if (HttpReply *r = dynamic_cast<HttpReply*>(msg)) {
326 newOptions = new Adaptation::Icap::Options;
327 newOptions->configure(r);
328 } else {
329 debugs(93,1, "ICAP service got wrong options message " << status());
330 }
331
332 handleNewOptions(newOptions);
333 }
334
335 // we (a) must keep trying to get OPTIONS and (b) are RefCounted so we
336 // must keep our job alive (XXX: until nobody needs us)
337 void Adaptation::Icap::ServiceRep::callException(const std::exception &e)
338 {
339 clearAdaptation(theOptionsFetcher);
340 debugs(93,2, "ICAP probably failed to fetch options (" << e.what() <<
341 ")" << status());
342 handleNewOptions(0);
343 }
344
345 void Adaptation::Icap::ServiceRep::handleNewOptions(Adaptation::Icap::Options *newOptions)
346 {
347 // new options may be NULL
348 changeOptions(newOptions);
349
350 debugs(93,3, HERE << "got new options and is now " << status());
351
352 scheduleUpdate(optionsFetchTime());
353 scheduleNotification();
354 }
355
356 void Adaptation::Icap::ServiceRep::startGettingOptions()
357 {
358 Must(!theOptionsFetcher);
359 debugs(93,6, HERE << "will get new options " << status());
360
361 // XXX: "this" here is "self"; works until refcounting API changes
362 theOptionsFetcher = initiateAdaptation(
363 new Adaptation::Icap::OptXactLauncher(this));
364 // TODO: timeout in case Adaptation::Icap::OptXact never calls us back?
365 // Such a timeout should probably be a generic AsyncStart feature.
366 }
367
368 void Adaptation::Icap::ServiceRep::scheduleUpdate(time_t when)
369 {
370 if (updateScheduled) {
371 debugs(93,7, HERE << "reschedules update");
372 // XXX: check whether the event is there because AR saw
373 // an unreproducible eventDelete assertion on 2007/06/18
374 if (eventFind(&ServiceRep_noteTimeToUpdate, this))
375 eventDelete(&ServiceRep_noteTimeToUpdate, this);
376 else
377 debugs(93,1, "XXX: ICAP service lost an update event.");
378 updateScheduled = false;
379 }
380
381 debugs(93,7, HERE << "raw OPTIONS fetch at " << when << " or in " <<
382 (when - squid_curtime) << " sec");
383 debugs(93,9, HERE << "last fetched at " << theLastUpdate << " or " <<
384 (squid_curtime - theLastUpdate) << " sec ago");
385
386 /* adjust update time to prevent too-frequent updates */
387
388 if (when < squid_curtime)
389 when = squid_curtime;
390
391 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
392 const int minUpdateGap = 30; // seconds
393 if (when < theLastUpdate + minUpdateGap)
394 when = theLastUpdate + minUpdateGap;
395
396 const int delay = when - squid_curtime;
397 debugs(93,5, HERE << "will fetch OPTIONS in " << delay << " sec");
398
399 eventAdd("Adaptation::Icap::ServiceRep::noteTimeToUpdate",
400 &ServiceRep_noteTimeToUpdate, this, delay, 0, true);
401 updateScheduled = true;
402 }
403
404 // returns absolute time when OPTIONS should be fetched
405 time_t
406 Adaptation::Icap::ServiceRep::optionsFetchTime() const
407 {
408 if (theOptions && theOptions->valid()) {
409 const time_t expire = theOptions->expire();
410 debugs(93,7, HERE << "options expire on " << expire << " >= " << squid_curtime);
411
412 // conservative estimate of how long the OPTIONS transaction will take
413 // XXX: move hard-coded constants from here to Adaptation::Icap::TheConfig
414 const int expectedWait = 20; // seconds
415
416 // Unknown or invalid (too small) expiration times should not happen.
417 // Adaptation::Icap::Options should use the default TTL, and ICAP servers should not
418 // send invalid TTLs, but bugs and attacks happen.
419 if (expire < expectedWait)
420 return squid_curtime;
421 else
422 return expire - expectedWait; // before the current options expire
423 }
424
425 // use revival delay as "expiration" time for a service w/o valid options
426 return squid_curtime + TheConfig.service_revival_delay;
427 }
428
429 Adaptation::Initiate *
430 Adaptation::Icap::ServiceRep::makeXactLauncher(HttpMsg *virgin,
431 HttpRequest *cause)
432 {
433 return new Adaptation::Icap::ModXactLauncher(virgin, cause, this);
434 }
435
436 // returns a temporary string depicting service status, for debugging
437 const char *Adaptation::Icap::ServiceRep::status() const
438 {
439 static MemBuf buf;
440
441 buf.reset();
442 buf.append("[", 1);
443
444 if (up())
445 buf.append("up", 2);
446 else {
447 buf.append("down", 4);
448 if (isSuspended)
449 buf.append(",susp", 5);
450
451 if (!theOptions)
452 buf.append(",!opt", 5);
453 else if (!theOptions->valid())
454 buf.append(",!valid", 7);
455 else if (!theOptions->fresh())
456 buf.append(",stale", 6);
457 }
458
459 if (detached())
460 buf.append(",detached", 9);
461
462 if (theOptionsFetcher.set())
463 buf.append(",fetch", 6);
464
465 if (notifying)
466 buf.append(",notif", 6);
467
468 if (const int failures = theSessionFailures.remembered())
469 buf.Printf(",fail%d", failures);
470
471 buf.append("]", 1);
472 buf.terminate();
473
474 return buf.content();
475 }
476
477 void Adaptation::Icap::ServiceRep::detach()
478 {
479 debugs(93,3, HERE << "detaching ICAP service: " << cfg().uri <<
480 ' ' << status());
481 isDetached = true;
482 }
483
484 bool Adaptation::Icap::ServiceRep::detached() const
485 {
486 return isDetached;
487 }