source: rtems-libbsd/mDNSResponder/mDNSCore/anonymous.c @ 406a2f4

4.115-freebsd-12freebsd-9.3
Last change on this file since 406a2f4 was 406a2f4, checked in by Sebastian Huber <sebastian.huber@…>, on Jan 23, 2014 at 1:59:37 PM

mDNS: AnonInfoAnswersQuestion?: Fix stack usage

The mDNSexport is normally defined to be empty, thus mDNSStorage is a
huge stack variable (roughly 50KiB) with random data.

Asuming a global mDNSStorage variable in the mDNSCore makes no sense.

  • Property mode set to 100644
File size: 19.2 KB
Line 
1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2012 Apple Computer, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18#include "mDNSEmbeddedAPI.h"
19#include "CryptoAlg.h"
20#include "anonymous.h"
21#include "DNSCommon.h"
22
23// Define ANONYMOUS_DISABLED to remove all the anonymous functionality
24// and use the stub functions implemented later in this file.
25
26#ifndef ANONYMOUS_DISABLED
27
28#define ANON_NSEC3_ITERATIONS        1
29
30mDNSlocal mDNSBool InitializeNSEC3Record(ResourceRecord *rr, const mDNSu8 *AnonData, int len, mDNSu32 salt)
31{
32    const mDNSu8 *ptr;
33    rdataNSEC3 *nsec3 = (rdataNSEC3 *)rr->rdata->u.data;
34    mDNSu8 *tmp, *nxt;
35    unsigned short iter = ANON_NSEC3_ITERATIONS;
36    int hlen;
37    const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
38
39    // Construct the RDATA first and construct the owner name based on that.
40    ptr = (const mDNSu8 *)&salt;
41    debugf("InitializeNSEC3Record: %x%x%x%x, name %##s", ptr[0], ptr[1], ptr[2], ptr[3], rr->name->c);
42
43    // Set the RDATA
44    nsec3->alg = SHA1_DIGEST_TYPE;
45    nsec3->flags = 0;
46    nsec3->iterations = swap16(iter);
47    nsec3->saltLength = 4;
48    tmp = (mDNSu8 *)&nsec3->salt;
49    *tmp++ = ptr[0];
50    *tmp++ = ptr[1];
51    *tmp++ = ptr[2];
52    *tmp++ = ptr[3];
53
54    // hashLength, nxt, bitmap
55    *tmp++ = SHA1_HASH_LENGTH;    // hash length
56    nxt = tmp;
57    tmp += SHA1_HASH_LENGTH;
58    *tmp++ = 0; // window number
59    *tmp++ = NSEC_MCAST_WINDOW_SIZE; // window length
60    mDNSPlatformMemZero(tmp, NSEC_MCAST_WINDOW_SIZE);
61    tmp[kDNSType_PTR >> 3] |= 128 >> (kDNSType_PTR & 7);
62
63    // Hash the base service name + salt + AnonData
64    if (!NSEC3HashName(rr->name, nsec3, AnonData, len, hashName, &hlen))
65    {
66        LogMsg("InitializeNSEC3Record: NSEC3HashName failed for ##s", rr->name->c);
67        return mDNSfalse;
68    }
69    if (hlen != SHA1_HASH_LENGTH)
70    {
71        LogMsg("InitializeNSEC3Record: hlen wrong %d", hlen);
72        return mDNSfalse;
73    }
74    mDNSPlatformMemCopy(nxt, hashName, hlen);
75
76    return mDNStrue;
77}
78
79mDNSlocal ResourceRecord *ConstructNSEC3Record(const domainname *service, const mDNSu8 *AnonData, int len, mDNSu32 salt)
80{
81    ResourceRecord *rr;
82    int dlen;
83    domainname *name;
84
85    // We are just allocating an RData which has StandardAuthRDSize
86    if (StandardAuthRDSize < MCAST_NSEC3_RDLENGTH)
87    {
88        LogMsg("ConstructNSEC3Record: StandardAuthRDSize %d smaller than MCAST_NSEC3_RDLENGTH %d", StandardAuthRDSize, MCAST_NSEC3_RDLENGTH);
89        return mDNSNULL;
90    }
91
92    dlen = DomainNameLength(service);
93 
94    // Allocate space for the name and RData.
95    rr = mDNSPlatformMemAllocate(sizeof(ResourceRecord) + dlen + sizeof(RData));
96    if (!rr)
97        return mDNSNULL;
98    name = (domainname *)((mDNSu8 *)rr + sizeof(ResourceRecord));
99    rr->RecordType        = kDNSRecordTypePacketAuth;
100    rr->InterfaceID       = mDNSInterface_Any;
101    rr->name              = (const domainname *)name;
102    rr->rrtype            = kDNSType_NSEC3;
103    rr->rrclass           = kDNSClass_IN;
104    rr->rroriginalttl     = kStandardTTL;
105    rr->rDNSServer        = mDNSNULL;
106    rr->rdlength          = MCAST_NSEC3_RDLENGTH;
107    rr->rdestimate        = MCAST_NSEC3_RDLENGTH;
108    rr->rdata             = (RData *)((mDNSu8 *)rr->name + dlen);
109
110    AssignDomainName(name, service);
111    if (!InitializeNSEC3Record(rr, AnonData, len, salt))
112    {
113        mDNSPlatformMemFree(rr);
114        return mDNSNULL;
115    }
116    return rr;
117}
118
119mDNSlocal ResourceRecord *CopyNSEC3ResourceRecord(AnonymousInfo *si, const ResourceRecord *rr)
120{
121    int len;
122    domainname *name;
123    ResourceRecord *nsec3rr;
124
125    if (rr->rdlength < MCAST_NSEC3_RDLENGTH)
126    {
127        LogMsg("CopyNSEC3ResourceRecord: rdlength %d smaller than MCAST_NSEC3_RDLENGTH %d", rr->rdlength, MCAST_NSEC3_RDLENGTH);
128        return mDNSNULL;
129    }
130    // Allocate space for the name and the rdata along with the ResourceRecord
131    len = DomainNameLength(rr->name);
132    nsec3rr = mDNSPlatformMemAllocate(sizeof(ResourceRecord) + len + sizeof(RData));
133    if (!nsec3rr)
134        return mDNSNULL;
135
136    *nsec3rr = *rr;
137    name = (domainname *)((mDNSu8 *)nsec3rr + sizeof(ResourceRecord));
138    nsec3rr->name = (const domainname *)name;
139    AssignDomainName(name, rr->name);
140
141    nsec3rr->rdata = (RData *)((mDNSu8 *)nsec3rr->name + len);
142    mDNSPlatformMemCopy(nsec3rr->rdata->u.data, rr->rdata->u.data, rr->rdlength);
143
144    si->nsec3RR = nsec3rr;
145
146    return nsec3rr;
147}
148
149// When a service is started or a browse is started with the Anonymous data, we allocate a new random
150// number and based on that allocate a new NSEC3 resource record whose hash is a function of random number (salt) and
151// the anonymous data.
152//
153// If we receive a packet with the NSEC3 option, we need to cache that along with the resource record so that we can
154// check against the question to see whether it answers them or not. In that case, we pass the "rr" that we received.
155mDNSexport  AnonymousInfo *AllocateAnonInfo(const domainname *service, const mDNSu8 *data, int len, const ResourceRecord *rr)
156{
157    AnonymousInfo *ai;
158    ai = (AnonymousInfo *)mDNSPlatformMemAllocate(sizeof(AnonymousInfo));
159    if (!ai)
160    {
161        return mDNSNULL;
162    }
163    mDNSPlatformMemZero(ai, sizeof(AnonymousInfo));
164    if (rr)
165    {
166        if (!CopyNSEC3ResourceRecord(ai, rr))
167        {
168            mDNSPlatformMemFree(ai);
169            return mDNSNULL;
170        }
171        return ai;
172    }
173    ai->salt = mDNSRandom(0xFFFFFFFF);
174    ai->AnonData = mDNSPlatformMemAllocate(len);
175    if (!ai->AnonData)
176    {
177        mDNSPlatformMemFree(ai);
178        return mDNSNULL;
179    }
180    ai->AnonDataLen = len;
181    mDNSPlatformMemCopy(ai->AnonData, data, len);
182    ai->nsec3RR = ConstructNSEC3Record(service, data, len, ai->salt);
183    if (!ai->nsec3RR)
184    {
185        mDNSPlatformMemFree(ai);
186        return mDNSNULL;
187    }
188    return ai;
189}
190
191mDNSexport void FreeAnonInfo(AnonymousInfo *ai)
192{
193    if (ai->nsec3RR)
194        mDNSPlatformMemFree(ai->nsec3RR);
195    if (ai->AnonData)
196        mDNSPlatformMemFree(ai->AnonData);
197    mDNSPlatformMemFree(ai);
198}
199
200mDNSexport void ReInitAnonInfo(AnonymousInfo **AnonInfo, const domainname *name)
201{
202    if (*AnonInfo)
203    {
204        AnonymousInfo *ai = *AnonInfo;
205        *AnonInfo = AllocateAnonInfo(name, ai->AnonData, ai->AnonDataLen, mDNSNULL);
206        if (!(*AnonInfo))
207            *AnonInfo = ai;
208        else
209            FreeAnonInfo(ai);
210    }
211}
212
213// This function should be used only if you know that the question and
214// the resource record belongs to the same set. The main usage is
215// in ProcessQuery where we find the question to be part of the same
216// set as the resource record, but it needs the AnonData to be
217// initialized so that it can walk the cache records to see if they
218// answer the question.
219mDNSexport void SetAnonData(DNSQuestion *q, ResourceRecord *rr, mDNSBool ForQuestion)
220{
221    if (!q->AnonInfo || !rr->AnonInfo)
222    {
223        LogMsg("SetAnonData: question %##s(%p), rr %##s(%p), NULL", q->qname.c, q->AnonInfo, rr->name->c, rr->AnonInfo);
224        return;
225    }
226   
227    debugf("SetAnonData: question %##s(%p), rr %##s(%p)", q->qname.c, q->AnonInfo, rr->name->c, rr->AnonInfo);
228    if (ForQuestion)
229    {
230        if (!q->AnonInfo->AnonData)
231        {
232            q->AnonInfo->AnonData = mDNSPlatformMemAllocate(rr->AnonInfo->AnonDataLen);
233            if (!q->AnonInfo->AnonData)
234                return;
235        }
236        mDNSPlatformMemCopy(q->AnonInfo->AnonData, rr->AnonInfo->AnonData, rr->AnonInfo->AnonDataLen);
237        q->AnonInfo->AnonDataLen = rr->AnonInfo->AnonDataLen;
238    }
239    else
240    {
241        if (!rr->AnonInfo->AnonData)
242        {
243            rr->AnonInfo->AnonData = mDNSPlatformMemAllocate(q->AnonInfo->AnonDataLen);
244            if (!rr->AnonInfo->AnonData)
245                return;
246        }
247        mDNSPlatformMemCopy(rr->AnonInfo->AnonData, q->AnonInfo->AnonData, q->AnonInfo->AnonDataLen);
248        rr->AnonInfo->AnonDataLen = q->AnonInfo->AnonDataLen;
249    }
250}
251
252mDNSlocal char *RRDisplayStringBuf(const ResourceRecord *const rr, char *const buffer)
253{
254    return GetRRDisplayString_rdb(rr, &rr->rdata->u, buffer);
255}
256
257// returns -1 if the caller should ignore the result
258// returns 1 if the record answers the question
259// returns 0 if the record does not answer the question
260mDNSexport int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q)
261{
262    char MsgBuffer[MaxMsg];             // Temp storage used while building error log messages
263    ResourceRecord *nsec3RR;
264    int i;
265    AnonymousInfo *qai, *rai;
266    mDNSu8 *AnonData;
267    int AnonDataLen;
268    rdataNSEC3 *nsec3;
269    int hlen;
270    const mDNSu8 hashName[NSEC3_MAX_HASH_LEN];
271    int nxtLength;
272    mDNSu8 *nxtName;
273
274    debugf("AnonInfoAnswersQuestion: question qname %##s", q->qname.c);
275
276    // Currently only PTR records can have anonymous information
277    if (q->qtype != kDNSType_PTR)
278    {
279        return -1;
280    }
281
282    // We allow anonymous questions to be answered by both normal services (without the
283    // anonymous information) and anonymous services that are part of the same set. And
284    // normal questions discover normal services and all anonymous services.
285    //
286    // The three cases have been enumerated clearly even though they all behave the
287    // same way.
288    if (!q->AnonInfo)
289    {
290        debugf("AnonInfoAnswersQuestion: not a anonymous type question");
291        if (!rr->AnonInfo)
292        {
293            // case 1
294            return -1;
295        }
296        else
297        {
298            // case 2
299            debugf("AnonInfoAnswersQuestion: Question %##s not answered using anonymous record %##s", q->qname.c, rr->name->c);
300            return -1;
301        }
302    }
303    else
304    {
305        // case 3
306        if (!rr->AnonInfo)
307        {
308            debugf("AnonInfoAnswersQuestion: not a anonymous type record");
309            return -1;
310        }
311    }
312
313    // case 4: We have the anonymous information both in the question and the record. We need
314    // two sets of information to validate.
315    //
316    // 1) Anonymous data that identifies the set/group
317    // 2) NSEC3 record that contains the hash and the salt
318    //
319    // If the question is a remote one, it does not have the anonymous information to validate (just
320    // the NSEC3 record) and hence the anonymous data should come from the local resource record. If the
321    // question is local, it can come from either of them and if there is a mismatch between the
322    // question and record, it won't validate.
323
324    qai = q->AnonInfo;
325    rai = rr->AnonInfo;
326
327    if (qai->AnonData && rai->AnonData)
328    {
329        // Before a cache record is created, if there is a matching question i.e., part
330        // of the same set, then when the cache is created we also set the anonymous
331        // information. Otherwise, the cache record contains just the NSEC3 record and we
332        // won't be here for that case.
333        //
334        // It is also possible that a local question is matched against the local AuthRecord
335        // as that is also the case for which the AnonData would be non-NULL for both.
336        // We match questions against AuthRecords (rather than the cache) for LocalOnly case and
337        // to see whether a .local query should be suppressed or not. The latter never happens
338        // because PTR queries are never suppressed.
339
340        // If they don't belong to the same anonymous set, then no point in validating.
341        if ((qai->AnonDataLen != rai->AnonDataLen) ||
342            mDNSPlatformMemCmp(qai->AnonData, rai->AnonData, qai->AnonDataLen) != 0)
343        {
344            debugf("AnonInfoAnswersQuestion: AnonData mis-match for record  %s question %##s ",
345                RRDisplayStringBuf(rr, MsgBuffer), q->qname.c);
346            return 0;
347        }
348        // AnonData matches i.e they belong to the same group and the same service.
349        LogInfo("AnonInfoAnswersQuestion: Answering qname %##s, rname %##s, without validation", q->qname.c,
350            rr->name->c);
351        return 1;
352    }
353    else
354    {
355        debugf("AnonInfoAnswersQuestion: question %p, record %p", qai->AnonData, rai->AnonData);
356    }
357
358    if (qai->AnonData)
359    {
360        // If there is AnonData, then this is a local question. The
361        // NSEC3 RR comes from the resource record which could be part
362        // of the cache or local auth record. The cache entry could
363        // be from a remote host or created when we heard our own
364        // announcements. In any case, we use that to see if it matches
365        // the question.
366        AnonData = qai->AnonData;
367        AnonDataLen = qai->AnonDataLen;
368        nsec3RR = rai->nsec3RR;
369    }
370    else
371    {
372        // Remote question or hearing our own question back
373        AnonData = rai->AnonData;
374        AnonDataLen = rai->AnonDataLen;
375        nsec3RR = qai->nsec3RR;
376    }
377
378    if (!AnonData || !nsec3RR)
379    {
380        // AnonData can be NULL for the cache entry and if we are hearing our own question back, AnonData is NULL for
381        // that too and we can end up here for that case.
382        debugf("AnonInfoAnswersQuestion: AnonData %p or nsec3RR %p, NULL for question %##s, record %s", AnonData, nsec3RR,
383            q->qname.c, RRDisplayStringBuf(rr, MsgBuffer));
384        return 0;
385    }
386    debugf("AnonInfoAnswersQuestion: Validating question %##s, ResourceRecord %s", q->qname.c, RRDisplayStringBuf(nsec3RR, MsgBuffer));
387
388
389    nsec3 = (rdataNSEC3 *)nsec3RR->rdata->u.data;
390
391    if (!NSEC3HashName(nsec3RR->name, nsec3, AnonData, AnonDataLen, hashName, &hlen))
392    {
393        LogMsg("AnonInfoAnswersQuestion: NSEC3HashName failed for ##s", nsec3RR->name->c);
394        return mDNSfalse;
395    }
396    if (hlen != SHA1_HASH_LENGTH)
397    {
398        LogMsg("AnonInfoAnswersQuestion: hlen wrong %d", hlen);
399        return mDNSfalse;
400    }
401
402    NSEC3Parse(nsec3RR, mDNSNULL, &nxtLength, &nxtName, mDNSNULL, mDNSNULL);
403
404    if (hlen != nxtLength)
405    {
406        LogMsg("AnonInfoAnswersQuestion: ERROR!! hlen %d not same as nxtLength %d", hlen, nxtLength);
407        return mDNSfalse;
408    }
409
410    for (i = 0; i < nxtLength; i++)
411    {
412        if (nxtName[i] != hashName[i])
413        {
414            debugf("AnonInfoAnswersQuestion: mismatch output %x, digest %x, i %d", nxtName[i+1], hashName[i], i);
415            return 0;
416        }
417    }
418    LogInfo("AnonInfoAnswersQuestion: ResourceRecord %s matched question %##s (%s)", RRDisplayStringBuf(nsec3RR, MsgBuffer), q->qname.c, DNSTypeName(q->qtype));
419    return 1;
420}
421
422// Find a matching NSEC3 record for the name. We parse the questions and the records in the packet in order.
423// Similarly we also parse the NSEC3 records in order and this mapping to the questions and records
424// respectively.
425mDNSlocal CacheRecord *FindMatchingNSEC3ForName(mDNS *const m, CacheRecord **nsec3, const domainname *name)
426{
427    CacheRecord *cr;
428    CacheRecord **prev = nsec3;
429   
430    (void) m;
431
432    for (cr = *nsec3; cr; cr = cr->next)
433    {
434        if (SameDomainName(cr->resrec.name, name))
435        {
436            debugf("FindMatchingNSEC3ForName: NSEC3 record %s matched %##s", CRDisplayString(m, cr), name->c);
437            *prev = cr->next;
438            cr->next = mDNSNULL;
439            return cr;
440        }
441        prev = &cr->next;
442    }
443    return mDNSNULL;
444}
445
446mDNSexport void InitializeAnonInfoForQuestion(mDNS *const m, CacheRecord **McastNSEC3Records, DNSQuestion *q)
447{
448    CacheRecord *nsec3CR;
449
450    if (q->qtype != kDNSType_PTR)
451        return;
452
453    nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, &q->qname);
454    if (nsec3CR)
455    {
456        q->AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec);
457        if (q->AnonInfo)
458        {
459            debugf("InitializeAnonInfoForQuestion: Found a matching NSEC3 record %s, for %##s (%s)",
460                RRDisplayString(m, q->AnonInfo->nsec3RR), q->qname.c, DNSTypeName(q->qtype));
461        }
462        ReleaseCacheRecord(m, nsec3CR);
463    }
464}
465
466mDNSexport void InitializeAnonInfoForCR(mDNS *const m, CacheRecord **McastNSEC3Records, CacheRecord *cr)
467{
468    CacheRecord *nsec3CR;
469
470    if (!(*McastNSEC3Records))
471        return;
472
473    // If already initialized or not a PTR type, we don't have to do anything
474    if (cr->resrec.AnonInfo || cr->resrec.rrtype != kDNSType_PTR)
475        return;
476
477    nsec3CR = FindMatchingNSEC3ForName(m, McastNSEC3Records, cr->resrec.name);
478    if (nsec3CR)
479    {
480        cr->resrec.AnonInfo = AllocateAnonInfo(mDNSNULL, mDNSNULL, 0, &nsec3CR->resrec);
481        if (cr->resrec.AnonInfo)
482        {
483            debugf("InitializeAnonInfoForCR: Found a matching NSEC3 record %s, for %##s (%s)",
484                RRDisplayString(m, cr->resrec.AnonInfo->nsec3RR), cr->resrec.name->c,
485                DNSTypeName(cr->resrec.rrtype));
486        }
487        ReleaseCacheRecord(m, nsec3CR);
488    }
489}
490
491mDNSexport mDNSBool IdenticalAnonInfo(AnonymousInfo *a1, AnonymousInfo *a2)
492{
493    // if a1 is NULL and a2 is not NULL AND vice-versa
494    // return false as there is a change.
495    if ((a1 != mDNSNULL) != (a2 != mDNSNULL))
496        return mDNSfalse;
497
498    // Both could be NULL or non-NULL
499    if (a1 && a2)
500    {
501        // The caller already verified that the owner name is the same.
502        // Check whether the RData is same.
503        if (!IdenticalSameNameRecord(a1->nsec3RR, a2->nsec3RR))
504        {
505            debugf("IdenticalAnonInfo: nsec3RR mismatch");
506            return mDNSfalse;
507        }
508    }
509    return mDNStrue;
510}
511
512mDNSexport void CopyAnonInfoForCR(mDNS *const m, CacheRecord *crto, CacheRecord *crfrom)
513{
514    AnonymousInfo *aifrom = crfrom->resrec.AnonInfo;
515    AnonymousInfo *aito = crto->resrec.AnonInfo;
516
517    (void) m;
518
519    if (!aifrom)
520        return;
521
522    if (aito)
523    {
524        crto->resrec.AnonInfo = aifrom;
525        FreeAnonInfo(aito);
526        crfrom->resrec.AnonInfo = mDNSNULL;
527    }
528    else
529    {
530        FreeAnonInfo(aifrom);
531        crfrom->resrec.AnonInfo = mDNSNULL;
532    }
533}
534
535#else // !ANONYMOUS_DISABLED
536
537mDNSexport void ReInitAnonInfo(AnonymousInfo **si, const domainname *name)
538{
539        (void)si;
540        (void)name;
541}
542
543mDNSexport AnonymousInfo * AllocateAnonInfo(const domainname *service, const mDNSu8 *AnonData, int len, const ResourceRecord *rr)
544{
545        (void)service;
546        (void)AnonData;
547        (void)len;
548        (void)rr;
549
550        return mDNSNULL;
551}
552
553mDNSexport void FreeAnonInfo(AnonymousInfo *ai)
554{
555        (void)ai;
556}
557
558mDNSexport void SetAnonData(DNSQuestion *q, ResourceRecord *rr, mDNSBool ForQuestion)
559{
560        (void)q;
561        (void)rr;
562        (void)ForQuestion;
563}
564
565mDNSexport int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q)
566{
567        (void)rr;
568        (void)q;
569
570        return mDNSfalse;
571}
572
573mDNSexport void InitializeAnonInfoForQuestion(mDNS *const m, CacheRecord **McastNSEC3Records, DNSQuestion *q)
574{
575        (void)m;
576        (void)McastNSEC3Records;
577        (void)q;
578}
579
580mDNSexport void InitializeAnonInfoForCR(mDNS *const m, CacheRecord **McastNSEC3Records, CacheRecord *cr)
581{
582        (void)m;
583        (void)McastNSEC3Records;
584        (void)cr;
585}
586
587mDNSexport void CopyAnonInfoForCR(mDNS *const m, CacheRecord *crto, CacheRecord *crfrom)
588{
589        (void)m;
590        (void)crto;
591        (void)crfrom;
592}
593
594mDNSexport mDNSBool IdenticalAnonInfo(AnonymousInfo *a1, AnonymousInfo *a2)
595{
596        (void)a1;
597        (void)a2;
598
599        return mDNStrue;
600}
601
602#endif // !ANONYMOUS_DISABLED
Note: See TracBrowser for help on using the repository browser.