/[resiprocate]/branches/b-identity-0505/DnsResult.cxx
ViewVC logotype

Contents of /branches/b-identity-0505/DnsResult.cxx

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4598 - (show annotations) (download)
Wed May 11 22:53:07 2005 UTC (14 years, 6 months ago) by derek
File size: 43549 byte(s)
set svn:eol-style to LF
1 #if defined(HAVE_CONFIG_H)
2 #include "resiprocate/config.hxx"
3 #endif
4
5 #include <algorithm>
6
7 #ifndef WIN32
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 #include <arpa/inet.h>
11 #ifndef __CYGWIN__
12 # include <netinet/in.h>
13 # include <arpa/nameser.h>
14 # include <resolv.h>
15 #endif
16 #include <netdb.h>
17 #include <netinet/in.h>
18 #else
19 #include <Winsock2.h>
20 #include <svcguid.h>
21 #ifdef USE_IPV6
22 #include <ws2tcpip.h>
23 #endif
24 #endif
25
26 #if defined(USE_ARES)
27 extern "C"
28 {
29 #include "ares.h"
30 #include "ares_dns.h"
31 }
32 #endif
33
34 #include "resiprocate/os/DnsUtil.hxx"
35 #include "resiprocate/os/Inserter.hxx"
36 #include "resiprocate/os/Logger.hxx"
37 #include "resiprocate/os/Random.hxx"
38 #include "resiprocate/os/Tuple.hxx"
39 #include "resiprocate/os/compat.hxx"
40
41 #include "resiprocate/DnsHandler.hxx"
42 #include "resiprocate/DnsInterface.hxx"
43 #include "resiprocate/DnsResult.hxx"
44 #include "resiprocate/Uri.hxx"
45 #include "resiprocate/os/WinLeakCheck.hxx"
46
47 #if !defined(USE_ARES)
48 #warning "ARES is required"
49 #endif
50
51 using namespace resip;
52
53 #define RESIPROCATE_SUBSYSTEM resip::Subsystem::DNS
54
55 DnsResult::DnsResult(DnsInterface& interfaceObj, DnsHandler* handler)
56 : mInterface(interfaceObj),
57 mHandler(handler),
58 mSRVCount(0),
59 mSips(false),
60 mTransport(UNKNOWN_TRANSPORT),
61 mPort(-1),
62 mType(Pending)
63 {
64 }
65
66 DnsResult::~DnsResult()
67 {
68 //DebugLog (<< "DnsResult::~DnsResult() " << *this);
69 assert(mType != Pending);
70 }
71
72 void
73 DnsResult::transition(Type t)
74 {
75 if ((t == Finished || t == Destroyed || t == Available) &&
76 (mType == Pending))
77 {
78 mInterface.mActiveQueryCount--;
79 assert(mInterface.mActiveQueryCount >= 0);
80 }
81 mType = t;
82 }
83
84 void
85 DnsResult::destroy()
86 {
87 assert(this);
88 //DebugLog (<< "DnsResult::destroy() " << *this);
89
90 if (mType == Pending)
91 {
92 transition(Destroyed);
93 }
94 else
95 {
96 delete this;
97 }
98 }
99
100 DnsResult::Type
101 DnsResult::available()
102 {
103 assert(mType != Destroyed);
104 if (mType == Available)
105 {
106 if (!mResults.empty())
107 {
108 return Available;
109 }
110 else
111 {
112 primeResults();
113 return available(); // recurse
114 }
115 }
116 else
117 {
118 return mType;
119 }
120 }
121
122 Tuple
123 DnsResult::next()
124 {
125 assert(available() == Available);
126 Tuple next = mResults.front();
127 mResults.pop_front();
128 StackLog (<< "Returning next dns entry: " << next);
129
130 return next;
131 }
132
133 void
134 DnsResult::lookup(const Uri& uri)
135 {
136 DebugLog (<< "DnsResult::lookup " << uri);
137
138 //assert(uri.scheme() == Symbols::Sips || uri.scheme() == Symbols::Sip);
139 mSips = (uri.scheme() == Symbols::Sips);
140 mTarget = (!mSips && uri.exists(p_maddr)) ? uri.param(p_maddr) : uri.host();
141 mSrvKey = Symbols::UNDERSCORE + uri.scheme().substr(0, uri.scheme().size()) + Symbols::DOT;
142 bool isNumeric = DnsUtil::isIpAddress(mTarget);
143
144 if (uri.exists(p_transport))
145 {
146 mTransport = Tuple::toTransport(uri.param(p_transport));
147
148 if (isNumeric) // IP address specified
149 {
150 mPort = getDefaultPort(mTransport, uri.port());
151 Tuple tuple(mTarget, mPort, mTransport, mTarget);
152 DebugLog (<< "Found immediate result: " << tuple);
153 mResults.push_back(tuple);
154 transition(Available);
155 mHandler->handle(this);
156 }
157 else if (uri.port() != 0)
158 {
159 mPort = uri.port();
160 if (mInterface.isSupported(mTransport, V6))
161 {
162 lookupAAAARecords(mTarget); // for current target and port
163 }
164 else if (mInterface.isSupported(mTransport, V4))
165 {
166 lookupARecords(mTarget); // for current target and port
167 }
168 else
169 {
170 assert(0);
171 mHandler->handle(this);
172 }
173 }
174 else
175 {
176 if (mSips)
177 {
178 if (mTransport == UDP)
179 {
180 mTransport = DTLS;
181 if (!mInterface.isSupportedProtocol(mTransport))
182 {
183 transition(Finished);
184 mHandler->handle(this);
185 return;
186 }
187 mSRVCount++;
188 lookupSRV("_sips._udp." + mTarget);
189 }
190 else
191 {
192 mTransport = TLS;
193 if (!mInterface.isSupportedProtocol(mTransport))
194 {
195 transition(Finished);
196 mHandler->handle(this);
197 return;
198 }
199 mSRVCount++;
200 lookupSRV("_sips._tcp." + mTarget);
201 }
202 }
203 else
204 {
205 if (!mInterface.isSupportedProtocol(mTransport))
206 {
207 transition(Finished);
208 mHandler->handle(this);
209 return;
210 }
211
212 switch(mTransport)
213 {
214 case TLS: //deprecated, mean TLS over TCP
215 mSRVCount++;
216 lookupSRV("_sip._tls." + mTarget);
217 break;
218 case DTLS: //deprecated, mean TLS over TCP
219 mSRVCount++;
220 lookupSRV("_sip._dtls." + mTarget);
221 break;
222 case TCP:
223 mSRVCount++;
224 lookupSRV("_sip._tcp." + mTarget);
225 break;
226 case SCTP:
227 case DCCP:
228 case UDP:
229 default: //fall through to UDP for unimplemented & unknown
230 mSRVCount++;
231 lookupSRV("_sip._udp." + mTarget);
232 }
233 }
234 }
235 }
236 else
237 {
238 if (isNumeric || uri.port() != 0)
239 {
240 if (mSips || mTransport == TLS)
241 {
242 mTransport = TLS;
243 }
244 else
245 {
246 mTransport = UDP;
247 }
248
249 if (isNumeric) // IP address specified
250 {
251 mPort = getDefaultPort(mTransport, uri.port());
252 Tuple tuple(mTarget, mPort, mTransport, mTarget);
253 mResults.push_back(tuple);
254 transition(Available);
255 DebugLog (<< "Numeric result so return immediately: " << tuple);
256 mHandler->handle(this);
257 }
258 else // port specified so we know the transport
259 {
260 mPort = uri.port();
261 if (mInterface.isSupported(mTransport, V6))
262 {
263 lookupAAAARecords(mTarget); // for current target and port
264 }
265 else if (mInterface.isSupported(mTransport, V4))
266 {
267 lookupARecords(mTarget); // for current target and port
268 }
269 else
270 {
271 assert(0);
272 mHandler->handle(this);
273 }
274 }
275 }
276 else // do NAPTR
277 {
278 lookupNAPTR(); // for current target
279 }
280 }
281 }
282
283 int
284 DnsResult::getDefaultPort(TransportType transport, int port)
285 {
286 if (port == 0)
287 {
288 switch (transport)
289 {
290 case UDP:
291 return Symbols::DefaultSipPort;
292 case TCP:
293 return mSips ? Symbols::DefaultSipsPort : Symbols::DefaultSipPort;
294 case TLS:
295 return Symbols::DefaultSipsPort;
296 default:
297 InfoLog( << "Should not get this - unkown transport" );
298 return Symbols::DefaultSipPort; // !cj! todo - remove
299 assert(0);
300 }
301 }
302 else
303 {
304 return port;
305 }
306
307 assert(0);
308 return 0;
309 }
310
311
312 void
313 DnsResult::lookupAAAARecords(const Data& target)
314 {
315 assert(mInterface.isSupported(mTransport, V6));
316 #if defined(USE_IPV6)
317 DebugLog(<< "Doing host (AAAA) lookup: " << target);
318 mPassHostFromAAAAtoA = target; // hackage
319 mInterface.lookupAAAARecords(target, this);
320 #else // IPV4
321 assert(0);
322 lookupARecords(target);
323 #endif
324 }
325
326 void
327 DnsResult::lookupARecords(const Data& target)
328 {
329 assert(mInterface.isSupported(mTransport, V4));
330 DebugLog (<< "Doing Host (A) lookup: " << target);
331 mInterface.lookupARecords(target, this);
332 }
333
334 void
335 DnsResult::lookupNAPTR()
336 {
337 DebugLog (<< "Doing NAPTR lookup: " << mTarget);
338 mInterface.lookupNAPTR(mTarget, this);
339 }
340
341 void
342 DnsResult::lookupNAPTR(const Data& target)
343 {
344 DebugLog (<< "Doing NAPTR lookup: " << target);
345 mInterface.lookupNAPTR(target, this);
346 }
347
348 void
349 DnsResult::lookupSRV(const Data& target)
350 {
351 DebugLog (<< "Doing SRV lookup: " << target);
352 mInterface.lookupSRV(target, this);
353 }
354
355 void
356 DnsResult::processNAPTR(int status, const unsigned char* abuf, int alen)
357 {
358 StackLog (<< "Received NAPTR result for: " << mTarget);
359 StackLog (<< "DnsResult::processNAPTR() " << status);
360
361 // This function assumes that the NAPTR query that caused this
362 // callback is the ONLY outstanding query that might cause
363 // a callback into this object
364 if (mType == Destroyed)
365 {
366 destroy();
367 return;
368 }
369
370 if (status == 0)
371 {
372 const unsigned char *aptr = abuf + HFIXEDSZ;
373 int qdcount = DNS_HEADER_QDCOUNT(abuf); /* question count */
374 for (int i = 0; i < qdcount && aptr; i++)
375 {
376 aptr = skipDNSQuestion(aptr, abuf, alen);
377 }
378
379 int ancount = DNS_HEADER_ANCOUNT(abuf); /* answer record count */
380 for (int i = 0; i < ancount && aptr; i++)
381 {
382 NAPTR naptr;
383 const unsigned char *aptr2;
384 aptr2 = parseNAPTR(aptr, abuf, alen, naptr);
385
386 if (aptr2)
387 {
388 StackLog (<< "Adding NAPTR record: " << naptr);
389 if ( mSips && naptr.service.find("SIPS") == 0)
390 {
391 if (mInterface.isSupported(naptr.service) && naptr < mPreferredNAPTR)
392 {
393 mPreferredNAPTR = naptr;
394 StackLog (<< "Picked preferred: " << mPreferredNAPTR);
395 }
396 }
397 else if (mInterface.isSupported(naptr.service) && naptr < mPreferredNAPTR)
398 {
399 mPreferredNAPTR = naptr;
400 StackLog (<< "Picked preferred: " << mPreferredNAPTR);
401 }
402 aptr = aptr2; // Move to next answer record.
403 }
404 else
405 {
406 // Response may actually have been a CNAME record. If so,
407 // re-run the NAPTR query on the name it points to.
408 Data trueName;
409 aptr2 = parseCNAME(aptr,abuf,alen,trueName);
410 if (aptr2)
411 {
412 // Re-try the NAPTR query on what the CNAME resolved to.
413 StackLog (<< "NAPTR query got CNAME, re-running on true name: " << trueName);
414 lookupNAPTR(trueName);
415 return;
416 }
417 }
418 }
419
420 // This means that dns / NAPTR is misconfigured for this client
421 if (mPreferredNAPTR.key.empty())
422 {
423 StackLog (<< "No NAPTR records that are supported by this client");
424 transition(Finished);
425 mHandler->handle(this);
426 return;
427 }
428
429 int nscount = DNS_HEADER_NSCOUNT(abuf); /* name server record count */
430 StackLog (<< "Found " << nscount << " nameserver records");
431 for (int i = 0; i < nscount && aptr; i++)
432 {
433 // this will ignore NS records
434 aptr = parseAdditional(aptr, abuf, alen);
435 }
436
437 int arcount = DNS_HEADER_ARCOUNT(abuf); /* additional record count */
438 StackLog (<< "Found " << arcount << " additional records");
439 for (int i = 0; i < arcount && aptr; i++)
440 {
441 // this will store any related SRV and A records
442 // don't store SRV records other than the one in the selected NAPTR
443 aptr = parseAdditional(aptr, abuf, alen);
444 }
445
446 if (ancount == 0) // didn't find any NAPTR records
447 {
448 StackLog (<< "There are no NAPTR records so do an SRV lookup instead");
449 goto NAPTRFail; // same as if no NAPTR records
450 }
451 else if (mSRVResults.empty())
452 {
453 // didn't find any SRV records in Additional, so do another query
454
455 assert(mSRVCount == 0);
456 assert(!mPreferredNAPTR.replacement.empty());
457
458 StackLog (<< "No SRV record for " << mPreferredNAPTR.replacement << " in additional section");
459 transition(Pending);
460 mSRVCount++;
461 lookupSRV(mPreferredNAPTR.replacement);
462 }
463 else
464 {
465 // This will fill in mResults based on the DNS result
466 std::sort(mSRVResults.begin(),mSRVResults.end()); // !jf! uggh
467 primeResults();
468 }
469 }
470 else
471 {
472 if (status > 6)
473 {
474 DebugLog (<< "NAPTR lookup failed: " << mInterface.errorMessage(status));
475 }
476 else
477 {
478 StackLog (<< "NAPTR lookup failed: " << mInterface.errorMessage(status));
479 }
480
481 // This will result in no NAPTR results. In this case, send out SRV
482 // queries for each supported protocol
483 NAPTRFail:
484 if (mSips)
485 {
486 if (!mInterface.isSupportedProtocol(TLS))
487 {
488 transition(Finished);
489 mHandler->handle(this);
490 return;
491 }
492
493 mSRVCount++;
494 lookupSRV("_sips._tcp." + mTarget);
495 }
496 else
497 {
498 //.dcm. assumes udp is supported
499 if (mInterface.isSupportedProtocol(TLS))
500 {
501 mSRVCount += 1;
502 lookupSRV("_sips._tcp." + mTarget);
503 }
504
505 if (mInterface.isSupportedProtocol(TCP))
506 {
507 mSRVCount += 2;
508 lookupSRV("_sip._tcp." + mTarget);
509 lookupSRV("_sip._udp." + mTarget);
510 }
511 else
512 {
513 mSRVCount++;
514 lookupSRV("_sip._udp." + mTarget);
515 }
516 }
517 StackLog (<< "Doing SRV queries " << mSRVCount << " for " << mTarget);
518 }
519 }
520
521 void
522 DnsResult::processSRV(int status, const unsigned char* abuf, int alen)
523 {
524 StackLog (<< "Received SRV result for: " << mTarget);
525 assert(mSRVCount>=0);
526 mSRVCount--;
527 StackLog (<< "DnsResult::processSRV() " << mSRVCount << " status=" << status);
528
529 // There could be multiple SRV queries outstanding, but there will be no
530 // other DNS queries outstanding that might cause a callback into this
531 // object.
532 if (mType == Destroyed && mSRVCount == 0)
533 {
534 destroy();
535 return;
536 }
537
538 if (status == 0)
539 {
540 const unsigned char *aptr = abuf + HFIXEDSZ;
541 int qdcount = DNS_HEADER_QDCOUNT(abuf); /* question count */
542 for (int i = 0; i < qdcount && aptr; i++)
543 {
544 aptr = skipDNSQuestion(aptr, abuf, alen);
545 }
546
547 int ancount = DNS_HEADER_ANCOUNT(abuf); /* answer record count */
548 for (int i = 0; i < ancount && aptr; i++)
549 {
550 SRV srv;
551 aptr = parseSRV(aptr, abuf, alen, srv);
552
553 if (aptr)
554 {
555 if (srv.key.find("_sips._udp") != Data::npos)
556 {
557 srv.transport = DTLS;
558 }
559 else if (srv.key.find("_sips._tcp") != Data::npos)
560 {
561 srv.transport = TLS;
562 }
563 else if (srv.key.find("_udp") != Data::npos)
564 {
565 srv.transport = UDP;
566 }
567 else if (srv.key.find("_dtls") != Data::npos)
568 {
569 srv.transport = DTLS;
570 }
571 else if (srv.key.find("_tls") != Data::npos)
572 {
573 srv.transport = TLS;
574 }
575 else if (srv.key.find("_tcp") != Data::npos)
576 {
577 srv.transport = TCP;
578 }
579 else
580 {
581 StackLog (<< "Skipping SRV " << srv.key);
582 continue;
583 }
584
585 StackLog (<< "Adding SRV record (no NAPTR): " << srv);
586 mSRVResults.push_back(srv);
587 }
588 }
589
590 int nscount = DNS_HEADER_NSCOUNT(abuf); /* name server record count */
591 StackLog (<< "Found " << nscount << " nameserver records");
592 for (int i = 0; i < nscount && aptr; i++)
593 {
594 // this will ignore NS records
595 aptr = parseAdditional(aptr, abuf, alen);
596 }
597
598 int arcount = DNS_HEADER_ARCOUNT(abuf); /* additional record count */
599 StackLog (<< "Found " << arcount << " additional records");
600 for (int i = 0; i < arcount && aptr; i++)
601 {
602 // this will store any related A records
603 aptr = parseAdditional(aptr, abuf, alen);
604 }
605 }
606 else
607 {
608 StackLog (<< "SRV lookup failed: " << mInterface.errorMessage(status));
609 }
610
611 // no outstanding queries
612 if (mSRVCount == 0)
613 {
614 if (mSRVResults.empty())
615 {
616 if (mTransport == UNKNOWN_TRANSPORT)
617 {
618 if (mSips)
619 {
620 mTransport = TLS;
621 mPort = Symbols::DefaultSipsPort;
622 }
623 else
624 {
625 mTransport = UDP;
626 mPort = Symbols::DefaultSipPort;
627 }
628 }
629 else
630 {
631 mPort = getDefaultPort(mTransport, 0);
632 }
633
634 StackLog (<< "No SRV records for " << mTarget << ". Trying A/AAAA records");
635 if (mInterface.isSupported(mTransport, V6))
636 {
637 lookupAAAARecords(mTarget);
638 }
639 else if (mInterface.isSupported(mTransport, V4))
640 {
641 lookupARecords(mTarget);
642 }
643 else
644 {
645 primeResults();
646 }
647 }
648 else
649 {
650 std::sort(mSRVResults.begin(),mSRVResults.end()); // !jf! uggh
651 primeResults();
652 }
653 }
654 }
655
656 void
657 DnsResult::processAAAA(int status, const unsigned char* abuf, int alen)
658 {
659 StackLog (<< "Received AAAA result for: " << mTarget);
660 if (!mInterface.isSupported(mTransport, V6))
661 {
662 return;
663 }
664
665
666 #if defined(USE_IPV6)
667 StackLog (<< "DnsResult::processAAAA() " << status);
668 // This function assumes that the AAAA query that caused this callback
669 // is the _only_ outstanding DNS query that might result in a
670 // callback into this function
671 if ( mType == Destroyed )
672 {
673 destroy();
674 return;
675 }
676 if (status == 0)
677 {
678 const unsigned char *aptr = abuf + HFIXEDSZ;
679
680 int qdcount = DNS_HEADER_QDCOUNT(abuf); /*question count*/
681 for (int i=0; i < qdcount && aptr; i++)
682 {
683 aptr = skipDNSQuestion(aptr, abuf, alen);
684 }
685
686 int ancount = DNS_HEADER_ANCOUNT(abuf); /*answer count*/
687 for (int i=0; i < ancount && aptr ; i++)
688 {
689 struct in6_addr aaaa;
690 aptr = parseAAAA(aptr,abuf,alen,&aaaa);
691 if (aptr)
692 {
693 Tuple tuple(aaaa,mPort,mTransport, mTarget);
694 StackLog (<< "Adding " << tuple << " to result set");
695
696 // !jf! Should this be going directly into mResults or into mARecords
697 // !jf! Check for duplicates?
698 mResults.push_back(tuple);
699 }
700 }
701 }
702 else
703 {
704 StackLog (<< "Failed async dns query: " << mInterface.errorMessage(status));
705 }
706 if (mInterface.isSupported(mTransport, V4))
707 {
708 lookupARecords(mPassHostFromAAAAtoA);
709 }
710 #else
711 assert(0);
712 #endif
713 }
714
715 void
716 DnsResult::processHost(int status, const struct hostent* result)
717 {
718 if (!mInterface.isSupported(mTransport, V4))
719 {
720 return;
721 }
722
723 StackLog (<< "Received A result for: " << mTarget);
724 StackLog (<< "DnsResult::processHost() " << status);
725 assert(mInterface.isSupported(mTransport, V4));
726
727 // This function assumes that the A query that caused this callback
728 // is the _only_ outstanding DNS query that might result in a
729 // callback into this function
730 if ( mType == Destroyed )
731 {
732 destroy();
733 return;
734 }
735
736 if (status == 0)
737 {
738 StackLog (<< "DNS A lookup canonical name: " << result->h_name);
739 for (char** pptr = result->h_addr_list; *pptr != 0; pptr++)
740 {
741 in_addr addr;
742 addr.s_addr = *((u_int32_t*)(*pptr));
743 Tuple tuple(addr, mPort, mTransport, mTarget);
744 StackLog (<< "Adding " << tuple << " to result set");
745 // !jf! Should this be going directly into mResults or into mARecords
746 // !jf! Check for duplicates?
747 mResults.push_back(tuple);
748 }
749 }
750 else
751 {
752 StackLog (<< "Failed async A query: " << mInterface.errorMessage(status));
753 }
754
755 if (mSRVCount == 0)
756 {
757 bool changed = (mType == Pending);
758 if (mResults.empty())
759 {
760 #ifdef WIN32_SYNCRONOUS_RESOLUTION_ON_ARES_FAILURE
761 // Try Windows Name Resolution (not asyncronous)
762 WSAQUERYSET QuerySet = { 0 };
763 GUID guidServiceTypeUDP = SVCID_UDP(mPort);
764 GUID guidServiceTypeTCP = SVCID_TCP(mPort);
765 HANDLE hQuery;
766 QuerySet.dwSize = sizeof(WSAQUERYSET);
767 QuerySet.lpServiceClassId = mTransport == UDP ? &guidServiceTypeUDP : &guidServiceTypeTCP;
768 QuerySet.dwNameSpace = NS_ALL;
769 QuerySet.lpszServiceInstanceName = (char *)mTarget.c_str();
770 if(WSALookupServiceBegin(&QuerySet, LUP_RETURN_ADDR, &hQuery) == 0)
771 {
772 DWORD dwQuerySize = 256; // Starting size
773 int iRet = 0;
774 bool fDone = false;
775 LPWSAQUERYSET pQueryResult = (LPWSAQUERYSET) new char[dwQuerySize];
776 while(iRet == 0 && pQueryResult)
777 {
778 iRet = WSALookupServiceNext(hQuery, 0, &dwQuerySize, pQueryResult);
779 if(pQueryResult && iRet == -1 && GetLastError() == WSAEFAULT)
780 {
781 delete [] pQueryResult;
782 pQueryResult = (LPWSAQUERYSET) new char[dwQuerySize]; // Re-allocate new size
783 iRet = WSALookupServiceNext(hQuery, 0, &dwQuerySize, pQueryResult);
784 }
785 if(pQueryResult && iRet == 0)
786 {
787 for(DWORD i = 0; i < pQueryResult->dwNumberOfCsAddrs; i++)
788 {
789 SOCKADDR_IN *pSockAddrIn = (SOCKADDR_IN *)pQueryResult->lpcsaBuffer[i].RemoteAddr.lpSockaddr;
790 Tuple tuple(pSockAddrIn->sin_addr, mPort, mTransport, mTarget);
791 StackLog (<< "Adding (WIN) " << tuple << " to result set");
792 mResults.push_back(tuple);
793 mType = Available;
794 }
795 }
796 }
797 delete [] pQueryResult;
798 WSALookupServiceEnd(hQuery);
799 }
800 if(mResults.empty())
801 {
802 mType = Finished;
803 }
804 #else
805 mType = Finished;
806 #endif
807 }
808 else
809 {
810 transition(Available);
811 }
812 if (changed) mHandler->handle(this);
813 }
814 }
815
816 void
817 DnsResult::primeResults()
818 {
819 StackLog(<< "Priming " << Inserter(mSRVResults));
820 //assert(mType != Pending);
821 //assert(mType != Finished);
822 assert(mResults.empty());
823
824 if (!mSRVResults.empty())
825 {
826 SRV next = retrieveSRV();
827 StackLog (<< "Primed with SRV=" << next);
828 if ( mARecords.count(next.target)
829 #if defined(USE_IPV6)
830 + mAAAARecords.count(next.target)
831 #endif
832 )
833 {
834 #if defined(USE_IPV6)
835 In6AddrList& aaaarecs = mAAAARecords[next.target];
836 for (In6AddrList::const_iterator i=aaaarecs.begin();
837 i!=aaaarecs.end(); i++)
838 {
839 Tuple tuple(i->addr, next.port,next.transport, mTarget);
840 if (mInterface.isSupported(next.transport, V6))
841 {
842 StackLog (<< "Adding " << tuple << " to result set");
843 mResults.push_back(tuple);
844 }
845 }
846 #endif
847 In4AddrList& arecs = mARecords[next.target];
848 for (In4AddrList::const_iterator i=arecs.begin(); i!=arecs.end(); i++)
849 {
850 Tuple tuple(i->addr, next.port, next.transport, mTarget);
851 StackLog (<< "Adding " << tuple << " to result set");
852 if (mInterface.isSupported(next.transport, V4))
853 {
854 mResults.push_back(tuple);
855 }
856 }
857 StackLog (<< "Try: " << Inserter(mResults));
858
859 bool changed = (mType == Pending);
860 transition(Available);
861 if (changed) mHandler->handle(this);
862
863 // recurse if there are no results. This could happen if the DNS SRV
864 // target record didn't have any A/AAAA records. This will terminate if we
865 // run out of SRV results.
866 if (mResults.empty())
867 {
868 primeResults();
869 }
870 }
871 else
872 {
873 // !jf! not going to test this for now
874 // this occurs when the A records were not provided in the Additional
875 // Records of the DNS result
876 // we will need to store the SRV record that is being looked up so we
877 // can populate the resulting Tuples
878 transition(Pending);
879 mPort = next.port;
880 mTransport = next.transport;
881 StackLog (<< "No A or AAAA record for " << next.target << " in additional records");
882 if (mInterface.isSupported(mTransport, V6))
883 {
884 lookupAAAARecords(next.target); // for current target and port
885 }
886 else if (mInterface.isSupported(mTransport, V4))
887 {
888 lookupARecords(next.target); // for current target and port
889 }
890 else
891 {
892 assert(0);
893 mHandler->handle(this);
894 }
895 // don't call primeResults since we need to wait for the response to
896 // AAAA/A query first
897 }
898 }
899 else
900 {
901 bool changed = (mType == Pending);
902 transition(Finished);
903 if (changed) mHandler->handle(this);
904 }
905
906 // Either we are finished or there are results primed
907 assert(mType == Finished ||
908 mType == Pending ||
909 (mType == Available && !mResults.empty()));
910 }
911
912 // implement the selection algorithm from rfc2782 (SRV records)
913 DnsResult::SRV
914 DnsResult::retrieveSRV()
915 {
916 // !ah! if mTransport is known -- should we ignore those that don't match?!
917 assert(!mSRVResults.empty());
918
919 const SRV& srv = *mSRVResults.begin();
920 if (srv.cumulativeWeight == 0)
921 {
922 int priority = srv.priority;
923
924 mCumulativeWeight=0;
925 //!dcm! -- this should be fixed properly; TCP req. lines were being sent
926 //out on UDP
927 TransportType transport = mSRVResults.begin()->transport;
928 for (std::vector<SRV>::iterator i=mSRVResults.begin();
929 i!=mSRVResults.end()
930 && i->priority == priority
931 && i->transport == transport; i++)
932 {
933 mCumulativeWeight += i->weight;
934 i->cumulativeWeight = mCumulativeWeight;
935 }
936 }
937
938 int selected = Random::getRandom() % (mCumulativeWeight+1);
939
940 StackLog (<< "cumulative weight = " << mCumulativeWeight << " selected=" << selected);
941
942 std::vector<SRV>::iterator i;
943 for (i=mSRVResults.begin(); i!=mSRVResults.end(); i++)
944 {
945 if (i->cumulativeWeight >= selected)
946 {
947 break;
948 }
949 }
950
951 if (i == mSRVResults.end())
952 {
953 InfoLog (<< "SRV Results problem selected=" << selected << " cum=" << mCumulativeWeight);
954 }
955 assert(i != mSRVResults.end());
956 SRV next = *i;
957 mCumulativeWeight -= next.cumulativeWeight;
958 mSRVResults.erase(i);
959
960 StackLog (<< "SRV: " << Inserter(mSRVResults));
961
962 return next;
963 }
964
965
966 // adapted from the adig.c example in ares
967 const unsigned char*
968 DnsResult::parseAdditional(const unsigned char *aptr,
969 const unsigned char *abuf,
970 int alen)
971 {
972 char *name=0;
973 int len=0;
974 int status=0;
975
976 // Parse the RR name.
977 status = ares_expand_name(aptr, abuf, alen, &name, &len);
978 if (status != ARES_SUCCESS)
979 {
980 StackLog (<< "Failed parse of RR");
981 return NULL;
982 }
983 aptr += len;
984
985 /* Make sure there is enough data after the RR name for the fixed
986 * part of the RR.
987 */
988 if (aptr + RRFIXEDSZ > abuf + alen)
989 {
990 StackLog (<< "Failed parse of RR");
991 free(name);
992 return NULL;
993 }
994
995 // Parse the fixed part of the RR, and advance to the RR data
996 // field.
997 int type = DNS_RR_TYPE(aptr);
998 int dlen = DNS_RR_LEN(aptr);
999 aptr += RRFIXEDSZ;
1000 if (aptr + dlen > abuf + alen)
1001 {
1002 StackLog (<< "Failed parse of RR");
1003 free(name);
1004 return NULL;
1005 }
1006
1007 // Display the RR data. Don't touch aptr.
1008 Data key = name;
1009 free(name);
1010
1011 if (type == T_SRV)
1012 {
1013 SRV srv;
1014
1015 // The RR data is three two-byte numbers representing the
1016 // priority, weight, and port, followed by a domain name.
1017 srv.key = key;
1018 srv.priority = DNS__16BIT(aptr);
1019 srv.weight = DNS__16BIT(aptr + 2);
1020 srv.port = DNS__16BIT(aptr + 4);
1021 status = ares_expand_name(aptr + 6, abuf, alen, &name, &len);
1022 if (status != ARES_SUCCESS)
1023 {
1024 return NULL;
1025 }
1026 srv.target = name;
1027 free(name);
1028
1029 // Only add SRV results for the preferred NAPTR target as per rfc3263
1030 // (section 4.1).
1031 assert(!mPreferredNAPTR.key.empty());
1032 if (srv.key == mPreferredNAPTR.replacement && srv.target != Symbols::DOT)
1033 {
1034 if (srv.key.find("_sips._tcp") != Data::npos)
1035 {
1036 srv.transport = TLS;
1037 }
1038 else if (srv.key.find("_sips._udp") != Data::npos)
1039 {
1040 srv.transport = DTLS;
1041 }
1042 else if (srv.key.find("_udp") != Data::npos)
1043 {
1044 srv.transport = UDP;
1045 }
1046 else if (srv.key.find("_tcp") != Data::npos)
1047 {
1048 srv.transport = TCP;
1049 }
1050 else
1051 {
1052 StackLog (<< "Skipping SRV " << srv.key);
1053 return aptr + dlen;
1054 }
1055
1056 StackLog (<< "Inserting SRV: " << srv);
1057 mSRVResults.push_back(srv);
1058 }
1059
1060 return aptr + dlen;
1061
1062 }
1063 else if (type == T_A)
1064 {
1065 // The RR data is a four-byte Internet address.
1066 if (dlen != 4)
1067 {
1068 StackLog (<< "Failed parse of RR");
1069 return NULL;
1070 }
1071
1072 struct in_addr addr;
1073 memcpy(&addr, aptr, sizeof(struct in_addr));
1074 StackLog (<< "From additional: " << key << ":" << DnsUtil::inet_ntop(addr));
1075
1076 // Only add the additional records if they weren't already there from
1077 // another query
1078 if (mARecords.count(key) == 0)
1079 {
1080 IpV4Addr a;
1081 a.addr = addr;
1082 mARecords[key].push_back(a);
1083 }
1084 return aptr + dlen;
1085 }
1086 #if defined(USE_IPV6)
1087 else if (type == T_AAAA)
1088 {
1089 if (dlen != 16) // The RR is 128 bits of ipv6 address
1090 {
1091 StackLog (<< "Failed parse of RR");
1092 return NULL;
1093 }
1094 struct in6_addr addr;
1095 memcpy(&addr, aptr, sizeof(struct in6_addr));
1096 StackLog (<< "From additional: " << key << ":" << DnsUtil::inet_ntop(addr));
1097 // Only add the additional records if they weren't already there from
1098 // another query
1099 if (mAAAARecords.count(key) == 0)
1100 {
1101 IpV6Addr a;
1102 a.addr = addr;
1103 mAAAARecords[key].push_back(a);
1104 }
1105 return aptr + dlen;
1106 }
1107 #endif
1108 else if (type == T_CNAME)
1109 {
1110 StackLog( << "key=" << key << " skipping additional CNAME " << dlen << " bytes");
1111 return aptr + dlen;
1112 }
1113 else // just skip it (we don't care :)
1114 {
1115 //StackLog (<< "Skipping: " << key);
1116 return aptr + dlen;
1117 }
1118 }
1119
1120 // adapted from the adig.c example in ares
1121 const unsigned char*
1122 DnsResult::skipDNSQuestion(const unsigned char *aptr,
1123 const unsigned char *abuf,
1124 int alen)
1125 {
1126 char *name=0;
1127 int status=0;
1128 int len=0;
1129
1130 // Parse the question name.
1131 status = ares_expand_name(aptr, abuf, alen, &name, &len);
1132 if (status != ARES_SUCCESS)
1133 {
1134 StackLog (<< "Failed parse of RR");
1135 return NULL;
1136 }
1137 aptr += len;
1138
1139 // Make sure there's enough data after the name for the fixed part
1140 // of the question.
1141 if (aptr + QFIXEDSZ > abuf + alen)
1142 {
1143 free(name);
1144 StackLog (<< "Failed parse of RR");
1145 return NULL;
1146 }
1147
1148 // Parse the question type and class.
1149 //int type = DNS_QUESTION_TYPE(aptr);
1150 //int dnsclass = DNS_QUESTION_CLASS(aptr);
1151 aptr += QFIXEDSZ;
1152
1153 free(name);
1154 return aptr;
1155 }
1156
1157 // adapted from the adig.c example in ares
1158 const unsigned char*
1159 DnsResult::parseSRV(const unsigned char *aptr,
1160 const unsigned char *abuf,
1161 int alen,
1162 SRV& srv)
1163 {
1164 char *name;
1165 int len=0;
1166 int status=0;
1167
1168 // Parse the RR name.
1169 status = ares_expand_name(aptr, abuf, alen, &name, &len);
1170 if (status != ARES_SUCCESS)
1171 {
1172 StackLog (<< "Failed parse of RR");
1173 return NULL;
1174 }
1175 aptr += len;
1176
1177 /* Make sure there is enough data after the RR name for the fixed
1178 * part of the RR.
1179 */
1180 if (aptr + RRFIXEDSZ > abuf + alen)
1181 {
1182 free(name);
1183 StackLog (<< "Failed parse of RR");
1184 return NULL;
1185 }
1186
1187 // Parse the fixed part of the RR, and advance to the RR data
1188 // field.
1189 int type = DNS_RR_TYPE(aptr);
1190 int dlen = DNS_RR_LEN(aptr);
1191 aptr += RRFIXEDSZ;
1192 if (aptr + dlen > abuf + alen)
1193 {
1194 free(name);
1195 StackLog (<< "Failed parse of RR");
1196 return NULL;
1197 }
1198 Data key = name;
1199 free(name);
1200
1201 // Display the RR data. Don't touch aptr.
1202 if (type == T_SRV)
1203 {
1204 // The RR data is three two-byte numbers representing the
1205 // priority, weight, and port, followed by a domain name.
1206 srv.key = key;
1207 srv.priority = DNS__16BIT(aptr);
1208 srv.weight = DNS__16BIT(aptr + 2);
1209 srv.port = DNS__16BIT(aptr + 4);
1210 status = ares_expand_name(aptr + 6, abuf, alen, &name, &len);
1211 if (status != ARES_SUCCESS)
1212 {
1213 StackLog (<< "Failed parse of RR");
1214 return NULL;
1215 }
1216 srv.target = name;
1217 free(name);
1218
1219 return aptr + dlen;
1220
1221 }
1222 else
1223 {
1224 StackLog (<< "Failed parse of RR");
1225 return NULL;
1226 }
1227 }
1228
1229
1230 #if defined(USE_IPV6)
1231 // adapted from the adig.c example in ares
1232 const unsigned char*
1233 DnsResult::parseAAAA(const unsigned char *aptr,
1234 const unsigned char *abuf,
1235 int alen,
1236 in6_addr * aaaa)
1237 {
1238 char *name;
1239 int len=0;
1240 int status=0;
1241
1242 // Parse the RR name.
1243 status = ares_expand_name(aptr, abuf, alen, &name, &len);
1244 if (status != ARES_SUCCESS)
1245 {
1246 StackLog (<< "Failed parse of RR");
1247 return NULL;
1248 }
1249 aptr += len;
1250
1251 /* Make sure there is enough data after the RR name for the fixed
1252 * part of the RR.
1253 */
1254 if (aptr + RRFIXEDSZ > abuf + alen)
1255 {
1256 free(name);
1257 StackLog (<< "Failed parse of RR");
1258 return NULL;
1259 }
1260
1261 // Parse the fixed part of the RR, and advance to the RR data
1262 // field.
1263 int type = DNS_RR_TYPE(aptr);
1264 int dlen = DNS_RR_LEN(aptr);
1265 aptr += RRFIXEDSZ;
1266 if (aptr + dlen > abuf + alen)
1267 {
1268 free(name);
1269 StackLog (<< "Failed parse of RR");
1270 return NULL;
1271 }
1272 Data key = name;
1273 free(name);
1274
1275 // Display the RR data. Don't touch aptr.
1276
1277 if (type == T_AAAA)
1278 {
1279 // The RR data is 128 bits of ipv6 address in network byte
1280 // order
1281 memcpy(aaaa, aptr, sizeof(in6_addr));
1282 return aptr + dlen;
1283 }
1284 if (type == T_CNAME)
1285 {
1286 StackLog( << "key=" << key << " skipping additional CNAME " << dlen << " bytes");
1287 return aptr + dlen;
1288 }
1289 else
1290
1291 {
1292 StackLog (<< "Failed parse of RR");
1293 return 0;
1294 }
1295 }
1296 #endif
1297
1298
1299 const unsigned char *
1300 DnsResult::parseCNAME(const unsigned char *aptr,
1301 const unsigned char *abuf,
1302 int alen,
1303 Data& trueName)
1304
1305 {
1306 char* name=0;
1307 int len=0;
1308 int status=0;
1309
1310 // Parse the RR name.
1311 status = ares_expand_name(aptr, abuf, alen, &name, &len);
1312 if (status != ARES_SUCCESS)
1313 {
1314 StackLog (<< "Failed parse of RR");
1315 return NULL;
1316 }
1317 aptr += len;
1318
1319 // Make sure there is enough data after the RR name for the fixed
1320 // part of the RR.
1321 if (aptr + RRFIXEDSZ > abuf + alen)
1322 {
1323 free(name);
1324 StackLog (<< "Failed parse of RR");
1325 return NULL;
1326 }
1327 free(name);
1328
1329 int type = DNS_RR_TYPE(aptr);
1330 int dlen = DNS_RR_LEN(aptr);
1331 aptr += RRFIXEDSZ;
1332 if (aptr + dlen > abuf + alen)
1333 {
1334 StackLog (<< "Failed parse of RR");
1335 return NULL;
1336 }
1337
1338 if (type == T_CNAME)
1339 {
1340 status = ares_expand_name(aptr,abuf,alen,&name,&len);
1341 if (status != ARES_SUCCESS)
1342 {
1343 StackLog (<< "Failed parse of RR");
1344 return NULL;
1345 }
1346 else
1347 {
1348 trueName = name;
1349 free(name);
1350 return aptr + dlen;
1351 }
1352 }
1353 else
1354 {
1355 StackLog (<< "Failed parse of RR");
1356 return NULL;
1357 }
1358 }
1359
1360 // adapted from the adig.c example in ares
1361 const unsigned char*
1362 DnsResult::parseNAPTR(const unsigned char *aptr,
1363 const unsigned char *abuf,
1364 int alen,
1365 NAPTR& naptr)
1366 {
1367 const unsigned char* p=0;
1368 char* name=0;
1369 int len=0;
1370 int status=0;
1371
1372 // Parse the RR name.
1373 status = ares_expand_name(aptr, abuf, alen, &name, &len);
1374 if (status != ARES_SUCCESS)
1375 {
1376 StackLog (<< "Failed parse of RR");
1377 return NULL;
1378 }
1379 aptr += len;
1380
1381 // Make sure there is enough data after the RR name for the fixed
1382 // part of the RR.
1383 if (aptr + RRFIXEDSZ > abuf + alen)
1384 {
1385 free(name);
1386 StackLog (<< "Failed parse of RR");
1387 return NULL;
1388 }
1389
1390 // Parse the fixed part of the RR, and advance to the RR data
1391 // field.
1392 int type = DNS_RR_TYPE(aptr);
1393 int dlen = DNS_RR_LEN(aptr);
1394 aptr += RRFIXEDSZ;
1395 if (aptr + dlen > abuf + alen)
1396 {
1397 free(name);
1398 StackLog (<< "Failed parse of RR");
1399 return NULL;
1400 }
1401
1402 Data key = name;
1403 free(name);
1404
1405 // Look at the RR data. Don't touch aptr.
1406 if (type == T_NAPTR)
1407 {
1408 // The RR data is two two-byte numbers representing the
1409 // order and preference, followed by three character strings
1410 // representing flags, services, a regex, and a domain name.
1411
1412 naptr.key = key;
1413 naptr.order = DNS__16BIT(aptr);
1414 naptr.pref = DNS__16BIT(aptr + 2);
1415 p = aptr + 4;
1416 len = *p;
1417 if (p + len + 1 > aptr + dlen)
1418 {
1419 StackLog (<< "Failed parse of RR");
1420 return NULL;
1421 }
1422 naptr.flags = Data(p+1, len);
1423
1424 p += len + 1;
1425 len = *p;
1426 if (p + len + 1 > aptr + dlen)
1427 {
1428 StackLog (<< "Failed parse of RR");
1429 return NULL;
1430 }
1431 naptr.service = Data(p+1, len);
1432
1433 p += len + 1;
1434 len = *p;
1435 if (p + len + 1 > aptr + dlen)
1436 {
1437 StackLog (<< "Failed parse of RR");
1438 return NULL;
1439 }
1440 naptr.regex = Data(p+1, len);
1441
1442 p += len + 1;
1443 status = ares_expand_name(p, abuf, alen, &name, &len);
1444 if (status != ARES_SUCCESS)
1445 {
1446 StackLog (<< "Failed parse of RR");
1447 return NULL;
1448 }
1449 naptr.replacement = name;
1450
1451 free(name);
1452 return aptr + dlen;
1453 }
1454 else
1455 {
1456 StackLog (<< "Failed parse of RR");
1457 return NULL;
1458 }
1459 }
1460
1461 DnsResult::NAPTR::NAPTR() : order(0), pref(0)
1462 {
1463 }
1464
1465 bool
1466 DnsResult::NAPTR::operator<(const DnsResult::NAPTR& rhs) const
1467 {
1468 if (key.empty()) // default value
1469 {
1470 return false;
1471 }
1472 else if (rhs.key.empty()) // default value
1473 {
1474 return true;
1475 }
1476 else if (order < rhs.order)
1477 {
1478 return true;
1479 }
1480 else if (order == rhs.order)
1481 {
1482 if (pref < rhs.pref)
1483 {
1484 return true;
1485 }
1486 else if (pref == rhs.pref)
1487 {
1488 return replacement < rhs.replacement;
1489 }
1490 }
1491 return false;
1492 }
1493
1494 DnsResult::SRV::SRV() : priority(0), weight(0), cumulativeWeight(0), port(0)
1495 {
1496 }
1497
1498 bool
1499 DnsResult::SRV::operator<(const DnsResult::SRV& rhs) const
1500 {
1501 if (transport < rhs.transport)
1502 {
1503 return true;
1504 }
1505 else if (transport == rhs.transport)
1506 {
1507 if (target < rhs.target)
1508 {
1509 return true;
1510 }
1511 else if (target == rhs.target)
1512 {
1513 if (priority < rhs.priority)
1514 {
1515 return true;
1516 }
1517 else if (priority == rhs.priority)
1518 {
1519 if (weight < rhs.weight)
1520 {
1521 return true;
1522 }
1523 }
1524 }
1525 }
1526 return false;
1527 }
1528
1529 std::ostream&
1530 resip::operator<<(std::ostream& strm, const resip::DnsResult& result)
1531 {
1532 strm << result.mTarget << " --> " << Inserter(result.mResults);
1533 return strm;
1534 }
1535
1536
1537 std::ostream&
1538 resip::operator<<(std::ostream& strm, const resip::DnsResult::NAPTR& naptr)
1539 {
1540 strm << "key=" << naptr.key
1541 << " order=" << naptr.order
1542 << " pref=" << naptr.pref
1543 << " flags=" << naptr.flags
1544 << " service=" << naptr.service
1545 << " regex=" << naptr.regex
1546 << " replacement=" << naptr.replacement;
1547 return strm;
1548 }
1549
1550 std::ostream&
1551 resip::operator<<(std::ostream& strm, const resip::DnsResult::SRV& srv)
1552 {
1553 strm << "key=" << srv.key
1554 << " t=" << Tuple::toData(srv.transport)
1555 << " p=" << srv.priority
1556 << " w=" << srv.weight
1557 << " c=" << srv.cumulativeWeight
1558 << " port=" << srv.port
1559 << " target=" << srv.target;
1560 return strm;
1561 }
1562
1563
1564 // Copyright (c) 2003, Jason Fischl
1565 /* ====================================================================
1566 * The Vovida Software License, Version 1.0
1567 *
1568 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
1569 *
1570 * Redistribution and use in source and binary forms, with or without
1571 * modification, are permitted provided that the following conditions
1572 * are met:
1573 *
1574 * 1. Redistributions of source code must retain the above copyright
1575 * notice, this list of conditions and the following disclaimer.
1576 *
1577 * 2. Redistributions in binary form must reproduce the above copyright
1578 * notice, this list of conditions and the following disclaimer in
1579 * the documentation and/or other materials provided with the
1580 * distribution.
1581 *
1582 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
1583 * and "Vovida Open Communication Application Library (VOCAL)" must
1584 * not be used to endorse or promote products derived from this
1585 * software without prior written permission. For written
1586 * permission, please contact vocal@vovida.org.
1587 *
1588 * 4. Products derived from this software may not be called "VOCAL", nor
1589 * may "VOCAL" appear in their name, without prior written
1590 * permission of Vovida Networks, Inc.
1591 *
1592 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
1593 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1594 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
1595 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
1596 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
1597 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
1598 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
1599 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
1600 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
1601 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1602 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1603 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1604 * DAMAGE.
1605 *
1606 * ====================================================================
1607 *
1608 * This software consists of voluntary contributions made by Vovida
1609 * Networks, Inc. and many individuals on behalf of Vovida Networks,
1610 * Inc. For more information on Vovida Networks, Inc., please see
1611 * <http://www.vovida.org/>.
1612 *
1613 */
1614

Properties

Name Value
svn:eol-style LF

webmaster AT resiprocate DOT org
ViewVC Help
Powered by ViewVC 1.1.27