Added IsSupportingRTP function to simplify detecting when STUN supports RTP
[pwlib.git] / src / ptclib / pdns.cxx
blob89511da4b1be2dcfd3eecd69b90903243a234d6e
1 /*
2 * pdns.cxx
4 * Portable Windows Library
6 * Copyright (c) 2003 Equivalence Pty. Ltd.
8 * The contents of this file are subject to the Mozilla Public License
9 * Version 1.0 (the "License"); you may not use this file except in
10 * compliance with the License. You may obtain a copy of the License at
11 * http://www.mozilla.org/MPL/
13 * Software distributed under the License is distributed on an "AS IS"
14 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15 * the License for the specific language governing rights and limitations
16 * under the License.
18 * The Original Code is Portable Windows Library.
20 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
22 * Contributor(s): ______________________________________.
24 * Copyright 2003 Equivalence Pty. Ltd.
26 * $Log$
27 * Revision 1.21 2004/11/15 23:47:18 csoutheren
28 * Fixed problem with empty SRV names
30 * Revision 1.20 2004/11/15 11:33:52 csoutheren
31 * Fixed problem in SRV record handling
33 * Revision 1.19 2004/06/24 07:36:24 csoutheren
34 * Added definitions of T_SRV and T_NAPTR for hosts that do not have these
36 * Revision 1.18 2004/06/01 23:30:38 csoutheren
37 * Removed warning under Linux
39 * Revision 1.17 2004/05/31 23:14:17 csoutheren
40 * Fixed warnings under VS.net and fixed problem with SRV records when returning multiple records
42 * Revision 1.16 2004/05/31 12:49:48 csoutheren
43 * Added handling of unknown DNS types
45 * Revision 1.15 2004/05/31 12:14:13 rjongbloed
46 * Fixed missing namespace selector on function definition.
47 * Opyimised some string processing
49 * Revision 1.14 2004/05/28 06:50:45 csoutheren
50 * Reorganised DNS functions to use templates, and exposed more internals to allow new DNS lookup types to be added
52 * Revision 1.13 2004/04/09 06:52:17 rjongbloed
53 * Removed #pargma linker command for /delayload of DLL as documentations sais that
54 * you cannot do this.
56 * Revision 1.12 2004/02/23 23:52:19 csoutheren
57 * Added pragmas to avoid every Windows application needing to include libs explicitly
59 * Revision 1.11 2004/01/03 03:37:53 csoutheren
60 * Fixed compile problem on Linux
62 * Revision 1.10 2004/01/03 03:10:42 csoutheren
63 * Fixed more problems with looking up SRV records, especially on Windows
65 * Revision 1.9 2004/01/02 13:22:04 csoutheren
66 * Fixed problem with extracting SRV records from DNS
68 * Revision 1.8 2003/11/02 15:52:58 shawn
69 * added arpa/nameser_compat.h for Mac OS X 10.3 (Panther)
71 * Revision 1.7 2003/04/28 23:57:40 robertj
72 * Fixed Solaris compatibility
74 * Revision 1.6 2003/04/22 23:21:37 craigs
75 * Swapped includes at request of Shawn Hsiao for compatiobility with MacOSX
77 * Revision 1.5 2003/04/16 14:21:12 craigs
78 * Added set of T_SRV for MacOS
80 * Revision 1.4 2003/04/16 08:00:19 robertj
81 * Windoes psuedo autoconf support
83 * Revision 1.3 2003/04/15 08:14:32 craigs
84 * Added single string form of GetSRVRecords
86 * Revision 1.2 2003/04/15 08:05:19 craigs
87 * Added Unix implementation
89 * Revision 1.1 2003/04/15 04:06:35 craigs
90 * Initial version
94 #ifdef __GNUC__
95 #pragma implementation "pdns.h"
96 #endif
98 #include <ptlib.h>
99 #include <ptclib/pdns.h>
101 #if P_DNS
104 /////////////////////////////////////////////////
106 #ifdef P_HAS_RESOLVER
108 static BOOL GetDN(const BYTE * reply, const BYTE * replyEnd, BYTE * & cp, char * buff)
110 int len = dn_expand(reply, replyEnd, cp, buff, MAXDNAME);
111 if (len < 0)
112 return FALSE;
113 cp += len;
114 return TRUE;
117 static BOOL ProcessDNSRecords(
118 const BYTE * reply,
119 const BYTE * replyEnd,
120 BYTE * cp,
121 PINDEX anCount,
122 PINDEX nsCount,
123 PINDEX arCount,
124 PDNS_RECORD * results)
126 PDNS_RECORD lastRecord = NULL;
128 PINDEX rrCount = anCount + nsCount + arCount;
129 nsCount += anCount;
130 arCount += nsCount;
132 PINDEX i;
133 for (i = 0; i < rrCount; i++) {
135 int section;
136 if (i < anCount)
137 section = DnsSectionAnswer;
138 else if (i < nsCount)
139 section = DnsSectionAuthority;
140 else // if (i < arCount)
141 section = DnsSectionAddtional;
143 // get the name
144 char pName[MAXDNAME];
145 if (!GetDN(reply, replyEnd, cp, pName))
146 return FALSE;
148 // get other common parts of the record
149 WORD type;
150 WORD dnsClass;
151 DWORD ttl;
152 WORD dlen;
154 GETSHORT(type, cp);
155 GETSHORT(dnsClass, cp);
156 GETLONG (ttl, cp);
157 GETSHORT(dlen, cp);
159 BYTE * data = cp;
160 cp += dlen;
162 PDNS_RECORD newRecord = NULL;
164 switch (type) {
165 default:
166 newRecord = (PDNS_RECORD)malloc(sizeof(DnsRecord) + sizeof(DWORD) + dlen);
167 newRecord->Data.Null.dwByteCount = dlen;
168 memcpy(&newRecord->Data, data, dlen);
169 break;
171 case T_SRV:
172 newRecord = (PDNS_RECORD)malloc(sizeof(DnsRecord));
173 memset(newRecord, 0, sizeof(DnsRecord));
174 GETSHORT(newRecord->Data.SRV.wPriority, data);
175 GETSHORT(newRecord->Data.SRV.wWeight, data);
176 GETSHORT(newRecord->Data.SRV.wPort, data);
177 if (!GetDN(reply, replyEnd, data, newRecord->Data.SRV.pNameTarget)) {
178 free(newRecord);
179 return FALSE;
181 break;
183 case T_MX:
184 newRecord = (PDNS_RECORD)malloc(sizeof(DnsRecord));
185 memset(newRecord, 0, sizeof(DnsRecord));
186 GETSHORT(newRecord->Data.MX.wPreference, data);
187 if (!GetDN(reply, replyEnd, data, newRecord->Data.MX.pNameExchange)) {
188 free(newRecord);
189 return FALSE;
191 break;
193 case T_A:
194 newRecord = (PDNS_RECORD)malloc(sizeof(DnsRecord));
195 memset(newRecord, 0, sizeof(DnsRecord));
196 GETLONG(newRecord->Data.A.IpAddress, data);
197 break;
199 case T_NS:
200 newRecord = (PDNS_RECORD)malloc(sizeof(DnsRecord));
201 memset(newRecord, 0, sizeof(DnsRecord));
202 if (!GetDN(reply, replyEnd, data, newRecord->Data.NS.pNameHost)) {
203 delete newRecord;
204 return FALSE;
206 break;
209 // initialise the new record
210 if (newRecord != NULL) {
211 newRecord->wType = type;
212 newRecord->Flags.S.Section = section;
213 newRecord->pNext = NULL;
214 strcpy(newRecord->pName, pName);
216 if (*results == NULL)
217 *results = newRecord;
219 if (lastRecord != NULL)
220 lastRecord->pNext = newRecord;
222 lastRecord = newRecord;
223 newRecord = NULL;
227 return TRUE;
230 void DnsRecordListFree(PDNS_RECORD rec, int /* FreeType */)
232 while (rec != NULL) {
233 PDNS_RECORD next = rec->pNext;
234 free(rec);
235 rec = next;
239 DNS_STATUS DnsQuery_A(const char * service,
240 WORD requestType,
241 DWORD options,
242 void *,
243 PDNS_RECORD * results,
244 void *)
246 if (results == NULL)
247 return -1;
249 *results = NULL;
251 res_init();
253 union {
254 HEADER hdr;
255 BYTE buf[PACKETSZ];
256 } reply;
258 int replyLen = res_search(service, C_IN, requestType, (BYTE *)&reply, sizeof(reply));
260 if (replyLen < 1)
261 return -1;
263 BYTE * replyStart = reply.buf;
264 BYTE * replyEnd = reply.buf + replyLen;
265 BYTE * cp = reply.buf + sizeof(HEADER);
267 // ignore questions in response
268 unsigned i;
269 for (i = 0; i < ntohs(reply.hdr.qdcount); i++) {
270 char qName[MAXDNAME];
271 if (!GetDN(replyStart, replyEnd, cp, qName))
272 return -1;
273 cp += QFIXEDSZ;
276 if (!ProcessDNSRecords(replyStart,
277 replyEnd,
279 ntohs(reply.hdr.ancount),
280 ntohs(reply.hdr.nscount),
281 ntohs(reply.hdr.arcount),
282 results)) {
283 DnsRecordListFree(*results, 0);
284 return -1;
287 return 0;
291 #endif // P_HAS_RESOLVER
293 PObject::Comparison PDNS::SRVRecord::Compare(const PObject & obj) const
295 const SRVRecord * other = dynamic_cast<const SRVRecord *>(&obj);
297 if (other == NULL)
298 return LessThan;
300 if (priority < other->priority)
301 return LessThan;
302 else if (priority > other->priority)
303 return GreaterThan;
305 if (weight < other->weight)
306 return LessThan;
307 else if (weight > other->weight)
308 return GreaterThan;
310 return EqualTo;
313 void PDNS::SRVRecord::PrintOn(ostream & strm) const
315 strm << "host=" << hostName << ":" << port << "(" << hostAddress << "), "
316 << "priority=" << priority << ", "
317 << "weight=" << weight;
320 /////////////////////////////////////////////////
322 PDNS::SRVRecord * PDNS::SRVRecordList::HandleDNSRecord(PDNS_RECORD dnsRecord, PDNS_RECORD results)
324 PDNS::SRVRecord * record = NULL;
326 if (
327 (dnsRecord->Flags.S.Section == DnsSectionAnswer) &&
328 (dnsRecord->wType == DNS_TYPE_SRV) &&
329 (strlen(dnsRecord->Data.SRV.pNameTarget) > 0) &&
330 (strcmp(dnsRecord->Data.SRV.pNameTarget, ".") != 0)
332 record = new SRVRecord();
333 record->hostName = PString(dnsRecord->Data.SRV.pNameTarget);
334 record->port = dnsRecord->Data.SRV.wPort;
335 record->priority = dnsRecord->Data.SRV.wPriority;
336 record->weight = dnsRecord->Data.SRV.wWeight;
338 // see if any A records match this hostname
339 PDNS_RECORD aRecord = results;
340 while (aRecord != NULL) {
341 if ((dnsRecord->Flags.S.Section == DnsSectionAddtional) && (dnsRecord->wType == DNS_TYPE_A)) {
342 record->hostAddress = PIPSocket::Address(dnsRecord->Data.A.IpAddress);
343 break;
345 aRecord = aRecord->pNext;
348 // if no A record found, then get address the hard way
349 if (aRecord == NULL)
350 PIPSocket::GetHostAddress(record->hostName, record->hostAddress);
353 return record;
356 void PDNS::SRVRecordList::PrintOn(ostream & strm) const
358 PINDEX i;
359 for (i = 0; i < GetSize(); i++)
360 strm << (*this)[i] << endl;
363 PDNS::SRVRecord * PDNS::SRVRecordList::GetFirst()
365 if (GetSize() == 0)
366 return NULL;
368 // create a list of all prioities, to save time
369 priPos = 0;
370 priList.SetSize(0);
372 PINDEX i;
373 if (GetSize() > 0) {
374 priList.SetSize(1);
375 WORD lastPri = (*this)[0].priority;
376 priList[0] = lastPri;
377 (*this)[0].used = FALSE;
378 for (i = 1; i < GetSize(); i++) {
379 (*this)[i].used = FALSE;
380 if ((*this)[i].priority != lastPri) {
381 priList.SetSize(priPos+1);
382 lastPri = (*this)[i].priority;
383 priList[priPos] = lastPri;
388 priPos = 0;
389 return GetNext();
392 PDNS::SRVRecord * PDNS::SRVRecordList::GetNext()
394 if (priList.GetSize() == 0)
395 return NULL;
397 while (priPos < priList.GetSize()) {
399 WORD currentPri = priList[priPos];
401 // find first record at current priority
402 PINDEX firstPos;
403 for (firstPos = 0; (firstPos < GetSize()) && ((*this)[firstPos].priority != currentPri); firstPos++)
405 if (firstPos == GetSize())
406 return NULL;
408 // calculate total of all unused weights at this priority
409 unsigned totalWeight = (*this)[firstPos].weight;
410 PINDEX i = firstPos + 1;
411 PINDEX count = 1;
412 while (i < GetSize() && ((*this)[i].priority == currentPri)) {
413 if (!(*this)[i].used) {
414 totalWeight += (*this)[i].weight;
415 count ++;
419 // if no matches found, go to the next priority level
420 if (count == 0) {
421 priPos++;
422 continue;
425 // selected the correct item
426 if (totalWeight > 0) {
427 unsigned targetWeight = PRandom::Number() % (totalWeight+1);
428 totalWeight = 0;
429 for (i = 0; i < GetSize() && ((*this)[i].priority == currentPri); i++) {
430 if (!(*this)[i].used) {
431 totalWeight += (*this)[i].weight;
432 if (totalWeight >= targetWeight) {
433 (*this)[i].used = TRUE;
434 return &(*this)[i];
440 // pick a random item at this priority
441 PINDEX j = firstPos + ((count == 0) ? 0 : (PRandom::Number() % count) );
442 count = 0;
443 for (i = 0; i < GetSize() && ((*this)[i].priority == currentPri); i++) {
444 if (!(*this)[i].used) {
445 if (count == j) {
446 (*this)[i].used = TRUE;
447 return &(*this)[i];
449 count++;
453 // go to the next priority level
454 priPos++;
457 return NULL;
460 BOOL PDNS::GetSRVRecords(
461 const PString & _service,
462 const PString & type,
463 const PString & domain,
464 PDNS::SRVRecordList & recordList
467 if (_service.IsEmpty())
468 return FALSE;
470 PStringStream service;
471 if (_service[0] != '_')
472 service << '_';
474 service << _service << "._" << type << '.' << domain;
476 return GetSRVRecords(service, recordList);
480 ///////////////////////////////////////////////////////
482 PObject::Comparison PDNS::MXRecord::Compare(const PObject & obj) const
484 const MXRecord * other = dynamic_cast<const MXRecord *>(&obj);
485 if (other == NULL)
486 return LessThan;
488 if (preference < other->preference)
489 return LessThan;
490 else if (preference > other->preference)
491 return GreaterThan;
493 return EqualTo;
496 void PDNS::MXRecord::PrintOn(ostream & strm) const
498 strm << "host=" << hostName << "(" << hostAddress << "), "
499 << "preference=" << preference;
502 ///////////////////////////////////////////////////////
504 PDNS::MXRecord * PDNS::MXRecordList::HandleDNSRecord(PDNS_RECORD dnsRecord, PDNS_RECORD results)
506 MXRecord * record = NULL;
508 if (
509 (dnsRecord->Flags.S.Section == DnsSectionAnswer) &&
510 (dnsRecord->wType == DNS_TYPE_MX) &&
511 (strlen(dnsRecord->Data.MX.pNameExchange) > 0)
513 record = new MXRecord();
514 record->hostName = PString(dnsRecord->Data.MX.pNameExchange);
515 record->preference = dnsRecord->Data.MX.wPreference;
517 // see if any A records match this hostname
518 PDNS_RECORD aRecord = results;
519 while (aRecord != NULL) {
520 if ((dnsRecord->Flags.S.Section == DnsSectionAddtional) && (dnsRecord->wType == DNS_TYPE_A)) {
521 record->hostAddress = PIPSocket::Address(dnsRecord->Data.A.IpAddress);
522 break;
524 aRecord = aRecord->pNext;
527 // if no A record found, then get address the hard way
528 if (aRecord == NULL)
529 PIPSocket::GetHostAddress(record->hostName, record->hostAddress);
532 return record;
535 void PDNS::MXRecordList::PrintOn(ostream & strm) const
537 PINDEX i;
538 for (i = 0; i < GetSize(); i++)
539 strm << (*this)[i] << endl;
542 PDNS::MXRecord * PDNS::MXRecordList::GetFirst()
544 PINDEX i;
545 for (i = 0; i < GetSize(); i++)
546 (*this)[i].used = FALSE;
548 lastIndex = 0;
550 return GetNext();
553 PDNS::MXRecord * PDNS::MXRecordList::GetNext()
555 if (GetSize() == 0)
556 return NULL;
558 if (lastIndex >= GetSize())
559 return NULL;
561 return (PDNS::MXRecord *)GetAt(lastIndex++);
564 #endif // P_DNS
567 // End Of File ///////////////////////////////////////////////////////////////