/
mDNS.c
executable file
·11456 lines (10308 loc) · 527 KB
/
mDNS.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* -*- Mode: C; tab-width: 4 -*-
*
* Copyright (c) 2002-2006 Apple Computer, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This code is completely 100% portable C. It does not depend on any external header files
* from outside the mDNS project -- all the types it expects to find are defined right here.
*
* The previous point is very important: This file does not depend on any external
* header files. It should compile on *any* platform that has a C compiler, without
* making *any* assumptions about availability of so-called "standard" C functions,
* routines, or types (which may or may not be present on any given platform).
* Formatting notes:
* This code follows the "Whitesmiths style" C indentation rules. Plenty of discussion
* on C indentation can be found on the web, such as <http://www.kafejo.com/komp/1tbs.htm>,
* but for the sake of brevity here I will say just this: Curly braces are not syntactially
* part of an "if" statement; they are the beginning and ending markers of a compound statement;
* therefore common sense dictates that if they are part of a compound statement then they
* should be indented to the same level as everything else in that compound statement.
* Indenting curly braces at the same level as the "if" implies that curly braces are
* part of the "if", which is false. (This is as misleading as people who write "char* x,y;"
* thinking that variables x and y are both of type "char*" -- and anyone who doesn't
* understand why variable y is not of type "char*" just proves the point that poor code
* layout leads people to unfortunate misunderstandings about how the C language really works.)
*/
#include "DNSCommon.h" // Defines general DNS untility routines
#include "uDNS.h" // Defines entry points into unicast-specific routines
// Disable certain benign warnings with Microsoft compilers
#if(defined(_MSC_VER))
// Disable "conditional expression is constant" warning for debug macros.
// Otherwise, this generates warnings for the perfectly natural construct "while(1)"
// If someone knows a variant way of writing "while(1)" that doesn't generate warning messages, please let us know
#pragma warning(disable:4127)
// Disable "assignment within conditional expression".
// Other compilers understand the convention that if you place the assignment expression within an extra pair
// of parentheses, this signals to the compiler that you really intended an assignment and no warning is necessary.
// The Microsoft compiler doesn't understand this convention, so in the absense of any other way to signal
// to the compiler that the assignment is intentional, we have to just turn this warning off completely.
#pragma warning(disable:4706)
#endif
#if APPLE_OSX_mDNSResponder
#include <WebFilterDNS/WebFilterDNS.h>
#if ! NO_WCF
WCFConnection *WCFConnectionNew(void) __attribute__((weak_import));
void WCFConnectionDealloc(WCFConnection* c) __attribute__((weak_import));
// Do we really need to define a macro for "if"?
#define CHECK_WCF_FUNCTION(X) if (X)
#endif // ! NO_WCF
#else
#define NO_WCF 1
#endif // APPLE_OSX_mDNSResponder
// Forward declarations
mDNSlocal void BeginSleepProcessing(mDNS *const m);
mDNSlocal void RetrySPSRegistrations(mDNS *const m);
mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password);
mDNSlocal mDNSBool CacheRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
mDNSlocal void mDNS_PurgeBeforeResolve(mDNS *const m, DNSQuestion *q);
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark - Program Constants
#endif
#define NO_HINFO 1
// Any records bigger than this are considered 'large' records
#define SmallRecordLimit 1024
#define kMaxUpdateCredits 10
#define kUpdateCreditRefreshInterval (mDNSPlatformOneSecond * 6)
mDNSexport const char *const mDNS_DomainTypeNames[] =
{
"b._dns-sd._udp.", // Browse
"db._dns-sd._udp.", // Default Browse
"lb._dns-sd._udp.", // Automatic Browse
"r._dns-sd._udp.", // Registration
"dr._dns-sd._udp." // Default Registration
};
#ifdef UNICAST_DISABLED
#define uDNS_IsActiveQuery(q, u) mDNSfalse
#endif
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - General Utility Functions
#endif
// If there is a authoritative LocalOnly record that answers questions of type A, AAAA and CNAME
// this returns true. Main use is to handle /etc/hosts records.
#define LORecordAnswersAddressType(rr) ((rr)->ARType == AuthRecordLocalOnly && \
(rr)->resrec.RecordType & kDNSRecordTypeUniqueMask && \
((rr)->resrec.rrtype == kDNSType_A || (rr)->resrec.rrtype == kDNSType_AAAA || \
(rr)->resrec.rrtype == kDNSType_CNAME))
#define FollowCNAME(q, rr, AddRecord) (AddRecord && (q)->qtype != kDNSType_CNAME && \
(rr)->RecordType != kDNSRecordTypePacketNegative && \
(rr)->rrtype == kDNSType_CNAME)
mDNSlocal void SetNextQueryStopTime(mDNS *const m, const DNSQuestion *const q)
{
if (m->mDNS_busy != m->mDNS_reentrancy+1)
LogMsg("SetNextQueryTime: Lock not held! mDNS_busy (%ld) mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy);
#if ForceAlerts
if (m->mDNS_busy != m->mDNS_reentrancy+1) *(long*)0 = 0;
#endif
if (m->NextScheduledStopTime - q->StopTime > 0)
m->NextScheduledStopTime = q->StopTime;
}
mDNSexport void SetNextQueryTime(mDNS *const m, const DNSQuestion *const q)
{
if (m->mDNS_busy != m->mDNS_reentrancy+1)
LogMsg("SetNextQueryTime: Lock not held! mDNS_busy (%ld) mDNS_reentrancy (%ld)", m->mDNS_busy, m->mDNS_reentrancy);
#if ForceAlerts
if (m->mDNS_busy != m->mDNS_reentrancy+1) *(long*)0 = 0;
#endif
if (ActiveQuestion(q))
{
// Depending on whether this is a multicast or unicast question we want to set either:
// m->NextScheduledQuery = NextQSendTime(q) or
// m->NextuDNSEvent = NextQSendTime(q)
mDNSs32 *const timer = mDNSOpaque16IsZero(q->TargetQID) ? &m->NextScheduledQuery : &m->NextuDNSEvent;
if (*timer - NextQSendTime(q) > 0)
*timer = NextQSendTime(q);
}
}
mDNSlocal void ReleaseAuthEntity(AuthHash *r, AuthEntity *e)
{
#if APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING >= 1
unsigned int i;
for (i=0; i<sizeof(*e); i++) ((char*)e)[i] = 0xFF;
#endif
e->next = r->rrauth_free;
r->rrauth_free = e;
r->rrauth_totalused--;
}
mDNSlocal void ReleaseAuthGroup(AuthHash *r, AuthGroup **cp)
{
AuthEntity *e = (AuthEntity *)(*cp);
LogMsg("ReleaseAuthGroup: Releasing AuthGroup %##s", (*cp)->name->c);
if ((*cp)->rrauth_tail != &(*cp)->members)
LogMsg("ERROR: (*cp)->members == mDNSNULL but (*cp)->rrauth_tail != &(*cp)->members)");
if ((*cp)->name != (domainname*)((*cp)->namestorage)) mDNSPlatformMemFree((*cp)->name);
(*cp)->name = mDNSNULL;
*cp = (*cp)->next; // Cut record from list
ReleaseAuthEntity(r, e);
}
mDNSlocal AuthEntity *GetAuthEntity(AuthHash *r, const AuthGroup *const PreserveAG)
{
AuthEntity *e = mDNSNULL;
if (r->rrauth_lock) { LogMsg("GetFreeCacheRR ERROR! Cache already locked!"); return(mDNSNULL); }
r->rrauth_lock = 1;
if (!r->rrauth_free)
{
// We allocate just one AuthEntity at a time because we need to be able
// free them all individually which normally happens when we parse /etc/hosts into
// AuthHash where we add the "new" entries and discard (free) the already added
// entries. If we allocate as chunks, we can't free them individually.
AuthEntity *storage = mDNSPlatformMemAllocate(sizeof(AuthEntity));
storage->next = mDNSNULL;
r->rrauth_free = storage;
}
// If we still have no free records, recycle all the records we can.
// Enumerating the entire auth is moderately expensive, so when we do it, we reclaim all the records we can in one pass.
if (!r->rrauth_free)
{
mDNSu32 oldtotalused = r->rrauth_totalused;
mDNSu32 slot;
for (slot = 0; slot < AUTH_HASH_SLOTS; slot++)
{
AuthGroup **cp = &r->rrauth_hash[slot];
while (*cp)
{
if ((*cp)->members || (*cp)==PreserveAG) cp=&(*cp)->next;
else ReleaseAuthGroup(r, cp);
}
}
LogInfo("GetAuthEntity: Recycled %d records to reduce auth cache from %d to %d",
oldtotalused - r->rrauth_totalused, oldtotalused, r->rrauth_totalused);
}
if (r->rrauth_free) // If there are records in the free list, take one
{
e = r->rrauth_free;
r->rrauth_free = e->next;
if (++r->rrauth_totalused >= r->rrauth_report)
{
LogInfo("RR Auth now using %ld objects", r->rrauth_totalused);
if (r->rrauth_report < 100) r->rrauth_report += 10;
else if (r->rrauth_report < 1000) r->rrauth_report += 100;
else r->rrauth_report += 1000;
}
mDNSPlatformMemZero(e, sizeof(*e));
}
r->rrauth_lock = 0;
return(e);
}
mDNSexport AuthGroup *AuthGroupForName(AuthHash *r, const mDNSu32 slot, const mDNSu32 namehash, const domainname *const name)
{
AuthGroup *ag;
for (ag = r->rrauth_hash[slot]; ag; ag=ag->next)
if (ag->namehash == namehash && SameDomainName(ag->name, name))
break;
return(ag);
}
mDNSexport AuthGroup *AuthGroupForRecord(AuthHash *r, const mDNSu32 slot, const ResourceRecord *const rr)
{
return(AuthGroupForName(r, slot, rr->namehash, rr->name));
}
mDNSlocal AuthGroup *GetAuthGroup(AuthHash *r, const mDNSu32 slot, const ResourceRecord *const rr)
{
mDNSu16 namelen = DomainNameLength(rr->name);
AuthGroup *ag = (AuthGroup*)GetAuthEntity(r, mDNSNULL);
if (!ag) { LogMsg("GetAuthGroup: Failed to allocate memory for %##s", rr->name->c); return(mDNSNULL); }
ag->next = r->rrauth_hash[slot];
ag->namehash = rr->namehash;
ag->members = mDNSNULL;
ag->rrauth_tail = &ag->members;
ag->name = (domainname*)ag->namestorage;
ag->NewLocalOnlyRecords = mDNSNULL;
if (namelen > InlineCacheGroupNameSize) ag->name = mDNSPlatformMemAllocate(namelen);
if (!ag->name)
{
LogMsg("GetAuthGroup: Failed to allocate name storage for %##s", rr->name->c);
ReleaseAuthEntity(r, (AuthEntity*)ag);
return(mDNSNULL);
}
AssignDomainName(ag->name, rr->name);
if (AuthGroupForRecord(r, slot, rr)) LogMsg("GetAuthGroup: Already have AuthGroup for %##s", rr->name->c);
r->rrauth_hash[slot] = ag;
if (AuthGroupForRecord(r, slot, rr) != ag) LogMsg("GetAuthGroup: Not finding AuthGroup for %##s", rr->name->c);
return(ag);
}
// Returns the AuthGroup in which the AuthRecord was inserted
mDNSexport AuthGroup *InsertAuthRecord(mDNS *const m, AuthHash *r, AuthRecord *rr)
{
AuthGroup *ag;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
ag = AuthGroupForRecord(r, slot, &rr->resrec);
if (!ag) ag = GetAuthGroup(r, slot, &rr->resrec); // If we don't have a AuthGroup for this name, make one now
if (ag)
{
LogInfo("InsertAuthRecord: inserting auth record %s from table", ARDisplayString(m, rr));
*(ag->rrauth_tail) = rr; // Append this record to tail of cache slot list
ag->rrauth_tail = &(rr->next); // Advance tail pointer
}
return ag;
}
mDNSexport AuthGroup *RemoveAuthRecord(mDNS *const m, AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) { LogMsg("RemoveAuthRecord: ERROR!! AuthGroup not found for %s", ARDisplayString(m, rr)); return mDNSNULL; }
rp = &(*ag)->members;
while (*rp)
{
if (*rp != rr)
rp=&(*rp)->next;
else
{
// We don't break here, so that we can set the tail below without tracking "prev" pointers
LogInfo("RemoveAuthRecord: removing auth record %s from table", ARDisplayString(m, rr));
*rp = (*rp)->next; // Cut record from list
}
}
// TBD: If there are no more members, release authgroup ?
(*ag)->rrauth_tail = rp;
return a;
}
mDNSexport CacheGroup *CacheGroupForName(const mDNS *const m, const mDNSu32 slot, const mDNSu32 namehash, const domainname *const name)
{
CacheGroup *cg;
for (cg = m->rrcache_hash[slot]; cg; cg=cg->next)
if (cg->namehash == namehash && SameDomainName(cg->name, name))
break;
return(cg);
}
mDNSlocal CacheGroup *CacheGroupForRecord(const mDNS *const m, const mDNSu32 slot, const ResourceRecord *const rr)
{
return(CacheGroupForName(m, slot, rr->namehash, rr->name));
}
mDNSexport mDNSBool mDNS_AddressIsLocalSubnet(mDNS *const m, const mDNSInterfaceID InterfaceID, const mDNSAddr *addr)
{
NetworkInterfaceInfo *intf;
if (addr->type == mDNSAddrType_IPv4)
{
// Normally we resist touching the NotAnInteger fields, but here we're doing tricky bitwise masking so we make an exception
if (mDNSv4AddressIsLinkLocal(&addr->ip.v4)) return(mDNStrue);
for (intf = m->HostInterfaces; intf; intf = intf->next)
if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx)
if (((intf->ip.ip.v4.NotAnInteger ^ addr->ip.v4.NotAnInteger) & intf->mask.ip.v4.NotAnInteger) == 0)
return(mDNStrue);
}
if (addr->type == mDNSAddrType_IPv6)
{
if (mDNSv6AddressIsLinkLocal(&addr->ip.v4)) return(mDNStrue);
for (intf = m->HostInterfaces; intf; intf = intf->next)
if (intf->ip.type == addr->type && intf->InterfaceID == InterfaceID && intf->McastTxRx)
if ((((intf->ip.ip.v6.l[0] ^ addr->ip.v6.l[0]) & intf->mask.ip.v6.l[0]) == 0) &&
(((intf->ip.ip.v6.l[1] ^ addr->ip.v6.l[1]) & intf->mask.ip.v6.l[1]) == 0) &&
(((intf->ip.ip.v6.l[2] ^ addr->ip.v6.l[2]) & intf->mask.ip.v6.l[2]) == 0) &&
(((intf->ip.ip.v6.l[3] ^ addr->ip.v6.l[3]) & intf->mask.ip.v6.l[3]) == 0))
return(mDNStrue);
}
return(mDNSfalse);
}
mDNSlocal NetworkInterfaceInfo *FirstInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
{
NetworkInterfaceInfo *intf = m->HostInterfaces;
while (intf && intf->InterfaceID != InterfaceID) intf = intf->next;
return(intf);
}
mDNSexport char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
{
NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
return(intf ? intf->ifname : mDNSNULL);
}
// Caller should hold the lock
mDNSlocal void GenerateNegativeResponse(mDNS *const m)
{
DNSQuestion *q;
if (!m->CurrentQuestion) { LogMsg("GenerateNegativeResponse: ERROR!! CurrentQuestion not set"); return; }
q = m->CurrentQuestion;
LogInfo("GenerateNegativeResponse: Generating negative response for question %##s (%s)", q->qname.c, DNSTypeName(q->qtype));
MakeNegativeCacheRecord(m, &m->rec.r, &q->qname, q->qnamehash, q->qtype, q->qclass, 60, mDNSInterface_Any, mDNSNULL);
AnswerCurrentQuestionWithResourceRecord(m, &m->rec.r, QC_addnocache);
if (m->CurrentQuestion == q) { q->ThisQInterval = 0; } // Deactivate this question
// Don't touch the question after this
m->rec.r.resrec.RecordType = 0; // Clear RecordType to show we're not still using it
}
mDNSlocal void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, ResourceRecord *rr)
{
const mDNSBool selfref = SameDomainName(&q->qname, &rr->rdata->u.name);
if (q->CNAMEReferrals >= 10 || selfref)
LogMsg("AnswerQuestionByFollowingCNAME: %p %##s (%s) NOT following CNAME referral %d%s for %s",
q, q->qname.c, DNSTypeName(q->qtype), q->CNAMEReferrals, selfref ? " (Self-Referential)" : "", RRDisplayString(m, rr));
else
{
const mDNSu32 c = q->CNAMEReferrals + 1; // Stash a copy of the new q->CNAMEReferrals value
// The SameDomainName check above is to ignore bogus CNAME records that point right back at
// themselves. Without that check we can get into a case where we have two duplicate questions,
// A and B, and when we stop question A, UpdateQuestionDuplicates copies the value of CNAMEReferrals
// from A to B, and then A is re-appended to the end of the list as a duplicate of B (because
// the target name is still the same), and then when we stop question B, UpdateQuestionDuplicates
// copies the B's value of CNAMEReferrals back to A, and we end up not incrementing CNAMEReferrals
// for either of them. This is not a problem for CNAME loops of two or more records because in
// those cases the newly re-appended question A has a different target name and therefore cannot be
// a duplicate of any other question ('B') which was itself a duplicate of the previous question A.
// Right now we just stop and re-use the existing query. If we really wanted to be 100% perfect,
// and track CNAMEs coming and going, we should really create a subordinate query here,
// which we would subsequently cancel and retract if the CNAME referral record were removed.
// In reality this is such a corner case we'll ignore it until someone actually needs it.
LogInfo("AnswerQuestionByFollowingCNAME: %p %##s (%s) following CNAME referral %d for %s",
q, q->qname.c, DNSTypeName(q->qtype), q->CNAMEReferrals, RRDisplayString(m, rr));
mDNS_StopQuery_internal(m, q); // Stop old query
AssignDomainName(&q->qname, &rr->rdata->u.name); // Update qname
q->qnamehash = DomainNameHashValue(&q->qname); // and namehash
// If a unicast query results in a CNAME that points to a .local, we need to re-try
// this as unicast. Setting the mDNSInterface_Unicast tells mDNS_StartQuery_internal
// to try this as unicast query even though it is a .local name
if (!mDNSOpaque16IsZero(q->TargetQID) && IsLocalDomain(&q->qname))
{
LogInfo("AnswerQuestionByFollowingCNAME: Resolving a .local CNAME %p %##s (%s) Record %s",
q, q->qname.c, DNSTypeName(q->qtype), RRDisplayString(m, rr));
q->InterfaceID = mDNSInterface_Unicast;
}
mDNS_StartQuery_internal(m, q); // start new query
// Record how many times we've done this. We need to do this *after* mDNS_StartQuery_internal,
// because mDNS_StartQuery_internal re-initializes CNAMEReferrals to zero
q->CNAMEReferrals = c;
}
}
// For a single given DNSQuestion pointed to by CurrentQuestion, deliver an add/remove result for the single given AuthRecord
// Note: All the callers should use the m->CurrentQuestion to see if the question is still valid or not
mDNSlocal void AnswerLocalQuestionWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
DNSQuestion *q = m->CurrentQuestion;
mDNSBool followcname;
if (!q)
{
LogMsg("AnswerLocalQuestionWithLocalAuthRecord: ERROR!! CurrentQuestion NULL while answering with %s", ARDisplayString(m, rr));
return;
}
followcname = FollowCNAME(q, &rr->resrec, AddRecord);
// We should not be delivering results for record types Unregistered, Deregistering, and (unverified) Unique
if (!(rr->resrec.RecordType & kDNSRecordTypeActiveMask))
{
LogMsg("AnswerLocalQuestionWithLocalAuthRecord: *NOT* delivering %s event for local record type %X %s",
AddRecord ? "Add" : "Rmv", rr->resrec.RecordType, ARDisplayString(m, rr));
return;
}
// Indicate that we've given at least one positive answer for this record, so we should be prepared to send a goodbye for it
if (AddRecord) rr->AnsweredLocalQ = mDNStrue;
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
if (q->QuestionCallback && !q->NoAnswer)
{
q->CurrentAnswers += AddRecord ? 1 : -1;
if (LORecordAnswersAddressType(rr))
{
if (!followcname || q->ReturnIntermed)
{
// Don't send this packet on the wire as we answered from /etc/hosts
q->ThisQInterval = 0;
q->LOAddressAnswers += AddRecord ? 1 : -1;
q->QuestionCallback(m, q, &rr->resrec, AddRecord);
}
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
// The callback above could have caused the question to stop. Detect that
// using m->CurrentQuestion
if (followcname && m->CurrentQuestion == q)
AnswerQuestionByFollowingCNAME(m, q, &rr->resrec);
return;
}
else
q->QuestionCallback(m, q, &rr->resrec, AddRecord);
}
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
}
mDNSlocal void AnswerInterfaceAnyQuestionsWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
if (m->CurrentQuestion)
LogMsg("AnswerInterfaceAnyQuestionsWithLocalAuthRecord: ERROR m->CurrentQuestion already set: %##s (%s)",
m->CurrentQuestion->qname.c, DNSTypeName(m->CurrentQuestion->qtype));
m->CurrentQuestion = m->Questions;
while (m->CurrentQuestion && m->CurrentQuestion != m->NewQuestions)
{
mDNSBool answered;
DNSQuestion *q = m->CurrentQuestion;
if (RRAny(rr))
answered = ResourceRecordAnswersQuestion(&rr->resrec, q);
else
answered = LocalOnlyRecordAnswersQuestion(rr, q);
if (answered)
AnswerLocalQuestionWithLocalAuthRecord(m, rr, AddRecord); // MUST NOT dereference q again
if (m->CurrentQuestion == q) // If m->CurrentQuestion was not auto-advanced, do it ourselves now
m->CurrentQuestion = q->next;
}
m->CurrentQuestion = mDNSNULL;
}
// When a new local AuthRecord is created or deleted, AnswerAllLocalQuestionsWithLocalAuthRecord()
// delivers the appropriate add/remove events to listening questions:
// 1. It runs though all our LocalOnlyQuestions delivering answers as appropriate,
// stopping if it reaches a NewLocalOnlyQuestion -- brand-new questions are handled by AnswerNewLocalOnlyQuestion().
// 2. If the AuthRecord is marked mDNSInterface_LocalOnly or mDNSInterface_P2P, then it also runs though
// our main question list, delivering answers to mDNSInterface_Any questions as appropriate,
// stopping if it reaches a NewQuestion -- brand-new questions are handled by AnswerNewQuestion().
//
// AnswerAllLocalQuestionsWithLocalAuthRecord is used by the m->NewLocalRecords loop in mDNS_Execute(),
// and by mDNS_Deregister_internal()
mDNSlocal void AnswerAllLocalQuestionsWithLocalAuthRecord(mDNS *const m, AuthRecord *rr, QC_result AddRecord)
{
if (m->CurrentQuestion)
LogMsg("AnswerAllLocalQuestionsWithLocalAuthRecord ERROR m->CurrentQuestion already set: %##s (%s)",
m->CurrentQuestion->qname.c, DNSTypeName(m->CurrentQuestion->qtype));
m->CurrentQuestion = m->LocalOnlyQuestions;
while (m->CurrentQuestion && m->CurrentQuestion != m->NewLocalOnlyQuestions)
{
mDNSBool answered;
DNSQuestion *q = m->CurrentQuestion;
// We are called with both LocalOnly/P2P record or a regular AuthRecord
if (RRAny(rr))
answered = ResourceRecordAnswersQuestion(&rr->resrec, q);
else
answered = LocalOnlyRecordAnswersQuestion(rr, q);
if (answered)
AnswerLocalQuestionWithLocalAuthRecord(m, rr, AddRecord); // MUST NOT dereference q again
if (m->CurrentQuestion == q) // If m->CurrentQuestion was not auto-advanced, do it ourselves now
m->CurrentQuestion = q->next;
}
m->CurrentQuestion = mDNSNULL;
// If this AuthRecord is marked LocalOnly or P2P, then we want to deliver it to all local 'mDNSInterface_Any' questions
if (rr->ARType == AuthRecordLocalOnly || rr->ARType == AuthRecordP2P)
AnswerInterfaceAnyQuestionsWithLocalAuthRecord(m, rr, AddRecord);
}
// ***************************************************************************
#if COMPILER_LIKES_PRAGMA_MARK
#pragma mark -
#pragma mark - Resource Record Utility Functions
#endif
#define RRTypeIsAddressType(T) ((T) == kDNSType_A || (T) == kDNSType_AAAA)
#define ResourceRecordIsValidAnswer(RR) ( ((RR)-> resrec.RecordType & kDNSRecordTypeActiveMask) && \
((RR)->Additional1 == mDNSNULL || ((RR)->Additional1->resrec.RecordType & kDNSRecordTypeActiveMask)) && \
((RR)->Additional2 == mDNSNULL || ((RR)->Additional2->resrec.RecordType & kDNSRecordTypeActiveMask)) && \
((RR)->DependentOn == mDNSNULL || ((RR)->DependentOn->resrec.RecordType & kDNSRecordTypeActiveMask)) )
#define ResourceRecordIsValidInterfaceAnswer(RR, INTID) \
(ResourceRecordIsValidAnswer(RR) && \
((RR)->resrec.InterfaceID == mDNSInterface_Any || (RR)->resrec.InterfaceID == (INTID)))
#define DefaultProbeCountForTypeUnique ((mDNSu8)3)
#define DefaultProbeCountForRecordType(X) ((X) == kDNSRecordTypeUnique ? DefaultProbeCountForTypeUnique : (mDNSu8)0)
#define InitialAnnounceCount ((mDNSu8)8)
// For goodbye packets we set the count to 3, and for wakeups we set it to 18
// (which will be up to 15 wakeup attempts over the course of 30 seconds,
// and then if the machine fails to wake, 3 goodbye packets).
#define GoodbyeCount ((mDNSu8)3)
#define WakeupCount ((mDNSu8)18)
// Number of wakeups we send if WakeOnResolve is set in the question
#define InitialWakeOnResolveCount ((mDNSu8)3)
// Note that the announce intervals use exponential backoff, doubling each time. The probe intervals do not.
// This means that because the announce interval is doubled after sending the first packet, the first
// observed on-the-wire inter-packet interval between announcements is actually one second.
// The half-second value here may be thought of as a conceptual (non-existent) half-second delay *before* the first packet is sent.
#define DefaultProbeIntervalForTypeUnique (mDNSPlatformOneSecond/4)
#define DefaultAnnounceIntervalForTypeShared (mDNSPlatformOneSecond/2)
#define DefaultAnnounceIntervalForTypeUnique (mDNSPlatformOneSecond/2)
#define DefaultAPIntervalForRecordType(X) ((X) & kDNSRecordTypeActiveSharedMask ? DefaultAnnounceIntervalForTypeShared : \
(X) & kDNSRecordTypeUnique ? DefaultProbeIntervalForTypeUnique : \
(X) & kDNSRecordTypeActiveUniqueMask ? DefaultAnnounceIntervalForTypeUnique : 0)
#define TimeToAnnounceThisRecord(RR,time) ((RR)->AnnounceCount && (time) - ((RR)->LastAPTime + (RR)->ThisAPInterval) >= 0)
#define TimeToSendThisRecord(RR,time) ((TimeToAnnounceThisRecord(RR,time) || (RR)->ImmedAnswer) && ResourceRecordIsValidAnswer(RR))
#define TicksTTL(RR) ((mDNSs32)(RR)->resrec.rroriginalttl * mDNSPlatformOneSecond)
#define RRExpireTime(RR) ((RR)->TimeRcvd + TicksTTL(RR))
#define MaxUnansweredQueries 4
// SameResourceRecordSignature returns true if two resources records have the same name, type, and class, and may be sent
// (or were received) on the same interface (i.e. if *both* records specify an interface, then it has to match).
// TTL and rdata may differ.
// This is used for cache flush management:
// When sending a unique record, all other records matching "SameResourceRecordSignature" must also be sent
// When receiving a unique record, all old cache records matching "SameResourceRecordSignature" are flushed
// SameResourceRecordNameClassInterface is functionally the same as SameResourceRecordSignature, except rrtype does not have to match
#define SameResourceRecordSignature(A,B) (A)->resrec.rrtype == (B)->resrec.rrtype && SameResourceRecordNameClassInterface((A),(B))
mDNSlocal mDNSBool SameResourceRecordNameClassInterface(const AuthRecord *const r1, const AuthRecord *const r2)
{
if (!r1) { LogMsg("SameResourceRecordSignature ERROR: r1 is NULL"); return(mDNSfalse); }
if (!r2) { LogMsg("SameResourceRecordSignature ERROR: r2 is NULL"); return(mDNSfalse); }
if (r1->resrec.InterfaceID &&
r2->resrec.InterfaceID &&
r1->resrec.InterfaceID != r2->resrec.InterfaceID) return(mDNSfalse);
return(mDNSBool)(
r1->resrec.rrclass == r2->resrec.rrclass &&
r1->resrec.namehash == r2->resrec.namehash &&
SameDomainName(r1->resrec.name, r2->resrec.name));
}
// PacketRRMatchesSignature behaves as SameResourceRecordSignature, except that types may differ if our
// authoratative record is unique (as opposed to shared). For unique records, we are supposed to have
// complete ownership of *all* types for this name, so *any* record type with the same name is a conflict.
// In addition, when probing we send our questions with the wildcard type kDNSQType_ANY,
// so a response of any type should match, even if it is not actually the type the client plans to use.
// For now, to make it easier to avoid false conflicts, we treat SPS Proxy records like shared records,
// and require the rrtypes to match for the rdata to be considered potentially conflicting
mDNSlocal mDNSBool PacketRRMatchesSignature(const CacheRecord *const pktrr, const AuthRecord *const authrr)
{
if (!pktrr) { LogMsg("PacketRRMatchesSignature ERROR: pktrr is NULL"); return(mDNSfalse); }
if (!authrr) { LogMsg("PacketRRMatchesSignature ERROR: authrr is NULL"); return(mDNSfalse); }
if (pktrr->resrec.InterfaceID &&
authrr->resrec.InterfaceID &&
pktrr->resrec.InterfaceID != authrr->resrec.InterfaceID) return(mDNSfalse);
if (!(authrr->resrec.RecordType & kDNSRecordTypeUniqueMask) || authrr->WakeUp.HMAC.l[0])
if (pktrr->resrec.rrtype != authrr->resrec.rrtype) return(mDNSfalse);
return(mDNSBool)(
pktrr->resrec.rrclass == authrr->resrec.rrclass &&
pktrr->resrec.namehash == authrr->resrec.namehash &&
SameDomainName(pktrr->resrec.name, authrr->resrec.name));
}
// CacheRecord *ka is the CacheRecord from the known answer list in the query.
// This is the information that the requester believes to be correct.
// AuthRecord *rr is the answer we are proposing to give, if not suppressed.
// This is the information that we believe to be correct.
// We've already determined that we plan to give this answer on this interface
// (either the record is non-specific, or it is specific to this interface)
// so now we just need to check the name, type, class, rdata and TTL.
mDNSlocal mDNSBool ShouldSuppressKnownAnswer(const CacheRecord *const ka, const AuthRecord *const rr)
{
// If RR signature is different, or data is different, then don't suppress our answer
if (!IdenticalResourceRecord(&ka->resrec, &rr->resrec)) return(mDNSfalse);
// If the requester's indicated TTL is less than half the real TTL,
// we need to give our answer before the requester's copy expires.
// If the requester's indicated TTL is at least half the real TTL,
// then we can suppress our answer this time.
// If the requester's indicated TTL is greater than the TTL we believe,
// then that's okay, and we don't need to do anything about it.
// (If two responders on the network are offering the same information,
// that's okay, and if they are offering the information with different TTLs,
// the one offering the lower TTL should defer to the one offering the higher TTL.)
return(mDNSBool)(ka->resrec.rroriginalttl >= rr->resrec.rroriginalttl / 2);
}
mDNSlocal void SetNextAnnounceProbeTime(mDNS *const m, const AuthRecord *const rr)
{
if (rr->resrec.RecordType == kDNSRecordTypeUnique)
{
if ((rr->LastAPTime + rr->ThisAPInterval) - m->timenow > mDNSPlatformOneSecond * 10)
{
LogMsg("SetNextAnnounceProbeTime: ProbeCount %d Next in %d %s", rr->ProbeCount, (rr->LastAPTime + rr->ThisAPInterval) - m->timenow, ARDisplayString(m, rr));
LogMsg("SetNextAnnounceProbeTime: m->SuppressProbes %d m->timenow %d diff %d", m->SuppressProbes, m->timenow, m->SuppressProbes - m->timenow);
}
if (m->NextScheduledProbe - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextScheduledProbe = (rr->LastAPTime + rr->ThisAPInterval);
// Some defensive code:
// If (rr->LastAPTime + rr->ThisAPInterval) happens to be far in the past, we don't want to allow
// NextScheduledProbe to be set excessively in the past, because that can cause bad things to happen.
// See: <rdar://problem/7795434> mDNS: Sometimes advertising stops working and record interval is set to zero
if (m->NextScheduledProbe - m->timenow < 0)
m->NextScheduledProbe = m->timenow;
}
else if (rr->AnnounceCount && (ResourceRecordIsValidAnswer(rr) || rr->resrec.RecordType == kDNSRecordTypeDeregistering))
{
if (m->NextScheduledResponse - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextScheduledResponse = (rr->LastAPTime + rr->ThisAPInterval);
}
}
mDNSlocal void InitializeLastAPTime(mDNS *const m, AuthRecord *const rr)
{
// For reverse-mapping Sleep Proxy PTR records, probe interval is one second
rr->ThisAPInterval = rr->AddressProxy.type ? mDNSPlatformOneSecond : DefaultAPIntervalForRecordType(rr->resrec.RecordType);
// * If this is a record type that's going to probe, then we use the m->SuppressProbes time.
// * Otherwise, if it's not going to probe, but m->SuppressProbes is set because we have other
// records that are going to probe, then we delay its first announcement so that it will
// go out synchronized with the first announcement for the other records that *are* probing.
// This is a minor performance tweak that helps keep groups of related records synchronized together.
// The addition of "interval / 2" is to make sure that, in the event that any of the probes are
// delayed by a few milliseconds, this announcement does not inadvertently go out *before* the probing is complete.
// When the probing is complete and those records begin to announce, these records will also be picked up and accelerated,
// because they will meet the criterion of being at least half-way to their scheduled announcement time.
// * If it's not going to probe and m->SuppressProbes is not already set then we should announce immediately.
if (rr->ProbeCount)
{
// If we have no probe suppression time set, or it is in the past, set it now
if (m->SuppressProbes == 0 || m->SuppressProbes - m->timenow < 0)
{
// To allow us to aggregate probes when a group of services are registered together,
// the first probe is delayed 1/4 second. This means the common-case behaviour is:
// 1/4 second wait; probe
// 1/4 second wait; probe
// 1/4 second wait; probe
// 1/4 second wait; announce (i.e. service is normally announced exactly one second after being registered)
m->SuppressProbes = NonZeroTime(m->timenow + DefaultProbeIntervalForTypeUnique/2 + mDNSRandom(DefaultProbeIntervalForTypeUnique/2));
// If we already have a *probe* scheduled to go out sooner, then use that time to get better aggregation
if (m->SuppressProbes - m->NextScheduledProbe >= 0)
m->SuppressProbes = NonZeroTime(m->NextScheduledProbe);
if (m->SuppressProbes - m->timenow < 0) // Make sure we don't set m->SuppressProbes excessively in the past
m->SuppressProbes = m->timenow;
// If we already have a *query* scheduled to go out sooner, then use that time to get better aggregation
if (m->SuppressProbes - m->NextScheduledQuery >= 0)
m->SuppressProbes = NonZeroTime(m->NextScheduledQuery);
if (m->SuppressProbes - m->timenow < 0) // Make sure we don't set m->SuppressProbes excessively in the past
m->SuppressProbes = m->timenow;
// except... don't expect to be able to send before the m->SuppressSending timer fires
if (m->SuppressSending && m->SuppressProbes - m->SuppressSending < 0)
m->SuppressProbes = NonZeroTime(m->SuppressSending);
if (m->SuppressProbes - m->timenow > mDNSPlatformOneSecond * 8)
{
LogMsg("InitializeLastAPTime ERROR m->SuppressProbes %d m->NextScheduledProbe %d m->NextScheduledQuery %d m->SuppressSending %d %d",
m->SuppressProbes - m->timenow,
m->NextScheduledProbe - m->timenow,
m->NextScheduledQuery - m->timenow,
m->SuppressSending,
m->SuppressSending - m->timenow);
m->SuppressProbes = NonZeroTime(m->timenow + DefaultProbeIntervalForTypeUnique/2 + mDNSRandom(DefaultProbeIntervalForTypeUnique/2));
}
}
rr->LastAPTime = m->SuppressProbes - rr->ThisAPInterval;
}
else if (m->SuppressProbes && m->SuppressProbes - m->timenow >= 0)
rr->LastAPTime = m->SuppressProbes - rr->ThisAPInterval + DefaultProbeIntervalForTypeUnique * DefaultProbeCountForTypeUnique + rr->ThisAPInterval / 2;
else
rr->LastAPTime = m->timenow - rr->ThisAPInterval;
// For reverse-mapping Sleep Proxy PTR records we don't want to start probing instantly -- we
// wait one second to give the client a chance to go to sleep, and then start our ARP/NDP probing.
// After three probes one second apart with no answer, we conclude the client is now sleeping
// and we can begin broadcasting our announcements to take over ownership of that IP address.
// If we don't wait for the client to go to sleep, then when the client sees our ARP Announcements there's a risk
// (depending on the OS and networking stack it's using) that it might interpret it as a conflict and change its IP address.
if (rr->AddressProxy.type) rr->LastAPTime = m->timenow;
// Unsolicited Neighbor Advertisements (RFC 2461 Section 7.2.6) give us fast address cache updating,
// but some older IPv6 clients get confused by them, so for now we don't send them. Without Unsolicited
// Neighbor Advertisements we have to rely on Neighbor Unreachability Detection instead, which is slower.
// Given this, we'll do our best to wake for existing IPv6 connections, but we don't want to encourage
// new ones for sleeping clients, so we'll we send deletions for our SPS clients' AAAA records.
if (m->KnownBugs & mDNS_KnownBug_LimitedIPv6)
if (rr->WakeUp.HMAC.l[0] && rr->resrec.rrtype == kDNSType_AAAA)
rr->LastAPTime = m->timenow - rr->ThisAPInterval + mDNSPlatformOneSecond * 10;
// Set LastMCTime to now, to inhibit multicast responses
// (no need to send additional multicast responses when we're announcing anyway)
rr->LastMCTime = m->timenow;
rr->LastMCInterface = mDNSInterfaceMark;
SetNextAnnounceProbeTime(m, rr);
}
mDNSlocal const domainname *SetUnicastTargetToHostName(mDNS *const m, AuthRecord *rr)
{
const domainname *target;
if (rr->AutoTarget)
{
// For autotunnel services pointing at our IPv6 ULA we don't need or want a NAT mapping, but for all other
// advertised services referencing our uDNS hostname, we want NAT mappings automatically created as appropriate,
// with the port number in our advertised SRV record automatically tracking the external mapped port.
DomainAuthInfo *AuthInfo = GetAuthInfoForName_internal(m, rr->resrec.name);
if (!AuthInfo || !AuthInfo->AutoTunnel) rr->AutoTarget = Target_AutoHostAndNATMAP;
}
target = GetServiceTarget(m, rr);
if (!target || target->c[0] == 0)
{
// defer registration until we've got a target
LogInfo("SetUnicastTargetToHostName No target for %s", ARDisplayString(m, rr));
rr->state = regState_NoTarget;
return mDNSNULL;
}
else
{
LogInfo("SetUnicastTargetToHostName target %##s for resource record %s", target->c, ARDisplayString(m,rr));
return target;
}
}
// Right now this only applies to mDNS (.local) services where the target host is always m->MulticastHostname
// Eventually we should unify this with GetServiceTarget() in uDNS.c
mDNSlocal void SetTargetToHostName(mDNS *const m, AuthRecord *const rr)
{
domainname *const target = GetRRDomainNameTarget(&rr->resrec);
const domainname *newname = &m->MulticastHostname;
if (!target) LogInfo("SetTargetToHostName: Don't know how to set the target of rrtype %s", DNSTypeName(rr->resrec.rrtype));
if (!(rr->ForceMCast || rr->ARType == AuthRecordLocalOnly || rr->ARType == AuthRecordP2P || IsLocalDomain(&rr->namestorage)))
{
const domainname *const n = SetUnicastTargetToHostName(m, rr);
if (n) newname = n;
else { target->c[0] = 0; SetNewRData(&rr->resrec, mDNSNULL, 0); return; }
}
if (target && SameDomainName(target, newname))
debugf("SetTargetToHostName: Target of %##s is already %##s", rr->resrec.name->c, target->c);
if (target && !SameDomainName(target, newname))
{
AssignDomainName(target, newname);
SetNewRData(&rr->resrec, mDNSNULL, 0); // Update rdlength, rdestimate, rdatahash
// If we're in the middle of probing this record, we need to start again,
// because changing its rdata may change the outcome of the tie-breaker.
// (If the record type is kDNSRecordTypeUnique (unconfirmed unique) then DefaultProbeCountForRecordType is non-zero.)
rr->ProbeCount = DefaultProbeCountForRecordType(rr->resrec.RecordType);
// If we've announced this record, we really should send a goodbye packet for the old rdata before
// changing to the new rdata. However, in practice, we only do SetTargetToHostName for unique records,
// so when we announce them we'll set the kDNSClass_UniqueRRSet and clear any stale data that way.
if (rr->RequireGoodbye && rr->resrec.RecordType == kDNSRecordTypeShared)
debugf("Have announced shared record %##s (%s) at least once: should have sent a goodbye packet before updating",
rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
rr->AnnounceCount = InitialAnnounceCount;
rr->RequireGoodbye = mDNSfalse;
InitializeLastAPTime(m, rr);
}
}
mDNSlocal void AcknowledgeRecord(mDNS *const m, AuthRecord *const rr)
{
if (rr->RecordCallback)
{
// CAUTION: MUST NOT do anything more with rr after calling rr->Callback(), because the client's callback function
// is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc.
rr->Acknowledged = mDNStrue;
mDNS_DropLockBeforeCallback(); // Allow client to legally make mDNS API calls from the callback
rr->RecordCallback(m, rr, mStatus_NoError);
mDNS_ReclaimLockAfterCallback(); // Decrement mDNS_reentrancy to block mDNS API calls again
}
}
mDNSexport void ActivateUnicastRegistration(mDNS *const m, AuthRecord *const rr)
{
// Make sure that we don't activate the SRV record and associated service records, if it is in
// NoTarget state. First time when a service is being instantiated, SRV record may be in NoTarget state.
// We should not activate any of the other reords (PTR, TXT) that are part of the service. When
// the target becomes available, the records will be reregistered.
if (rr->resrec.rrtype != kDNSType_SRV)
{
AuthRecord *srvRR = mDNSNULL;
if (rr->resrec.rrtype == kDNSType_PTR)
srvRR = rr->Additional1;
else if (rr->resrec.rrtype == kDNSType_TXT)
srvRR = rr->DependentOn;
if (srvRR)
{
if (srvRR->resrec.rrtype != kDNSType_SRV)
{
LogMsg("ActivateUnicastRegistration: ERROR!! Resource record %s wrong, expecting SRV type", ARDisplayString(m, srvRR));
}
else
{
LogInfo("ActivateUnicastRegistration: Found Service Record %s in state %d for %##s (%s)",
ARDisplayString(m, srvRR), srvRR->state, rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
rr->state = srvRR->state;
}
}
}
if (rr->state == regState_NoTarget)
{
LogInfo("ActivateUnicastRegistration record %s in regState_NoTarget, not activating", ARDisplayString(m, rr));
return;
}
// When we wake up from sleep, we call ActivateUnicastRegistration. It is possible that just before we went to sleep,
// the service/record was being deregistered. In that case, we should not try to register again. For the cases where
// the records are deregistered due to e.g., no target for the SRV record, we would have returned from above if it
// was already in NoTarget state. If it was in the process of deregistration but did not complete fully before we went
// to sleep, then it is okay to start in Pending state as we will go back to NoTarget state if we don't have a target.
if (rr->resrec.RecordType == kDNSRecordTypeDeregistering)
{
LogInfo("ActivateUnicastRegistration: Resource record %s, current state %d, moving to DeregPending", ARDisplayString(m, rr), rr->state);
rr->state = regState_DeregPending;
}
else
{
LogInfo("ActivateUnicastRegistration: Resource record %s, current state %d, moving to Pending", ARDisplayString(m, rr), rr->state);
rr->state = regState_Pending;
}
rr->ProbeCount = 0;
rr->AnnounceCount = 0;
rr->ThisAPInterval = INIT_RECORD_REG_INTERVAL;
rr->LastAPTime = m->timenow - rr->ThisAPInterval;
rr->expire = 0; // Forget about all the leases, start fresh
rr->uselease = mDNStrue;
rr->updateid = zeroID;
rr->SRVChanged = mDNSfalse;
rr->updateError = mStatus_NoError;
// RestartRecordGetZoneData calls this function whenever a new interface gets registered with core.
// The records might already be registered with the server and hence could have NAT state.
if (rr->NATinfo.clientContext)
{
mDNS_StopNATOperation_internal(m, &rr->NATinfo);
rr->NATinfo.clientContext = mDNSNULL;
}
if (rr->nta) { CancelGetZoneData(m, rr->nta); rr->nta = mDNSNULL; }
if (rr->tcp) { DisposeTCPConn(rr->tcp); rr->tcp = mDNSNULL; }
if (m->NextuDNSEvent - (rr->LastAPTime + rr->ThisAPInterval) >= 0)
m->NextuDNSEvent = (rr->LastAPTime + rr->ThisAPInterval);
}
// Two records qualify to be local duplicates if:
// (a) the RecordTypes are the same, or
// (b) one is Unique and the other Verified
// (c) either is in the process of deregistering
#define RecordLDT(A,B) ((A)->resrec.RecordType == (B)->resrec.RecordType || \
((A)->resrec.RecordType | (B)->resrec.RecordType) == (kDNSRecordTypeUnique | kDNSRecordTypeVerified) || \
((A)->resrec.RecordType == kDNSRecordTypeDeregistering || (B)->resrec.RecordType == kDNSRecordTypeDeregistering))
#define RecordIsLocalDuplicate(A,B) \
((A)->resrec.InterfaceID == (B)->resrec.InterfaceID && RecordLDT((A),(B)) && IdenticalResourceRecord(&(A)->resrec, &(B)->resrec))
mDNSlocal AuthRecord *CheckAuthIdenticalRecord(AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) return mDNSNULL;
rp = &(*ag)->members;
while (*rp)
{
if (!RecordIsLocalDuplicate(*rp, rr))
rp=&(*rp)->next;
else
{
if ((*rp)->resrec.RecordType == kDNSRecordTypeDeregistering)
{
(*rp)->AnnounceCount = 0;
rp=&(*rp)->next;
}
else return *rp;
}
}
return (mDNSNULL);
}
mDNSlocal mDNSBool CheckAuthRecordConflict(AuthHash *r, AuthRecord *rr)
{
AuthGroup *a;
AuthGroup **ag = &a;
AuthRecord **rp;
const mDNSu32 slot = AuthHashSlot(rr->resrec.name);
a = AuthGroupForRecord(r, slot, &rr->resrec);
if (!a) return mDNSfalse;
rp = &(*ag)->members;
while (*rp)
{
const AuthRecord *s1 = rr->RRSet ? rr->RRSet : rr;
const AuthRecord *s2 = (*rp)->RRSet ? (*rp)->RRSet : *rp;
if (s1 != s2 && SameResourceRecordSignature((*rp), rr) && !IdenticalSameNameRecord(&(*rp)->resrec, &rr->resrec))
return mDNStrue;
else
rp=&(*rp)->next;
}
return (mDNSfalse);
}
// checks to see if "rr" is already present