]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ueberbackend.cc
dnsdist: Add HTTPStatusAction to return a specific HTTP response
[thirdparty/pdns.git] / pdns / ueberbackend.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include <boost/archive/binary_iarchive.hpp>
26 #include <boost/archive/binary_oarchive.hpp>
27
28 #include "auth-querycache.hh"
29 #include "utility.hh"
30
31
32 #include <dlfcn.h>
33 #include <string>
34 #include <map>
35 #include <sys/types.h>
36 #include <sstream>
37 #include <errno.h>
38 #include <iostream>
39 #include <sstream>
40 #include <functional>
41
42 #include "dns.hh"
43 #include "arguments.hh"
44 #include "dnsbackend.hh"
45 #include "ueberbackend.hh"
46 #include "dnspacket.hh"
47 #include "logger.hh"
48 #include "statbag.hh"
49
50 extern StatBag S;
51
52 vector<UeberBackend *>UeberBackend::instances;
53 pthread_mutex_t UeberBackend::instances_lock=PTHREAD_MUTEX_INITIALIZER;
54
55 // initially we are blocked
56 bool UeberBackend::d_go=false;
57 pthread_mutex_t UeberBackend::d_mut = PTHREAD_MUTEX_INITIALIZER;
58 pthread_cond_t UeberBackend::d_cond = PTHREAD_COND_INITIALIZER;
59
60 //! Loads a module and reports it to all UeberBackend threads
61 bool UeberBackend::loadmodule(const string &name)
62 {
63 g_log<<Logger::Warning <<"Loading '"<<name<<"'" << endl;
64
65 void *dlib=dlopen(name.c_str(), RTLD_NOW);
66
67 if(dlib == NULL) {
68 g_log<<Logger::Error <<"Unable to load module '"<<name<<"': "<<dlerror() << endl;
69 return false;
70 }
71
72 return true;
73 }
74
75 bool UeberBackend::loadModules(const vector<string>& modules, const string& path)
76 {
77 for (const auto& module: modules) {
78 bool res;
79 if (module.find(".")==string::npos) {
80 res = UeberBackend::loadmodule(path+"/lib"+module+"backend.so");
81 } else if (module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) {
82 // absolute or current path
83 res = UeberBackend::loadmodule(module);
84 } else {
85 res = UeberBackend::loadmodule(path+"/"+module);
86 }
87
88 if (res == false) {
89 return false;
90 }
91 }
92 return true;
93 }
94
95 void UeberBackend::go(void)
96 {
97 pthread_mutex_lock(&d_mut);
98 d_go=true;
99 pthread_cond_broadcast(&d_cond);
100 pthread_mutex_unlock(&d_mut);
101 }
102
103 bool UeberBackend::getDomainInfo(const DNSName &domain, DomainInfo &di, bool getSerial)
104 {
105 for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
106 if((*i)->getDomainInfo(domain, di, getSerial))
107 return true;
108 return false;
109 }
110
111 bool UeberBackend::createDomain(const DNSName &domain)
112 {
113 for(DNSBackend* mydb : backends) {
114 if(mydb->createDomain(domain)) {
115 return true;
116 }
117 }
118 return false;
119 }
120
121 bool UeberBackend::doesDNSSEC()
122 {
123 for(auto* db : backends) {
124 if(db->doesDNSSEC())
125 return true;
126 }
127 return false;
128 }
129
130 bool UeberBackend::addDomainKey(const DNSName& name, const DNSBackend::KeyData& key, int64_t& id)
131 {
132 id = -1;
133 for(DNSBackend* db : backends) {
134 if(db->addDomainKey(name, key, id))
135 return true;
136 }
137 return false;
138 }
139 bool UeberBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
140 {
141 for(DNSBackend* db : backends) {
142 if(db->getDomainKeys(name, keys))
143 return true;
144 }
145 return false;
146 }
147
148 bool UeberBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta)
149 {
150 for(DNSBackend* db : backends) {
151 if(db->getAllDomainMetadata(name, meta))
152 return true;
153 }
154 return false;
155 }
156
157 bool UeberBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
158 {
159 for(DNSBackend* db : backends) {
160 if(db->getDomainMetadata(name, kind, meta))
161 return true;
162 }
163 return false;
164 }
165
166 bool UeberBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
167 {
168 for(DNSBackend* db : backends) {
169 if(db->setDomainMetadata(name, kind, meta))
170 return true;
171 }
172 return false;
173 }
174
175 bool UeberBackend::activateDomainKey(const DNSName& name, unsigned int id)
176 {
177 for(DNSBackend* db : backends) {
178 if(db->activateDomainKey(name, id))
179 return true;
180 }
181 return false;
182 }
183
184 bool UeberBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
185 {
186 for(DNSBackend* db : backends) {
187 if(db->deactivateDomainKey(name, id))
188 return true;
189 }
190 return false;
191 }
192
193 bool UeberBackend::removeDomainKey(const DNSName& name, unsigned int id)
194 {
195 for(DNSBackend* db : backends) {
196 if(db->removeDomainKey(name, id))
197 return true;
198 }
199 return false;
200 }
201
202
203 bool UeberBackend::getTSIGKey(const DNSName& name, DNSName* algorithm, string* content)
204 {
205 for(DNSBackend* db : backends) {
206 if(db->getTSIGKey(name, algorithm, content))
207 return true;
208 }
209 return false;
210 }
211
212
213 bool UeberBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
214 {
215 for(DNSBackend* db : backends) {
216 if(db->setTSIGKey(name, algorithm, content))
217 return true;
218 }
219 return false;
220 }
221
222 bool UeberBackend::deleteTSIGKey(const DNSName& name)
223 {
224 for(DNSBackend* db : backends) {
225 if(db->deleteTSIGKey(name))
226 return true;
227 }
228 return false;
229 }
230
231 bool UeberBackend::getTSIGKeys(std::vector< struct TSIGKey > &keys)
232 {
233 for(DNSBackend* db : backends) {
234 db->getTSIGKeys(keys);
235 }
236 return true;
237 }
238
239 void UeberBackend::reload()
240 {
241 for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
242 {
243 ( *i )->reload();
244 }
245 }
246
247 void UeberBackend::rediscover(string *status)
248 {
249
250 for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
251 {
252 string tmpstr;
253 ( *i )->rediscover(&tmpstr);
254 if(status)
255 *status+=tmpstr + (i!=backends.begin() ? "\n" : "");
256 }
257 }
258
259
260 void UeberBackend::getUnfreshSlaveInfos(vector<DomainInfo>* domains)
261 {
262 for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
263 {
264 ( *i )->getUnfreshSlaveInfos( domains );
265 }
266 }
267
268
269
270 void UeberBackend::getUpdatedMasters(vector<DomainInfo>* domains)
271 {
272 for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
273 {
274 ( *i )->getUpdatedMasters( domains );
275 }
276 }
277
278 bool UeberBackend::getAuth(const DNSName &target, const QType& qtype, SOAData* sd, bool cachedOk)
279 {
280 // A backend can respond to our authority request with the 'best' match it
281 // has. For example, when asked for a.b.c.example.com. it might respond with
282 // com. We then store that and keep querying the other backends in case one
283 // of them has a more specific zone but don't bother asking this specific
284 // backend again for b.c.example.com., c.example.com. and example.com.
285 // If a backend has no match it may respond with an empty qname.
286
287 bool found = false;
288 int cstat;
289 DNSName shorter(target);
290 vector<pair<size_t, SOAData> > bestmatch (backends.size(), make_pair(target.wirelength()+1, SOAData()));
291 do {
292
293 // Check cache
294 if(cachedOk && (d_cache_ttl || d_negcache_ttl)) {
295 d_question.qtype = QType::SOA;
296 d_question.qname = shorter;
297 d_question.zoneId = -1;
298
299 cstat = cacheHas(d_question,d_answers);
300
301 if(cstat == 1 && !d_answers.empty() && d_cache_ttl) {
302 DLOG(g_log<<Logger::Error<<"has pos cache entry: "<<shorter<<endl);
303 fillSOAData(d_answers[0], *sd);
304
305 sd->db = 0;
306 sd->qname = shorter;
307 goto found;
308 } else if(cstat == 0 && d_negcache_ttl) {
309 DLOG(g_log<<Logger::Error<<"has neg cache entry: "<<shorter<<endl);
310 continue;
311 }
312 }
313
314 // Check backends
315 {
316 vector<DNSBackend *>::const_iterator i = backends.begin();
317 vector<pair<size_t, SOAData> >::iterator j = bestmatch.begin();
318 for(; i != backends.end() && j != bestmatch.end(); ++i, ++j) {
319
320 DLOG(g_log<<Logger::Error<<"backend: "<<i-backends.begin()<<", qname: "<<shorter<<endl);
321
322 if(j->first < shorter.wirelength()) {
323 DLOG(g_log<<Logger::Error<<"skipped, we already found a shorter best match in this backend: "<<j->second.qname<<endl);
324 continue;
325 } else if(j->first == shorter.wirelength()) {
326 DLOG(g_log<<Logger::Error<<"use shorter best match: "<<j->second.qname<<endl);
327 *sd = j->second;
328 break;
329 } else {
330 DLOG(g_log<<Logger::Error<<"lookup: "<<shorter<<endl);
331 if((*i)->getAuth(shorter, sd)) {
332 DLOG(g_log<<Logger::Error<<"got: "<<sd->qname<<endl);
333 if(!sd->qname.empty() && !shorter.isPartOf(sd->qname)) {
334 throw PDNSException("getAuth() returned an SOA for the wrong zone. Zone '"+sd->qname.toLogString()+"' is not part of '"+shorter.toLogString()+"'");
335 }
336 j->first = sd->qname.wirelength();
337 j->second = *sd;
338 if(sd->qname == shorter) {
339 break;
340 }
341 } else {
342 DLOG(g_log<<Logger::Error<<"no match for: "<<shorter<<endl);
343 }
344 }
345 }
346
347 // Add to cache
348 if(i == backends.end()) {
349 if(d_negcache_ttl) {
350 DLOG(g_log<<Logger::Error<<"add neg cache entry:"<<shorter<<endl);
351 d_question.qname=shorter;
352 addNegCache(d_question);
353 }
354 continue;
355 } else if(d_cache_ttl) {
356 DLOG(g_log<<Logger::Error<<"add pos cache entry: "<<sd->qname<<endl);
357 d_question.qtype = QType::SOA;
358 d_question.qname = sd->qname;
359 d_question.zoneId = -1;
360
361 DNSZoneRecord rr;
362 rr.dr.d_name = sd->qname;
363 rr.dr.d_type = QType::SOA;
364 rr.dr.d_content = makeSOAContent(*sd);
365 rr.dr.d_ttl = sd->ttl;
366 rr.domain_id = sd->domain_id;
367
368 addCache(d_question, {rr});
369 }
370 }
371
372 found:
373 if(found == (qtype == QType::DS) || target != shorter) {
374 DLOG(g_log<<Logger::Error<<"found: "<<sd->qname<<endl);
375 return true;
376 } else {
377 DLOG(g_log<<Logger::Error<<"chasing next: "<<sd->qname<<endl);
378 found = true;
379 }
380
381 } while(shorter.chopOff());
382 return found;
383 }
384
385 bool UeberBackend::getSOA(const DNSName &domain, SOAData &sd)
386 {
387 d_question.qtype=QType::SOA;
388 d_question.qname=domain;
389 d_question.zoneId=-1;
390
391 int cstat=cacheHas(d_question,d_answers);
392 if(cstat==0) { // negative
393 return false;
394 }
395 else if(cstat==1 && !d_answers.empty()) {
396 fillSOAData(d_answers[0],sd);
397 sd.domain_id=d_answers[0].domain_id;
398 sd.ttl=d_answers[0].dr.d_ttl;
399 sd.db=0;
400 return true;
401 }
402
403 // not found in neg. or pos. cache, look it up
404 return getSOAUncached(domain, sd);
405 }
406
407 bool UeberBackend::getSOAUncached(const DNSName &domain, SOAData &sd)
408 {
409 d_question.qtype=QType::SOA;
410 d_question.qname=domain;
411 d_question.zoneId=-1;
412
413 for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
414 if((*i)->getSOA(domain, sd)) {
415 if(domain != sd.qname) {
416 throw PDNSException("getSOA() returned an SOA for the wrong zone. Question: '"+domain.toLogString()+"', answer: '"+sd.qname.toLogString()+"'");
417 }
418 if(d_cache_ttl) {
419 DNSZoneRecord rr;
420 rr.dr.d_name = sd.qname;
421 rr.dr.d_type = QType::SOA;
422 rr.dr.d_content = makeSOAContent(sd);
423 rr.dr.d_ttl = sd.ttl;
424 rr.domain_id = sd.domain_id;
425
426 addCache(d_question, {rr});
427
428 }
429 return true;
430 }
431
432 if(d_negcache_ttl)
433 addNegCache(d_question);
434 return false;
435 }
436
437 bool UeberBackend::superMasterBackend(const string &ip, const DNSName &domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
438 {
439 for(vector<DNSBackend *>::const_iterator i=backends.begin();i!=backends.end();++i)
440 if((*i)->superMasterBackend(ip, domain, nsset, nameserver, account, db))
441 return true;
442 return false;
443 }
444
445 UeberBackend::UeberBackend(const string &pname)
446 {
447 pthread_mutex_lock(&instances_lock);
448 instances.push_back(this); // report to the static list of ourself
449 pthread_mutex_unlock(&instances_lock);
450
451 d_negcached=0;
452 d_ancount=0;
453 d_domain_id=-1;
454 d_cached=0;
455 d_cache_ttl = ::arg().asNum("query-cache-ttl");
456 d_negcache_ttl = ::arg().asNum("negquery-cache-ttl");
457
458 d_tid=pthread_self();
459 d_stale=false;
460
461 backends=BackendMakers().all(pname=="key-only");
462 }
463
464 void del(DNSBackend* d)
465 {
466 delete d;
467 }
468
469 void UeberBackend::cleanup()
470 {
471 pthread_mutex_lock(&instances_lock);
472
473 remove(instances.begin(),instances.end(),this);
474 instances.resize(instances.size()-1);
475
476 pthread_mutex_unlock(&instances_lock);
477
478 for_each(backends.begin(),backends.end(),del);
479 }
480
481 // returns -1 for miss, 0 for negative match, 1 for hit
482 int UeberBackend::cacheHas(const Question &q, vector<DNSZoneRecord> &rrs)
483 {
484 extern AuthQueryCache QC;
485
486 if(!d_cache_ttl && ! d_negcache_ttl) {
487 return -1;
488 }
489
490 rrs.clear();
491 // g_log<<Logger::Warning<<"looking up: '"<<q.qname+"'|N|"+q.qtype.getName()+"|"+itoa(q.zoneId)<<endl;
492
493 bool ret=QC.getEntry(q.qname, q.qtype, rrs, q.zoneId); // think about lowercasing here
494 if(!ret) {
495 return -1;
496 }
497 if(rrs.empty()) // negatively cached
498 return 0;
499
500 return 1;
501 }
502
503 void UeberBackend::addNegCache(const Question &q)
504 {
505 extern AuthQueryCache QC;
506 if(!d_negcache_ttl)
507 return;
508 // we should also not be storing negative answers if a pipebackend does scopeMask, but we can't pass a negative scopeMask in an empty set!
509 QC.insert(q.qname, q.qtype, vector<DNSZoneRecord>(), d_negcache_ttl, q.zoneId);
510 }
511
512 void UeberBackend::addCache(const Question &q, const vector<DNSZoneRecord> &rrs)
513 {
514 extern AuthQueryCache QC;
515
516 if(!d_cache_ttl)
517 return;
518
519 unsigned int store_ttl = d_cache_ttl;
520 for(const auto& rr : rrs) {
521 if (rr.dr.d_ttl < d_cache_ttl)
522 store_ttl = rr.dr.d_ttl;
523 if (rr.scopeMask)
524 return;
525 }
526
527 QC.insert(q.qname, q.qtype, rrs, store_ttl, q.zoneId);
528 }
529
530 void UeberBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
531 {
532 for ( vector< DNSBackend * >::iterator i = backends.begin(); i != backends.end(); ++i )
533 (*i)->alsoNotifies(domain,ips);
534 }
535
536 UeberBackend::~UeberBackend()
537 {
538 DLOG(g_log<<Logger::Error<<"UeberBackend destructor called, removing ourselves from instances, and deleting our backends"<<endl);
539 cleanup();
540 }
541
542 // this handle is more magic than most
543 void UeberBackend::lookup(const QType &qtype,const DNSName &qname, DNSPacket *pkt_p, int zoneId)
544 {
545 if(d_stale) {
546 g_log<<Logger::Error<<"Stale ueberbackend received question, signalling that we want to be recycled"<<endl;
547 throw PDNSException("We are stale, please recycle");
548 }
549
550 DLOG(g_log<<"UeberBackend received question for "<<qtype.getName()<<" of "<<qname<<endl);
551 if(!d_go) {
552 pthread_mutex_lock(&d_mut);
553 while (d_go==false) {
554 g_log<<Logger::Error<<"UeberBackend is blocked, waiting for 'go'"<<endl;
555 pthread_cond_wait(&d_cond, &d_mut);
556 g_log<<Logger::Error<<"Broadcast received, unblocked"<<endl;
557 }
558 pthread_mutex_unlock(&d_mut);
559 }
560
561 d_domain_id=zoneId;
562
563 d_handle.i=0;
564 d_handle.qtype=qtype;
565 d_handle.qname=qname;
566 d_handle.pkt_p=pkt_p;
567 d_ancount=0;
568
569 if(!backends.size()) {
570 g_log<<Logger::Error<<"No database backends available - unable to answer questions."<<endl;
571 d_stale=true; // please recycle us!
572 throw PDNSException("We are stale, please recycle");
573 }
574 else {
575 d_question.qtype=qtype;
576 d_question.qname=qname;
577 d_question.zoneId=zoneId;
578 int cstat=cacheHas(d_question, d_answers);
579 if(cstat<0) { // nothing
580 // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): uncached"<<endl;
581 d_negcached=d_cached=false;
582 d_answers.clear();
583 (d_handle.d_hinterBackend=backends[d_handle.i++])->lookup(qtype, qname,pkt_p,zoneId);
584 }
585 else if(cstat==0) {
586 // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): NEGcached"<<endl;
587 d_negcached=true;
588 d_cached=false;
589 d_answers.clear();
590 }
591 else {
592 // cout<<"UeberBackend::lookup("<<qname<<"|"<<DNSRecordContent::NumberToType(qtype.getCode())<<"): CACHED"<<endl;
593 d_negcached=false;
594 d_cached=true;
595 d_cachehandleiter = d_answers.begin();
596 }
597 }
598
599 d_handle.parent=this;
600 }
601
602 void UeberBackend::getAllDomains(vector<DomainInfo> *domains, bool include_disabled) {
603 for (vector<DNSBackend*>::iterator i = backends.begin(); i != backends.end(); ++i )
604 {
605 (*i)->getAllDomains(domains, include_disabled);
606 }
607 }
608
609 bool UeberBackend::get(DNSZoneRecord &rr)
610 {
611 // cout<<"UeberBackend::get(DNSZoneRecord) called"<<endl;
612 if(d_negcached) {
613 return false;
614 }
615
616 if(d_cached) {
617 if(d_cachehandleiter != d_answers.end()) {
618 rr=*d_cachehandleiter++;;
619 return true;
620 }
621 return false;
622 }
623 if(!d_handle.get(rr)) {
624 // cout<<"end of ueberbackend get, seeing if we should cache"<<endl;
625 if(!d_ancount && d_handle.qname.countLabels()) {// don't cache axfr
626 // cout<<"adding negcache"<<endl;
627 addNegCache(d_question);
628 }
629 else {
630 // cout<<"adding query cache"<<endl;
631 addCache(d_question, d_answers);
632 }
633 d_answers.clear();
634 return false;
635 }
636 d_ancount++;
637 d_answers.push_back(rr);
638 return true;
639 }
640
641 bool UeberBackend::searchRecords(const string& pattern, int maxResults, vector<DNSResourceRecord>& result)
642 {
643 bool rc = false;
644 for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && i != backends.end(); ++i )
645 if ((*i)->searchRecords(pattern, maxResults - result.size(), result)) rc = true;
646 return rc;
647 }
648
649 bool UeberBackend::searchComments(const string& pattern, int maxResults, vector<Comment>& result)
650 {
651 bool rc = false;
652 for ( vector< DNSBackend * >::iterator i = backends.begin(); result.size() < static_cast<vector<Comment>::size_type>(maxResults) && i != backends.end(); ++i )
653 if ((*i)->searchComments(pattern, maxResults - result.size(), result)) rc = true;
654 return rc;
655 }
656
657 AtomicCounter UeberBackend::handle::instances(0);
658
659 UeberBackend::handle::handle()
660 {
661 // g_log<<Logger::Warning<<"Handle instances: "<<instances<<endl;
662 ++instances;
663 parent=NULL;
664 d_hinterBackend=NULL;
665 pkt_p=NULL;
666 i=0;
667 }
668
669 UeberBackend::handle::~handle()
670 {
671 --instances;
672 }
673
674 bool UeberBackend::handle::get(DNSZoneRecord &r)
675 {
676 DLOG(g_log << "Ueber get() was called for a "<<qtype.getName()<<" record" << endl);
677 bool isMore=false;
678 while(d_hinterBackend && !(isMore=d_hinterBackend->get(r))) { // this backend out of answers
679 if(i<parent->backends.size()) {
680 DLOG(g_log<<"Backend #"<<i<<" of "<<parent->backends.size()
681 <<" out of answers, taking next"<<endl);
682
683 d_hinterBackend=parent->backends[i++];
684 d_hinterBackend->lookup(qtype,qname,pkt_p,parent->d_domain_id);
685 }
686 else
687 break;
688
689 DLOG(g_log<<"Now asking backend #"<<i<<endl);
690 }
691
692 if(!isMore && i==parent->backends.size()) {
693 DLOG(g_log<<"UeberBackend reached end of backends"<<endl);
694 return false;
695 }
696
697 DLOG(g_log<<"Found an answering backend - will not try another one"<<endl);
698 i=parent->backends.size(); // don't go on to the next backend
699 return true;
700 }