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