]> git.ipfire.org Git - thirdparty/squid.git/blame - src/peer_select.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / peer_select.cc
CommitLineData
062e2281 1/*
f70aedc4 2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
062e2281 7 */
8
bbc27441
AJ
9/* DEBUG: section 44 Peer Selection Algorithm */
10
582c2af2
FC
11#include "squid.h"
12#include "acl/FilledChecklist.h"
9865de72 13#include "base/AsyncCbdataCalls.h"
6043e368 14#include "base/InstanceId.h"
a011edee 15#include "CachePeer.h"
21c22f04 16#include "carp.h"
582c2af2 17#include "client_side.h"
4a3b98d7 18#include "dns/LookupDetails.h"
a37fdd8a 19#include "errorpage.h"
a553a5a3 20#include "event.h"
eb13c21e 21#include "FwdState.h"
af69c635 22#include "globals.h"
bbaf2685 23#include "hier_code.h"
924f73bc 24#include "htcp.h"
d3dddfb5 25#include "http/Stream.h"
582c2af2 26#include "HttpRequest.h"
9b5c4a9a 27#include "icmp/net_db.h"
582c2af2 28#include "ICP.h"
c7a52e6d 29#include "ip/tools.h"
602d9612 30#include "ipcache.h"
f0ba2534 31#include "neighbors.h"
f795b373 32#include "peer_sourcehash.h"
37236ba1 33#include "peer_userhash.h"
582c2af2 34#include "PeerSelectState.h"
4d5904f7 35#include "SquidConfig.h"
582c2af2
FC
36#include "SquidTime.h"
37#include "Store.h"
9865de72 38#include "util.h" // for tvSubDsec() which should be in SquidTime.h
062e2281 39
6043e368
AR
40/**
41 * A CachePeer which has been selected as a possible destination.
42 * Listed as pointers here so as to prevent duplicates being added but will
43 * be converted to a set of IP address path options before handing back out
44 * to the caller.
45 *
46 * Certain connection flags and outgoing settings will also be looked up and
47 * set based on the received request and CachePeer settings before handing back.
48 */
49class FwdServer
50{
51 MEMPROXY_CLASS(FwdServer);
52
53public:
54 FwdServer(CachePeer *p, hier_code c) :
55 _peer(p),
56 code(c),
57 next(nullptr)
58 {}
59
60 CbcPointer<CachePeer> _peer; /* NULL --> origin server */
61 hier_code code;
62 FwdServer *next;
63};
64
26ac0430 65static struct {
75e88d56 66 int timeouts;
2fadd50d 67} PeerStats;
062e2281 68
26ac0430
AJ
69static const char *DirectStr[] = {
70 "DIRECT_UNKNOWN",
71 "DIRECT_NO",
72 "DIRECT_MAYBE",
73 "DIRECT_YES"
74};
75e88d56 75
e7b08eec
AR
76/// a helper class to report a selected destination (for debugging)
77class PeerSelectionDumper
78{
79public:
cb835136
AR
80 PeerSelectionDumper(const PeerSelector * const aSelector, const CachePeer * const aPeer, const hier_code aCode):
81 selector(aSelector), peer(aPeer), code(aCode) {}
e7b08eec 82
cb835136 83 const PeerSelector * const selector; ///< selection parameters
e7b08eec
AR
84 const CachePeer * const peer; ///< successful selection info
85 const hier_code code; ///< selection algorithm
86};
87
cb835136 88CBDATA_CLASS_INIT(PeerSelector);
aa839030 89
e7b08eec
AR
90/// prints PeerSelectionDumper (for debugging)
91static std::ostream &
92operator <<(std::ostream &os, const PeerSelectionDumper &fsd)
93{
94 os << hier_code_str[fsd.code];
95
96 if (fsd.peer)
97 os << '/' << fsd.peer->host;
cb835136
AR
98 else if (fsd.selector) // useful for DIRECT and gone PINNED destinations
99 os << '#' << fsd.selector->request->url.host();
e7b08eec
AR
100
101 return os;
102}
103
9865de72
EB
104/// An ICP ping timeout service.
105/// Protects event.cc (which is designed to handle a few unrelated timeouts)
106/// from exposure to thousands of ping-related timeouts on busy proxies.
107class PeerSelectorPingMonitor
108{
109public:
110 /// registers the given selector to be notified about the IPC ping timeout
111 void monitor(PeerSelector *);
112
113 /// removes a PeerSelector from the waiting list
114 void forget(PeerSelector *);
115
116 /// \returns a (nil) registration of a non-waiting peer selector
117 WaitingPeerSelectorPosition npos() { return selectors.end(); }
118
119private:
120 static void NoteWaitOver(void *monitor);
121
122 void startWaiting();
123 void abortWaiting();
124 void noteWaitOver();
125
126 WaitingPeerSelectors selectors; ///< \see WaitingPeerSelectors
127};
128
129/// monitors all PeerSelector ICP ping timeouts
130static PeerSelectorPingMonitor &
131PingMonitor()
132{
133 static const auto Instance = new PeerSelectorPingMonitor();
134 return *Instance;
135}
136
137/* PeerSelectorPingMonitor */
138
139/// PeerSelectorPingMonitor::noteWaitOver() wrapper
140void
141PeerSelectorPingMonitor::NoteWaitOver(void *raw)
142{
143 assert(raw);
144 static_cast<PeerSelectorPingMonitor*>(raw)->noteWaitOver();
145}
146
147/// schedules a single event to represent all waiting selectors
148void
149PeerSelectorPingMonitor::startWaiting()
150{
151 assert(!selectors.empty());
152 const auto interval = tvSubDsec(current_time, selectors.begin()->first);
153 eventAdd("PeerSelectorPingMonitor::NoteWaitOver", &PeerSelectorPingMonitor::NoteWaitOver, this, interval, 0, false);
154}
155
156/// undoes an earlier startWaiting() call
157void
158PeerSelectorPingMonitor::abortWaiting()
159{
160 // our event may be already in the AsyncCallQueue but that is OK:
161 // such queued calls cannot accumulate, and we ignore any stale ones
162 eventDelete(&PeerSelectorPingMonitor::NoteWaitOver, nullptr);
163}
164
165/// calls back all ready PeerSelectors and continues to wait for others
166void
167PeerSelectorPingMonitor::noteWaitOver()
168{
169 while (!selectors.empty() && current_time >= selectors.begin()->first) {
170 const auto selector = selectors.begin()->second;
171 CallBack(selector->al, [selector,this] {
172 selector->ping.monitorRegistration = npos();
173 AsyncCall::Pointer callback = asyncCall(44, 4, "PeerSelector::HandlePingTimeout",
70ac5b29 174 cbdataDialer(PeerSelector::HandlePingTimeout, selector));
9865de72
EB
175 ScheduleCallHere(callback);
176 });
177 selectors.erase(selectors.begin());
178 }
179
180 if (!selectors.empty()) {
181 // Since abortWaiting() is unreliable, we may have been awakened by a
182 // stale event A after event B has been scheduled. Now we are going to
183 // schedule event C. Prevent event accumulation by deleting B (if any).
184 abortWaiting();
185
186 startWaiting();
187 }
188}
189
190void
191PeerSelectorPingMonitor::monitor(PeerSelector *selector)
192{
193 assert(selector);
194
195 const auto deadline = selector->ping.deadline();
196 const auto position = selectors.emplace(deadline, selector);
197 selector->ping.monitorRegistration = position;
198
199 if (position == selectors.begin()) {
200 if (selectors.size() > 1)
201 abortWaiting(); // remove the previously scheduled earlier event
202 startWaiting();
203 } // else the already scheduled event is still the earliest one
204}
205
206void
207PeerSelectorPingMonitor::forget(PeerSelector *selector)
208{
209 assert(selector);
210
211 if (selector->ping.monitorRegistration == npos())
212 return; // already forgotten
213
214 const auto wasFirst = selector->ping.monitorRegistration == selectors.begin();
215 selectors.erase(selector->ping.monitorRegistration);
216 selector->ping.monitorRegistration = npos();
217
218 if (wasFirst) {
219 // do not reschedule if there are still elements with the same deadline
220 if (!selectors.empty() && selectors.begin()->first == selector->ping.deadline())
221 return;
222 abortWaiting();
223 if (!selectors.empty())
224 startWaiting();
225 } // else do nothing since the old scheduled event is still the earliest one
226}
227
228/* PeerSelector */
229
cb835136 230PeerSelector::~PeerSelector()
348b2031 231{
36339742
AJ
232 while (servers) {
233 FwdServer *next = servers->next;
3c670b50 234 delete servers;
36339742
AJ
235 servers = next;
236 }
237
9865de72
EB
238 cancelPingTimeoutMonitoring();
239
029c8349
AJ
240 if (entry) {
241 debugs(44, 3, entry->url());
029c8349 242 entry->ping_status = PING_DONE;
5ae21d99
AJ
243 }
244
029c8349 245 if (acl_checklist) {
cb835136 246 debugs(44, DBG_IMPORTANT, "BUG: peer selector gone while waiting for a slow ACL");
029c8349 247 delete acl_checklist;
348b2031 248 }
62e76326 249
029c8349 250 HTTPMSGUNLOCK(request);
62e76326 251
029c8349
AJ
252 if (entry) {
253 assert(entry->ping_status != PING_WAITING);
1f90fd4a 254 entry->unlock("peerSelect");
029c8349 255 entry = NULL;
73a201f8 256 }
62e76326 257
029c8349 258 delete lastError;
348b2031 259}
062e2281 260
9865de72
EB
261void
262PeerSelector::startPingWaiting()
263{
264 assert(entry);
265 assert(entry->ping_status != PING_WAITING);
266 PingMonitor().monitor(this);
267 entry->ping_status = PING_WAITING;
268}
269
270void
271PeerSelector::cancelPingTimeoutMonitoring()
272{
273 PingMonitor().forget(this);
274}
275
2d72d4fd 276static int
cb365059 277peerSelectIcpPing(PeerSelector *ps, int direct, StoreEntry * entry)
062e2281 278{
cb365059
EB
279 assert(ps);
280 HttpRequest *request = ps->request;
281
7b665aeb 282 int n;
db1cd23c 283 assert(entry);
284 assert(entry->ping_status == PING_NONE);
9bd6a36d 285 assert(direct != DIRECT_YES);
cb835136 286 debugs(44, 3, entry->url());
62e76326 287
45e5102d 288 if (!request->flags.hierarchical && direct != DIRECT_NO)
62e76326 289 return 0;
290
d46a87a8 291 if (EBIT_TEST(entry->flags, KEY_PRIVATE) && !neighbors_do_private_keys)
62e76326 292 if (direct != DIRECT_NO)
293 return 0;
294
cb365059 295 n = neighborsCount(ps);
62e76326 296
cb835136 297 debugs(44, 3, "counted " << n << " neighbors");
62e76326 298
7b665aeb 299 return n;
062e2281 300}
301
6043e368
AR
302static void
303peerSelect(PeerSelectionInitiator *initiator,
cfd66529 304 HttpRequest * request,
d4806c91 305 AccessLogEntry::Pointer const &al,
6043e368 306 StoreEntry * entry)
75e88d56 307{
86b389fc 308 if (entry)
7f06a3d8 309 debugs(44, 3, *entry << ' ' << entry->url());
86b389fc 310 else
7f06a3d8 311 debugs(44, 3, request->method);
62e76326 312
cb835136 313 const auto selector = new PeerSelector(initiator);
62e76326 314
cb835136
AR
315 selector->request = request;
316 HTTPMSGLOCK(selector->request);
317 selector->al = al;
62e76326 318
cb835136 319 selector->entry = entry;
62e76326 320
6cfa8966 321#if USE_CACHE_DIGESTS
62e76326 322
39edba21 323 request->hier.peer_select_start = current_time;
62e76326 324
39edba21 325#endif
62e76326 326
cb835136
AR
327 if (selector->entry)
328 selector->entry->lock("peerSelect");
62e76326 329
cb835136 330 selector->selectMore();
75e88d56 331}
332
6043e368
AR
333void
334PeerSelectionInitiator::startSelectingDestinations(HttpRequest *request, const AccessLogEntry::Pointer &ale, StoreEntry *entry)
335{
336 subscribed = true;
337 peerSelect(this, request, ale, entry);
338 // and wait for noteDestination() and/or noteDestinationsEnd() calls
339}
340
cb835136 341void
329c128c 342PeerSelector::checkNeverDirectDone(const Acl::Answer answer)
75e88d56 343{
cb835136
AR
344 acl_checklist = nullptr;
345 debugs(44, 3, answer);
346 never_direct = answer;
3985b3f8 347 switch (answer) {
ae026ec6 348 case ACCESS_ALLOWED:
f365d297 349 /** if never_direct says YES, do that. */
cb835136
AR
350 direct = DIRECT_NO;
351 debugs(44, 3, "direct = " << DirectStr[direct] << " (never_direct allow)");
ae026ec6
AJ
352 break;
353 case ACCESS_DENIED: // not relevant.
1f585949 354 case ACCESS_DUNNO: // not relevant.
ae026ec6 355 break;
1f585949 356 case ACCESS_AUTH_REQUIRED:
ae026ec6 357 debugs(44, DBG_IMPORTANT, "WARNING: never_direct resulted in " << answer << ". Username ACLs are not reliable here.");
1f585949 358 break;
ae026ec6 359 }
cb835136 360 selectMore();
75e88d56 361}
362
cb835136 363void
329c128c 364PeerSelector::CheckNeverDirectDone(Acl::Answer answer, void *data)
75e88d56 365{
cb835136
AR
366 static_cast<PeerSelector*>(data)->checkNeverDirectDone(answer);
367}
368
369void
329c128c 370PeerSelector::checkAlwaysDirectDone(const Acl::Answer answer)
cb835136
AR
371{
372 acl_checklist = nullptr;
373 debugs(44, 3, answer);
374 always_direct = answer;
3985b3f8 375 switch (answer) {
ae026ec6
AJ
376 case ACCESS_ALLOWED:
377 /** if always_direct says YES, do that. */
cb835136
AR
378 direct = DIRECT_YES;
379 debugs(44, 3, "direct = " << DirectStr[direct] << " (always_direct allow)");
ae026ec6
AJ
380 break;
381 case ACCESS_DENIED: // not relevant.
1f585949 382 case ACCESS_DUNNO: // not relevant.
ae026ec6 383 break;
1f585949 384 case ACCESS_AUTH_REQUIRED:
ae026ec6 385 debugs(44, DBG_IMPORTANT, "WARNING: always_direct resulted in " << answer << ". Username ACLs are not reliable here.");
1f585949 386 break;
ae026ec6 387 }
cb835136
AR
388 selectMore();
389}
390
391void
329c128c 392PeerSelector::CheckAlwaysDirectDone(Acl::Answer answer, void *data)
cb835136
AR
393{
394 static_cast<PeerSelector*>(data)->checkAlwaysDirectDone(answer);
75e88d56 395}
396
cb835136 397/// \returns true (after destroying "this") if the peer initiator is gone
6043e368 398/// \returns false (without side effects) otherwise
cb835136
AR
399bool
400PeerSelector::selectionAborted()
6043e368 401{
cb835136 402 if (interestedInitiator())
6043e368
AR
403 return false;
404
405 debugs(44, 3, "Aborting peer selection: Initiator gone or lost interest.");
cb835136 406 delete this;
6043e368
AR
407 return true;
408}
409
cb835136 410/// A single DNS resolution loop iteration: Converts selected FwdServer to IPs.
cfd66529 411void
cb835136 412PeerSelector::resolveSelected()
cfd66529 413{
cb835136 414 if (selectionAborted())
36339742 415 return;
6043e368 416
cb835136 417 FwdServer *fs = servers;
36339742 418
7177edfb
AJ
419 // Bug 3243: CVE 2009-0801
420 // Bypass of browser same-origin access control in intercepted communication
421 // To resolve this we must use only the original client destination when going DIRECT
422 // on intercepted traffic which failed Host verification
cb835136 423 const HttpRequest *req = request;
45e5102d 424 const bool isIntercepted = !req->flags.redirected &&
0d901ef4 425 (req->flags.intercepted || req->flags.interceptTproxy);
45e5102d 426 const bool useOriginalDst = Config.onoff.client_dst_passthru || !req->flags.hostVerified;
7177edfb
AJ
427 const bool choseDirect = fs && fs->code == HIER_DIRECT;
428 if (isIntercepted && useOriginalDst && choseDirect) {
d9371d7b
AJ
429 // check the client is still around before using any of its details
430 if (req->clientConnectionManager.valid()) {
431 // construct a "result" adding the ORIGINAL_DST to the set instead of DIRECT
432 Comm::ConnectionPointer p = new Comm::Connection();
433 p->remote = req->clientConnectionManager->clientConnection->local;
6043e368 434 fs->code = ORIGINAL_DST; // fs->code is DIRECT. This fixes the display.
cb835136 435 handlePath(p, *fs);
d9371d7b 436 }
7177edfb
AJ
437
438 // clear the used fs and continue
cb835136 439 servers = fs->next;
3c670b50 440 delete fs;
cb835136 441 resolveSelected();
7177edfb
AJ
442 return;
443 }
444
3dde9e52
CT
445 if (fs && fs->code == PINNED) {
446 // Nil path signals a PINNED destination selection. Our initiator should
447 // borrow and use clientConnectionManager's pinned connection object
448 // (regardless of that connection destination).
449 handlePath(nullptr, *fs);
450 servers = fs->next;
451 delete fs;
452 resolveSelected();
453 return;
454 }
455
cfd66529 456 // convert the list of FwdServer destinations into destinations IP addresses
cb835136 457 if (fs && wantsMoreDestinations()) {
cfd66529 458 // send the next one off for DNS lookup.
cb835136
AR
459 const char *host = fs->_peer.valid() ? fs->_peer->host : request->url.host();
460 debugs(44, 2, "Find IP destination for: " << url() << "' via " << host);
461 Dns::nbgethostbyname(host, this);
cfd66529
AJ
462 return;
463 }
464
c2f4e9cc
AJ
465 // Bug 3605: clear any extra listed FwdServer destinations, when the options exceeds max_foward_tries.
466 // due to the allocation method of fs, we must deallocate each manually.
467 // TODO: use a std::list so we can get the size and abort adding whenever the selection loops reach Config.forward_max_tries
6043e368 468 if (fs) {
cb835136 469 assert(fs == servers);
9bc68187 470 while (fs) {
cb835136 471 servers = fs->next;
3c670b50 472 delete fs;
cb835136 473 fs = servers;
c2f4e9cc
AJ
474 }
475 }
476
cfd66529 477 // done with DNS lookups. pass back to caller
62e76326 478
cb835136
AR
479 debugs(44, 2, id << " found all " << foundPaths << " destinations for " << url());
480 debugs(44, 2, " always_direct = " << always_direct);
481 debugs(44, 2, " never_direct = " << never_direct);
482 debugs(44, 2, " timedout = " << ping.timedout);
5ae21d99 483
cb835136
AR
484 ping.stop = current_time;
485 request->hier.ping = ping; // final result
3dfe8371 486
cb835136 487 if (lastError && foundPaths) {
6043e368
AR
488 // nobody cares about errors if we found destinations despite them
489 debugs(44, 3, "forgetting the last error");
cb835136
AR
490 delete lastError;
491 lastError = nullptr;
db1cd23c 492 }
62e76326 493
cb835136
AR
494 if (const auto initiator = interestedInitiator())
495 initiator->noteDestinationsEnd(lastError);
496 lastError = nullptr; // initiator owns the ErrorState object now
497 delete this;
93775f90 498}
499
fd9c47d1 500void
cb835136 501PeerSelector::noteLookup(const Dns::LookupDetails &details)
cfd66529 502{
fd9c47d1 503 /* ignore lookup delays that occurred after the initiator moved on */
cfd66529 504
cb835136 505 if (selectionAborted())
fd9c47d1 506 return;
cfd66529 507
fd9c47d1
AR
508 if (!wantsMoreDestinations())
509 return;
cfd66529 510
fd9c47d1
AR
511 request->recordLookup(details);
512}
5229395c 513
fd9c47d1 514void
cb835136 515PeerSelector::noteIp(const Ip::Address &ip)
fd9c47d1 516{
cb835136 517 if (selectionAborted())
fd9c47d1 518 return;
cfd66529 519
fd9c47d1
AR
520 if (!wantsMoreDestinations())
521 return;
27d1f0a0 522
fd9c47d1 523 const auto peer = servers->_peer.valid();
cfd66529 524
fd9c47d1
AR
525 // for TPROXY spoofing, we must skip unusable addresses
526 if (request->flags.spoofClientIp && !(peer && peer->options.no_tproxy) ) {
527 if (ip.isIPv4() != request->client_addr.isIPv4())
528 return; // cannot spoof the client address on this link
529 }
c7a52e6d 530
fd9c47d1
AR
531 Comm::ConnectionPointer p = new Comm::Connection();
532 p->remote = ip;
533 p->remote.port(peer ? peer->http_port : request->url.port());
534 handlePath(p, *servers);
535}
c7a52e6d 536
fd9c47d1 537void
cb835136 538PeerSelector::noteIps(const Dns::CachedIps *ia, const Dns::LookupDetails &details)
fd9c47d1 539{
cb835136 540 if (selectionAborted())
fd9c47d1 541 return;
cfd66529 542
fd9c47d1
AR
543 FwdServer *fs = servers;
544 if (!ia) {
545 debugs(44, 3, "Unknown host: " << (fs->_peer.valid() ? fs->_peer->host : request->url.host()));
a37fdd8a 546 // discard any previous error.
fd9c47d1
AR
547 delete lastError;
548 lastError = NULL;
a37fdd8a 549 if (fs->code == HIER_DIRECT) {
7e6eabbc 550 lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request, al);
fd9c47d1 551 lastError->dnsError = details.error;
a37fdd8a 552 }
cfd66529 553 }
fd9c47d1 554 // else noteIp() calls have already processed all IPs in *ia
cfd66529 555
fd9c47d1 556 servers = fs->next;
3c670b50 557 delete fs;
cfd66529 558
cb835136
AR
559 // continue resolving selected peers
560 resolveSelected();
cfd66529
AJ
561}
562
cb835136
AR
563int
564PeerSelector::checkNetdbDirect()
b3264694 565{
9b5c4a9a 566#if USE_ICMP
a3c6762c 567 CachePeer *p;
b3264694 568 int myrtt;
569 int myhops;
62e76326 570
cb835136 571 if (direct == DIRECT_NO)
62e76326 572 return 0;
573
9b5c4a9a
AJ
574 /* base lookup on RTT and Hops if ICMP NetDB is enabled. */
575
cb835136 576 myrtt = netdbHostRtt(request->url.host());
5c51bffb
AJ
577 debugs(44, 3, "MY RTT = " << myrtt << " msec");
578 debugs(44, 3, "minimum_direct_rtt = " << Config.minDirectRtt << " msec");
62e76326 579
5f84d830 580 if (myrtt && myrtt <= Config.minDirectRtt)
62e76326 581 return 1;
582
cb835136 583 myhops = netdbHostHops(request->url.host());
62e76326 584
cb835136
AR
585 debugs(44, 3, "MY hops = " << myhops);
586 debugs(44, 3, "minimum_direct_hops = " << Config.minDirectHops);
62e76326 587
b3264694 588 if (myhops && myhops <= Config.minDirectHops)
62e76326 589 return 1;
590
cb835136 591 p = whichPeer(closest_parent_miss);
62e76326 592
5f84d830 593 if (p == NULL)
62e76326 594 return 0;
595
cb835136 596 debugs(44, 3, "closest_parent_miss RTT = " << ping.p_rtt << " msec");
62e76326 597
cb835136 598 if (myrtt && myrtt <= ping.p_rtt)
62e76326 599 return 1;
600
9b5c4a9a
AJ
601#endif /* USE_ICMP */
602
b3264694 603 return 0;
604}
605
cb835136
AR
606void
607PeerSelector::selectMore()
062e2281 608{
cb835136 609 if (selectionAborted())
36339742 610 return;
36339742 611
5c51bffb 612 debugs(44, 3, request->method << ' ' << request->url.host());
62e76326 613
45e5102d 614 /** If we don't know whether DIRECT is permitted ... */
cb835136
AR
615 if (direct == DIRECT_UNKNOWN) {
616 if (always_direct == ACCESS_DUNNO) {
617 debugs(44, 3, "direct = " << DirectStr[direct] << " (always_direct to be checked)");
45e5102d 618 /** check always_direct; */
d4806c91 619 ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, NULL);
cb835136
AR
620 ch->al = al;
621 acl_checklist = ch;
cb365059 622 acl_checklist->syncAle(request, nullptr);
cb835136 623 acl_checklist->nonBlockingCheck(CheckAlwaysDirectDone, this);
62e76326 624 return;
cb835136
AR
625 } else if (never_direct == ACCESS_DUNNO) {
626 debugs(44, 3, "direct = " << DirectStr[direct] << " (never_direct to be checked)");
45e5102d 627 /** check never_direct; */
d4806c91 628 ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, NULL);
cb835136
AR
629 ch->al = al;
630 acl_checklist = ch;
cb365059 631 acl_checklist->syncAle(request, nullptr);
cb835136 632 acl_checklist->nonBlockingCheck(CheckNeverDirectDone, this);
62e76326 633 return;
450fe1cb 634 } else if (request->flags.noDirect) {
45e5102d 635 /** if we are accelerating, direct is not an option. */
cb835136
AR
636 direct = DIRECT_NO;
637 debugs(44, 3, "direct = " << DirectStr[direct] << " (forced non-direct)");
450fe1cb 638 } else if (request->flags.loopDetected) {
45e5102d 639 /** if we are in a forwarding-loop, direct is not an option. */
cb835136
AR
640 direct = DIRECT_YES;
641 debugs(44, 3, "direct = " << DirectStr[direct] << " (forwarding loop detected)");
642 } else if (checkNetdbDirect()) {
643 direct = DIRECT_YES;
644 debugs(44, 3, "direct = " << DirectStr[direct] << " (checkNetdbDirect)");
62e76326 645 } else {
cb835136
AR
646 direct = DIRECT_MAYBE;
647 debugs(44, 3, "direct = " << DirectStr[direct] << " (default)");
62e76326 648 }
649
cb835136 650 debugs(44, 3, "direct = " << DirectStr[direct]);
db1cd23c 651 }
62e76326 652
d67acb4e 653 if (!entry || entry->ping_status == PING_NONE)
cb835136 654 selectPinned();
df503d6c 655 if (entry == NULL) {
62e76326 656 (void) 0;
df503d6c 657 } else if (entry->ping_status == PING_NONE) {
cb835136 658 selectSomeNeighbor();
62e76326 659
660 if (entry->ping_status == PING_WAITING)
661 return;
db1cd23c 662 } else if (entry->ping_status == PING_WAITING) {
cb835136 663 selectSomeNeighborReplies();
9865de72 664 cancelPingTimeoutMonitoring();
62e76326 665 entry->ping_status = PING_DONE;
db1cd23c 666 }
62e76326 667
cb835136 668 switch (direct) {
62e76326 669
168dfda9 670 case DIRECT_YES:
cb835136 671 selectSomeDirect();
62e76326 672 break;
673
168dfda9 674 case DIRECT_NO:
cb835136
AR
675 selectSomeParent();
676 selectAllParents();
62e76326 677 break;
678
168dfda9 679 default:
62e76326 680
681 if (Config.onoff.prefer_direct)
cb835136 682 selectSomeDirect();
62e76326 683
45e5102d 684 if (request->flags.hierarchical || !Config.onoff.nonhierarchical_direct) {
cb835136
AR
685 selectSomeParent();
686 selectAllParents();
8b9a89c9 687 }
62e76326 688
689 if (!Config.onoff.prefer_direct)
cb835136 690 selectSomeDirect();
62e76326 691
692 break;
168dfda9 693 }
62e76326 694
cb835136
AR
695 // end peer selection; start resolving selected peers
696 resolveSelected();
db1cd23c 697}
698
cb365059 699bool peerAllowedToUse(const CachePeer *, PeerSelector*);
cfd66529 700
cb835136
AR
701/// Selects a pinned connection if it exists, is valid, and is allowed.
702void
703PeerSelector::selectPinned()
d67acb4e 704{
cb835136 705 // TODO: Avoid all repeated calls. Relying on PING_DONE is not enough.
d67acb4e
AJ
706 if (!request->pinnedConnection())
707 return;
3dde9e52 708
daf80700
CT
709 const auto peer = request->pinnedConnection()->pinnedPeer();
710 const auto usePinned = peer ? peerAllowedToUse(peer, this) : (direct != DIRECT_NO);
711 // If the pinned connection is prohibited (for this request) then
cb835136 712 // the initiator must decide whether it is OK to open a new one instead.
daf80700
CT
713 request->pinnedConnection()->pinning.peerAccessDenied = !usePinned;
714
715 addSelection(peer, PINNED);
716 if (entry)
717 entry->ping_status = PING_DONE; // skip ICP
d67acb4e
AJ
718}
719
27774cee 720/**
db1cd23c 721 * Selects a neighbor (parent or sibling) based on one of the
722 * following methods:
723 * Cache Digests
724 * CARP
9b5c4a9a 725 * ICMP Netdb RTT estimates
db1cd23c 726 * ICP/HTCP queries
727 */
cb835136
AR
728void
729PeerSelector::selectSomeNeighbor()
db1cd23c 730{
a3c6762c 731 CachePeer *p;
db1cd23c 732 hier_code code = HIER_NONE;
733 assert(entry->ping_status == PING_NONE);
62e76326 734
cb835136 735 if (direct == DIRECT_YES) {
62e76326 736 entry->ping_status = PING_DONE;
737 return;
124511e5 738 }
62e76326 739
6cfa8966 740#if USE_CACHE_DIGESTS
cb365059 741 if ((p = neighborsDigestSelect(this))) {
5c51bffb 742 if (neighborType(p, request->url) == PEER_PARENT)
62e76326 743 code = CD_PARENT_HIT;
744 else
745 code = CD_SIBLING_HIT;
db1cd23c 746 } else
c127134a 747#endif
cb365059 748 if ((p = netdbClosestParent(this))) {
62e76326 749 code = CLOSEST_PARENT;
cb365059 750 } else if (peerSelectIcpPing(this, direct, entry)) {
cb835136
AR
751 debugs(44, 3, "Doing ICP pings");
752 ping.start = current_time;
753 ping.n_sent = neighborsUdpPing(request,
754 entry,
755 HandlePingReply,
756 this,
757 &ping.n_replies_expected,
758 &ping.timeout);
9865de72
EB
759 // TODO: Refactor neighborsUdpPing() to guarantee positive timeouts.
760 if (ping.timeout < 0)
761 ping.timeout = 0;
cb835136
AR
762
763 if (ping.n_sent == 0)
fa84c01d 764 debugs(44, DBG_CRITICAL, "WARNING: neighborsUdpPing returned 0");
cb835136
AR
765 debugs(44, 3, ping.n_replies_expected <<
766 " ICP replies expected, RTT " << ping.timeout <<
26ac0430 767 " msec");
62e76326 768
cb835136 769 if (ping.n_replies_expected > 0) {
9865de72 770 startPingWaiting();
62e76326 771 return;
772 }
773 }
774
db1cd23c 775 if (code != HIER_NONE) {
62e76326 776 assert(p);
cb835136 777 addSelection(p, code);
db1cd23c 778 }
62e76326 779
db1cd23c 780 entry->ping_status = PING_DONE;
781}
782
cb835136
AR
783/// Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
784void
785PeerSelector::selectSomeNeighborReplies()
db1cd23c 786{
a3c6762c 787 CachePeer *p = NULL;
db1cd23c 788 hier_code code = HIER_NONE;
cb835136
AR
789 assert(entry->ping_status == PING_WAITING);
790 assert(direct != DIRECT_YES);
62e76326 791
cb835136 792 if (checkNetdbDirect()) {
62e76326 793 code = CLOSEST_DIRECT;
cb835136 794 addSelection(nullptr, code);
62e76326 795 return;
db1cd23c 796 }
62e76326 797
cb835136
AR
798 if ((p = hit)) {
799 code = hit_type == PEER_PARENT ? PARENT_HIT : SIBLING_HIT;
26ac0430 800 } else {
cb835136
AR
801 if (!closest_parent_miss.isAnyAddr()) {
802 p = whichPeer(closest_parent_miss);
cc192b50 803 code = CLOSEST_PARENT_MISS;
cb835136
AR
804 } else if (!first_parent_miss.isAnyAddr()) {
805 p = whichPeer(first_parent_miss);
cc192b50 806 code = FIRST_PARENT_MISS;
807 }
808 }
db1cd23c 809 if (p && code != HIER_NONE) {
cb835136 810 addSelection(p, code);
db1cd23c 811 }
812}
813
cb835136
AR
814/// Adds a "direct" entry if the request can be forwarded to the origin server.
815void
816PeerSelector::selectSomeDirect()
db1cd23c 817{
cb835136 818 if (direct == DIRECT_NO)
62e76326 819 return;
820
db80e881 821 /* WAIS is not implemented natively */
cb835136 822 if (request->url.getScheme() == AnyP::PROTO_WAIS)
26ac0430 823 return;
db80e881 824
cb835136 825 addSelection(nullptr, HIER_DIRECT);
db1cd23c 826}
827
cb835136
AR
828void
829PeerSelector::selectSomeParent()
db1cd23c 830{
a3c6762c 831 CachePeer *p;
db1cd23c 832 hier_code code = HIER_NONE;
5c51bffb 833 debugs(44, 3, request->method << ' ' << request->url.host());
62e76326 834
cb835136 835 if (direct == DIRECT_YES)
62e76326 836 return;
837
cb365059 838 if ((p = peerSourceHashSelectParent(this))) {
b4ab8666 839 code = SOURCEHASH_PARENT;
2f1431ea 840#if USE_AUTH
cb365059 841 } else if ((p = peerUserHashSelectParent(this))) {
f7e1d9ce 842 code = USERHASH_PARENT;
2f1431ea 843#endif
cb365059 844 } else if ((p = carpSelectParent(this))) {
62e76326 845 code = CARP;
cb365059 846 } else if ((p = getRoundRobinParent(this))) {
62e76326 847 code = ROUNDROBIN_PARENT;
cb365059 848 } else if ((p = getWeightedRoundRobinParent(this))) {
62e76326 849 code = ROUNDROBIN_PARENT;
cb365059 850 } else if ((p = getFirstUpParent(this))) {
62e76326 851 code = FIRSTUP_PARENT;
cb365059 852 } else if ((p = getDefaultParent(this))) {
b4ab8666 853 code = DEFAULT_PARENT;
db1cd23c 854 }
62e76326 855
db1cd23c 856 if (code != HIER_NONE) {
cb835136 857 addSelection(p, code);
062e2281 858 }
859}
860
cb835136
AR
861/// Adds alive parents. Used as a last resort for never_direct.
862void
863PeerSelector::selectAllParents()
168dfda9 864{
a3c6762c 865 CachePeer *p;
168dfda9 866 /* Add all alive parents */
62e76326 867
168dfda9 868 for (p = Config.peers; p; p = p->next) {
62e76326 869 /* XXX: neighbors.c lacks a public interface for enumerating
870 * parents to a request so we have to dig some here..
871 */
872
5c51bffb 873 if (neighborType(p, request->url) != PEER_PARENT)
62e76326 874 continue;
875
cb365059 876 if (!peerHTTPOkay(p, this))
62e76326 877 continue;
878
cb835136 879 addSelection(p, ANY_OLD_PARENT);
168dfda9 880 }
62e76326 881
168dfda9 882 /* XXX: should add dead parents here, but it is currently
883 * not possible to find out which parents are dead or which
884 * simply are not configured to handle the request.
885 */
886 /* Add default parent as a last resort */
cb365059 887 if ((p = getDefaultParent(this))) {
cb835136 888 addSelection(p, DEFAULT_PARENT);
168dfda9 889 }
890}
891
cb835136
AR
892void
893PeerSelector::handlePingTimeout()
062e2281 894{
cb835136 895 debugs(44, 3, url());
62e76326 896
9865de72
EB
897 // do nothing if ping reply came while handlePingTimeout() was queued
898 if (!entry || entry->ping_status != PING_WAITING)
899 return;
900
901 entry->ping_status = PING_DONE;
6043e368 902
cb835136 903 if (selectionAborted())
62e76326 904 return;
62e76326 905
5db6bf73 906 ++PeerStats.timeouts;
cb835136
AR
907 ping.timedout = 1;
908 selectMore();
909}
910
911void
9865de72 912PeerSelector::HandlePingTimeout(PeerSelector *selector)
cb835136 913{
9865de72 914 selector->handlePingTimeout();
85034133 915}
916
917void
918peerSelectInit(void)
919{
75e88d56 920 memset(&PeerStats, '\0', sizeof(PeerStats));
062e2281 921}
93775f90 922
cb835136
AR
923void
924PeerSelector::handleIcpParentMiss(CachePeer *p, icp_common_t *header)
b3264694 925{
926 int rtt;
62e76326 927
9b5c4a9a 928#if USE_ICMP
b3264694 929 if (Config.onoff.query_icmp) {
62e76326 930 if (header->flags & ICP_FLAG_SRC_RTT) {
931 rtt = header->pad & 0xFFFF;
9b5c4a9a 932 int hops = (header->pad >> 16) & 0xFFFF;
62e76326 933
934 if (rtt > 0 && rtt < 0xFFFF)
cb835136 935 netdbUpdatePeer(request->url, p, rtt, hops);
62e76326 936
cb835136
AR
937 if (rtt && (ping.p_rtt == 0 || rtt < ping.p_rtt)) {
938 closest_parent_miss = p->in_addr;
939 ping.p_rtt = rtt;
62e76326 940 }
941 }
b3264694 942 }
9b5c4a9a 943#endif /* USE_ICMP */
62e76326 944
18ec72b2 945 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
cd196bc8 946 if (p->options.closest_only)
62e76326 947 return;
948
85223cd7 949 /* set FIRST_MISS if there is no CLOSEST parent */
cb835136 950 if (!closest_parent_miss.isAnyAddr())
62e76326 951 return;
952
cb835136 953 rtt = (tvSubMsec(ping.start, current_time) - p->basetime) / p->weight;
62e76326 954
d1b63fc8 955 if (rtt < 1)
62e76326 956 rtt = 1;
957
cb835136
AR
958 if (first_parent_miss.isAnyAddr() || rtt < ping.w_rtt) {
959 first_parent_miss = p->in_addr;
960 ping.w_rtt = rtt;
b3264694 961 }
962}
93775f90 963
cb835136
AR
964void
965PeerSelector::handleIcpReply(CachePeer *p, const peer_t type, icp_common_t *header)
93775f90 966{
e6ccf245 967 icp_opcode op = header->getOpCode();
cb835136 968 debugs(44, 3, icp_opcode_str[op] << ' ' << url());
69c95dd3 969#if USE_CACHE_DIGESTS && 0
26b164ac 970 /* do cd lookup to count false misses */
62e76326 971
3ab66981 972 if (p && request)
62e76326 973 peerNoteDigestLookup(request, p,
cb365059 974 peerDigestLookup(p, this));
62e76326 975
26b164ac 976#endif
62e76326 977
cb835136 978 ++ping.n_recv;
62e76326 979
27cd7235 980 if (op == ICP_MISS || op == ICP_DECHO) {
62e76326 981 if (type == PEER_PARENT)
cb835136 982 handleIcpParentMiss(p, header);
a7c05555 983 } else if (op == ICP_HIT) {
cb835136
AR
984 hit = p;
985 hit_type = type;
986 selectMore();
62e76326 987 return;
db1cd23c 988 }
62e76326 989
cb835136 990 if (ping.n_recv < ping.n_replies_expected)
62e76326 991 return;
992
cb835136 993 selectMore();
93775f90 994}
86aebcda 995
996#if USE_HTCP
cb835136
AR
997void
998PeerSelector::handleHtcpReply(CachePeer *p, const peer_t type, HtcpReplyData *htcp)
86aebcda 999{
cb835136
AR
1000 debugs(44, 3, (htcp->hit ? "HIT" : "MISS") << ' ' << url());
1001 ++ping.n_recv;
62e76326 1002
44e237d0 1003 if (htcp->hit) {
cb835136
AR
1004 hit = p;
1005 hit_type = type;
1006 selectMore();
62e76326 1007 return;
44e237d0 1008 }
62e76326 1009
44e237d0 1010 if (type == PEER_PARENT)
cb835136 1011 handleHtcpParentMiss(p, htcp);
62e76326 1012
cb835136 1013 if (ping.n_recv < ping.n_replies_expected)
62e76326 1014 return;
1015
cb835136 1016 selectMore();
44e237d0 1017}
1018
cb835136
AR
1019void
1020PeerSelector::handleHtcpParentMiss(CachePeer *p, HtcpReplyData *htcp)
44e237d0 1021{
1022 int rtt;
62e76326 1023
9b5c4a9a 1024#if USE_ICMP
44e237d0 1025 if (Config.onoff.query_icmp) {
62e76326 1026 if (htcp->cto.rtt > 0) {
1027 rtt = (int) htcp->cto.rtt * 1000;
9b5c4a9a 1028 int hops = (int) htcp->cto.hops * 1000;
cb835136 1029 netdbUpdatePeer(request->url, p, rtt, hops);
62e76326 1030
cb835136
AR
1031 if (rtt && (ping.p_rtt == 0 || rtt < ping.p_rtt)) {
1032 closest_parent_miss = p->in_addr;
1033 ping.p_rtt = rtt;
62e76326 1034 }
1035 }
44e237d0 1036 }
9b5c4a9a 1037#endif /* USE_ICMP */
62e76326 1038
44e237d0 1039 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
cd196bc8 1040 if (p->options.closest_only)
62e76326 1041 return;
1042
44e237d0 1043 /* set FIRST_MISS if there is no CLOSEST parent */
cb835136 1044 if (!closest_parent_miss.isAnyAddr())
62e76326 1045 return;
1046
cb835136 1047 rtt = (tvSubMsec(ping.start, current_time) - p->basetime) / p->weight;
62e76326 1048
d1b63fc8 1049 if (rtt < 1)
62e76326 1050 rtt = 1;
1051
cb835136
AR
1052 if (first_parent_miss.isAnyAddr() || rtt < ping.w_rtt) {
1053 first_parent_miss = p->in_addr;
1054 ping.w_rtt = rtt;
44e237d0 1055 }
86aebcda 1056}
62e76326 1057
86aebcda 1058#endif
1059
cb835136
AR
1060void
1061PeerSelector::HandlePingReply(CachePeer * p, peer_t type, AnyP::ProtocolType proto, void *pingdata, void *data)
86aebcda 1062{
0c3d3f65 1063 if (proto == AnyP::PROTO_ICP)
cb835136 1064 static_cast<PeerSelector*>(data)->handleIcpReply(p, type, static_cast<icp_common_t*>(pingdata));
62e76326 1065
86aebcda 1066#if USE_HTCP
62e76326 1067
0c3d3f65 1068 else if (proto == AnyP::PROTO_HTCP)
cb835136 1069 static_cast<PeerSelector*>(data)->handleHtcpReply(p, type, static_cast<HtcpReplyData*>(pingdata));
62e76326 1070
86aebcda 1071#endif
62e76326 1072
86aebcda 1073 else
cb835136 1074 debugs(44, DBG_IMPORTANT, "ERROR: ignoring an ICP reply with unknown protocol " << proto);
86aebcda 1075}
db1cd23c 1076
cb835136
AR
1077void
1078PeerSelector::addSelection(CachePeer *peer, const hier_code code)
db1cd23c 1079{
e7b08eec 1080 // Find the end of the servers list. Bail on a duplicate destination.
cb835136
AR
1081 auto **serversTail = &servers;
1082 while (const auto server = *serversTail) {
e7b08eec
AR
1083 // There can be at most one PINNED destination.
1084 // Non-PINNED destinations are uniquely identified by their CachePeer
1085 // (even though a DIRECT destination might match a cache_peer address).
9b7992d9
EB
1086 // TODO: We may still add duplicates because the same peer could have
1087 // been removed from `servers` already (and given to the requestor).
e7b08eec 1088 const bool duplicate = (server->code == PINNED) ?
cb835136 1089 (code == PINNED) : (server->_peer == peer);
e7b08eec 1090 if (duplicate) {
cb835136
AR
1091 debugs(44, 3, "skipping " << PeerSelectionDumper(this, peer, code) <<
1092 "; have " << PeerSelectionDumper(this, server->_peer.get(), server->code));
e7b08eec
AR
1093 return;
1094 }
cb835136 1095 serversTail = &server->next;
e7b08eec 1096 }
62e76326 1097
cb835136
AR
1098 debugs(44, 3, "adding " << PeerSelectionDumper(this, peer, code));
1099 *serversTail = new FwdServer(peer, code);
db1cd23c 1100}
b24880fe 1101
cb835136 1102PeerSelector::PeerSelector(PeerSelectionInitiator *initiator):
6043e368 1103 request(nullptr),
f53969cc
SM
1104 entry (NULL),
1105 always_direct(Config.accessList.AlwaysDirect?ACCESS_DUNNO:ACCESS_DENIED),
1106 never_direct(Config.accessList.NeverDirect?ACCESS_DUNNO:ACCESS_DENIED),
1107 direct(DIRECT_UNKNOWN),
f53969cc
SM
1108 lastError(NULL),
1109 servers (NULL),
1110 first_parent_miss(),
1111 closest_parent_miss(),
1112 hit(NULL),
1113 hit_type(PEER_NONE),
6043e368
AR
1114 acl_checklist (NULL),
1115 initiator_(initiator)
b24880fe 1116{
cc192b50 1117 ; // no local defaults.
b24880fe 1118}
1119
851feda6 1120const SBuf
cb835136 1121PeerSelector::url() const
769c64cc
AJ
1122{
1123 if (entry)
851feda6 1124 return SBuf(entry->url());
769c64cc
AJ
1125
1126 if (request)
851feda6 1127 return request->effectiveRequestUri();
769c64cc 1128
851feda6
AJ
1129 static const SBuf noUrl("[no URL]");
1130 return noUrl;
769c64cc
AJ
1131}
1132
6043e368
AR
1133/// \returns valid/interested peer initiator or nil
1134PeerSelectionInitiator *
cb835136 1135PeerSelector::interestedInitiator()
6043e368
AR
1136{
1137 const auto initiator = initiator_.valid();
1138
1139 if (!initiator) {
1140 debugs(44, 3, id << " initiator gone");
1141 return nullptr;
1142 }
1143
1144 if (!initiator->subscribed) {
1145 debugs(44, 3, id << " initiator lost interest");
1146 return nullptr;
1147 }
1148
fd9c47d1 1149 debugs(44, 7, id);
6043e368
AR
1150 return initiator;
1151}
1152
1153bool
cb835136 1154PeerSelector::wantsMoreDestinations() const {
6043e368
AR
1155 const auto maxCount = Config.forward_max_tries;
1156 return maxCount >= 0 && foundPaths <
d22511f9 1157 static_cast<std::make_unsigned<decltype(maxCount)>::type>(maxCount);
6043e368
AR
1158}
1159
1160void
3dde9e52 1161PeerSelector::handlePath(const Comm::ConnectionPointer &path, FwdServer &fs)
6043e368
AR
1162{
1163 ++foundPaths;
1164
3dde9e52
CT
1165 if (path) {
1166 path->peerType = fs.code;
1167 path->setPeer(fs._peer.get());
6043e368 1168
3dde9e52
CT
1169 // check for a configured outgoing address for this destination...
1170 getOutgoingAddress(request, path);
1171 debugs(44, 2, id << " found " << path << ", destination #" << foundPaths << " for " << url());
1172 } else
1173 debugs(44, 2, id << " found pinned, destination #" << foundPaths << " for " << url());
6043e368
AR
1174
1175 request->hier.ping = ping; // may be updated later
1176
6043e368
AR
1177 debugs(44, 2, " always_direct = " << always_direct);
1178 debugs(44, 2, " never_direct = " << never_direct);
1179 debugs(44, 2, " timedout = " << ping.timedout);
1180
1181 if (const auto initiator = interestedInitiator())
1182 initiator->noteDestination(path);
1183}
1184
cb835136 1185InstanceIdDefinitions(PeerSelector, "PeerSelector");
6043e368 1186
b24880fe 1187ping_data::ping_data() :
f53969cc
SM
1188 n_sent(0),
1189 n_recv(0),
1190 n_replies_expected(0),
1191 timeout(0),
1192 timedout(0),
1193 w_rtt(0),
9865de72
EB
1194 p_rtt(0),
1195 monitorRegistration(PingMonitor().npos())
b24880fe 1196{
1197 start.tv_sec = 0;
1198 start.tv_usec = 0;
1199 stop.tv_sec = 0;
1200 stop.tv_usec = 0;
1201}
f53969cc 1202
9865de72
EB
1203timeval
1204ping_data::deadline() const
1205{
1206 timeval timeInterval;
1207 timeInterval.tv_sec = timeout / 1000;
1208 timeInterval.tv_usec = (timeout % 1000) * 1000;
1209
1210 timeval result;
1211 tvAdd(result, start, timeInterval);
1212 return result;
1213}
1214