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