Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
DnsResolver.cs
1using System;
3using System.IO;
4using System.Net;
5using System.Net.NetworkInformation;
7using System.Text;
8using System.Text.RegularExpressions;
9using System.Threading.Tasks;
10using Waher.Events;
17
19{
31 public static class DnsResolver
32 {
36 public const int DefaultDnsPort = 53;
37
38 private static readonly Regex arpanetHostName = new Regex(@"^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?([.][a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$", RegexOptions.Compiled | RegexOptions.Singleline);
39 private static readonly object synchObject = new object();
40 private static readonly Random rnd = new Random();
41 private static ushort nextId = 0;
42 private static DnsHttpsClient httpsClient = null;
43 private static DnsUdpClient udpClient = null;
44 private static bool networkChanged = false;
45 private static int nestingDepth = 0;
46
47 static DnsResolver()
48 {
49 try
50 {
51 NetworkChange.NetworkAddressChanged += (Sender, e) => networkChanged = true;
52 }
53 catch (Exception ex)
54 {
55 Log.Exception(ex);
56 }
57 }
58
62 public static Uri DnsOverHttpsUri
63 {
64 get => httpsClient?.Uri;
65 set
66 {
67 if (value is null)
68 {
69 httpsClient?.Dispose();
70 httpsClient = null;
71 }
72 else if (httpsClient is null)
73 httpsClient = new DnsHttpsClient(value);
74 else
75 httpsClient.Uri = value;
76 }
77 }
78
82 public static IPAddress[] DnsServerAddresses
83 {
84 get
85 {
86 List<IPAddress> Addresses = new List<IPAddress>();
87
88 NetworkInterface[] Interfaces = NetworkInterface.GetAllNetworkInterfaces();
89 foreach (NetworkInterface Interface in Interfaces)
90 {
91 if (Interface.OperationalStatus == OperationalStatus.Up)
92 {
93 foreach (IPAddress Address in Interface.GetIPProperties().DnsAddresses)
94 {
95 if (!Addresses.Contains(Address))
96 Addresses.Add(Address);
97 }
98 }
99 }
100
101 return Addresses.ToArray();
102 }
103 }
104
110 public static bool IsValidArpanetHostName(string HostName)
111 {
112 if (HostName.Length > 255)
113 return false;
114
115 Match M;
116
117 lock (arpanetHostName)
118 {
119 M = arpanetHostName.Match(HostName);
120 }
121
122 return (M.Success && M.Index == 0 && M.Length == HostName.Length);
123 }
124
128 internal static ushort NextID
129 {
130 get
131 {
132 lock (synchObject)
133 {
134 return nextId++;
135 }
136 }
137 }
138
147 public static Task<ResourceRecord[]> Resolve(string Name, QTYPE TYPE, QCLASS CLASS)
148 {
149 return Resolve(Name, TYPE, null, CLASS, null);
150 }
151
161 public static Task<ResourceRecord[]> Resolve(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
162 {
163 return Resolve(Name, TYPE, null, CLASS, Thread);
164 }
165
175 public static Task<ResourceRecord[]> Resolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS)
176 {
177 return Resolve(Name, TYPE, ExceptionType, CLASS, null);
178 }
179
189 public static async Task<ResourceRecord[]> Resolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS, ProfilerThread Thread)
190 {
191 ResourceRecord[] Result = await TryResolve(Name, TYPE, ExceptionType, CLASS, Thread)
192 ?? throw new GenericException("Unable to resolve DNS query: " + Name,
193 null, Name, null, null, null, null, null,
194 new KeyValuePair<string, object>("Name", Name),
195 new KeyValuePair<string, object>("TYPE", TYPE),
196 new KeyValuePair<string, object>("CLASS", CLASS));
197
198 return Result;
199 }
200
208 public static Task<ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, QCLASS CLASS)
209 {
210 return TryResolve(Name, TYPE, null, CLASS, null);
211 }
212
221 public static Task<ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
222 {
223 return TryResolve(Name, TYPE, null, CLASS, Thread);
224 }
225
234 public static Task<ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS)
235 {
236 return TryResolve(Name, TYPE, ExceptionType, CLASS, null);
237 }
238
248 public static async Task<ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS, ProfilerThread Thread)
249 {
250 LinkedList<KeyValuePair<string, IPEndPoint>> Backup = null;
251 TYPE? ExpectedType;
252 IPEndPoint Destination = null;
253 int Timeout = 5000; // Local timeout
254
255 if (Enum.TryParse(TYPE.ToString(), out TYPE T))
256 ExpectedType = T;
257 else
258 ExpectedType = null;
259
260 Thread?.NewState("Client");
261
262 lock (synchObject)
263 {
264 if (nestingDepth == 0 && networkChanged)
265 {
266 networkChanged = false;
267 udpClient?.Dispose();
268 udpClient = null;
269 }
270
271 if (udpClient is null)
272 udpClient = new DnsUdpClient();
273
274 nestingDepth++;
275 }
276
277 try
278 {
279 while (true)
280 {
281 Thread?.NewState("Query");
282
283 DnsResponse Response = await TryQuery(Name, TYPE, CLASS, Timeout, Destination, false, Thread);
284 string CName = null;
285
286 if (Response is null)
287 {
288 Destination = await NextDestination(Backup);
289 if (Destination is null)
290 return null;
291
292 continue; // Check an alternative
293 }
294
295 if (!(Response.Answer is null))
296 {
297 if (!ExpectedType.HasValue)
298 return Response.Answer;
299
300 foreach (ResourceRecord RR in Response.Answer)
301 {
302 if (RR.Type == ExpectedType.Value)
303 return Response.Answer;
304
305 if (CName is null && RR.Type == Enumerations.TYPE.CNAME && RR is CNAME CNAME)
306 CName = CNAME.Name2;
307 }
308
309 if (ExceptionType.HasValue)
310 {
311 foreach (ResourceRecord RR in Response.Answer)
312 {
313 if (RR.Type == ExceptionType.Value)
314 return Response.Answer;
315 }
316 }
317
318 if (!(CName is null))
319 {
320 Name = CName;
321 Backup = null;
322 continue;
323 }
324 }
325
326 if (!(Response.Authority is null))
327 {
328 foreach (ResourceRecord RR in Response.Authority)
329 {
330 if (RR is NS NS)
331 {
332 string Authority = NS.Name2;
333 IPAddress AuthorityAddress = null;
334
335 if (!(Response.Additional is null))
336 {
337 foreach (ResourceRecord RR2 in Response.Additional)
338 {
339 if (RR2 is A A)
340 {
341 AuthorityAddress = A.Address;
342 break;
343 }
344 else if (RR2 is AAAA AAAA)
345 {
346 AuthorityAddress = AAAA.Address;
347 break;
348 }
349 }
350 }
351
352 if (Backup is null)
353 Backup = new LinkedList<KeyValuePair<string, IPEndPoint>>();
354
355 if (AuthorityAddress is null)
356 Backup.AddLast(new KeyValuePair<string, IPEndPoint>(Authority, null));
357 else
358 Backup.AddLast(new KeyValuePair<string, IPEndPoint>(null, new IPEndPoint(AuthorityAddress, DefaultDnsPort)));
359 }
360 }
361 }
362
363 Destination = await NextDestination(Backup);
364 if (Destination is null)
365 return null;
366
367 Timeout = 5000;
368 }
369 }
370 catch (Exception)
371 {
372 return null;
373 }
374 finally
375 {
376 lock (synchObject)
377 {
378 nestingDepth--;
379 }
380 }
381 }
382
391 public static Task<DnsResponse> Query(string Name, QTYPE TYPE, QCLASS CLASS)
392 {
393 return Query(Name, TYPE, CLASS, null);
394 }
395
405 public static async Task<DnsResponse> Query(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
406 {
407 DnsResponse Result = await TryQuery(Name, TYPE, CLASS, Thread)
408 ?? throw new GenericException("Domain name not found: " + Name,
409 null, Name, null, null, null, null, null,
410 new KeyValuePair<string, object>("Name", Name),
411 new KeyValuePair<string, object>("TYPE", TYPE),
412 new KeyValuePair<string, object>("CLASS", CLASS));
413
414 return Result;
415 }
416
424 public static Task<DnsResponse> TryQuery(string Name, QTYPE TYPE, QCLASS CLASS)
425 {
426 return TryQuery(Name, TYPE, CLASS, null);
427 }
428
437 public static Task<DnsResponse> TryQuery(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
438 {
439 lock (synchObject)
440 {
441 if (nestingDepth == 0 && networkChanged)
442 {
443 networkChanged = false;
444 udpClient?.Dispose();
445 udpClient = null;
446 }
447
448 if (udpClient is null)
449 udpClient = new DnsUdpClient();
450
451 nestingDepth++;
452 }
453
454 try
455 {
456 return TryQuery(Name, TYPE, CLASS, 2000, null, true, Thread);
457 }
458 finally
459 {
460 lock (synchObject)
461 {
462 nestingDepth--;
463 }
464 }
465 }
466
467 private static async Task<DnsResponse> TryQuery(string Name, QTYPE TYPE, QCLASS CLASS, int Timeout, IPEndPoint Destination,
468 bool ReturnRaw, ProfilerThread Thread)
469 {
470 DnsResponse Response;
471 DnsMessage Message;
472
474 {
475 try
476 {
477 Thread?.NewState("Search");
478
479 IEnumerable<DnsResponse> Records = await Database.Find<DnsResponse>();
480
481 Response = await Database.FindFirstDeleteRest<DnsResponse>(new FilterAnd(
482 new FilterFieldEqualTo("Name", Name),
483 new FilterFieldEqualTo("Type", TYPE),
484 new FilterFieldEqualTo("Class", CLASS)));
485
486 if (!(Response is null) && (Response.Expires <= DateTime.Now || Response.Raw is null))
487 {
488 await Database.Delete(Response);
489 Response = null;
490 }
491 }
492 catch (Exception ex)
493 {
494 Thread?.Exception(ex);
495
496 // Some inconsistency in database. Clear collection to get fresh set of DNS entries.
497 await Database.Clear("DnsCache");
498 Response = null;
499 }
500 }
501 else
502 Response = null;
503
504 if (Response is null)
505 {
506 bool Save = true;
507 DnsClient Client;
508
509 if (Destination is null)
510 {
511 Client = httpsClient;
512 if (Client is null)
513 {
514 Client = udpClient;
515 Thread?.NewState("UDP");
516 }
517 else
518 Thread?.NewState("HTTPS");
519 }
520 else
521 {
522 Client = udpClient;
523 Thread?.NewState("UDP");
524 }
525
526 Client.Thread = Thread;
527
528 try
529 {
530 Message = await Client.SendRequestAsync(OpCode.Query, true, new Question[]
531 {
532 new Question(Name, TYPE, CLASS)
533 }, Destination, Timeout);
534
535 switch (Message.RCode)
536 {
537 case RCode.NXDomain:
538 if (ReturnRaw)
539 Save = false;
540 else
541 return null;
542 break;
543 }
544 }
545 catch (TimeoutException ex)
546 {
547 Thread?.Exception(ex);
548 Message = null;
549 }
550 finally
551 {
552 Client.Thread = null;
553 }
554
555 if (Message is null || Message.RCode != RCode.NoError)
556 {
557 if (ReturnRaw)
558 Save = false;
559 else
560 return null;
561 }
562
563 uint Ttl = 60 * 60 * 24 * 30; // Maximum TTL = 30 days
564
565 Response = new DnsResponse()
566 {
567 Name = Name,
568 Type = TYPE,
569 Class = CLASS,
570 Answer = CheckTtl(ref Ttl, Message?.Answer),
571 Authority = CheckTtl(ref Ttl, Message?.Authority),
572 Additional = CheckTtl(ref Ttl, Message?.Additional),
573 Raw = Message?.Binary,
574 Expires = DateTime.Now.AddSeconds(Ttl)
575 };
576
577 if (Save && Database.HasProvider)
578 {
579 Thread?.NewState("Store");
580 try
581 {
582 await Database.Insert(Response);
583 }
584 catch (Exception ex)
585 {
586 Log.Exception(ex);
587 }
588 }
589 }
590
591 return Response;
592 }
593
594 private static async Task<IPEndPoint> NextDestination(LinkedList<KeyValuePair<string, IPEndPoint>> Backup)
595 {
596 IPEndPoint Destination = null;
597
598 while (Destination is null && !(Backup?.First is null))
599 {
600 KeyValuePair<string, IPEndPoint> P = Backup.First.Value;
601 Backup.RemoveFirst();
602
603 Destination = P.Value;
604
605 if (Destination is null)
606 {
607 IPAddress[] Addresses;
608
609 try
610 {
611 Addresses = await LookupIP4Addresses(P.Key);
612 }
613 catch (Exception)
614 {
615 Addresses = null;
616 }
617
618 if (Addresses is null || Addresses.Length == 0)
619 {
620 try
621 {
622 Addresses = await LookupIP6Addresses(P.Key);
623 }
624 catch (Exception)
625 {
626 Addresses = null;
627 }
628 }
629
630 if (!(Addresses is null))
631 {
632 foreach (IPAddress Address in Addresses)
633 {
634 IPEndPoint EP = new IPEndPoint(Address, DefaultDnsPort);
635
636 if (Destination is null)
637 Destination = EP;
638 else
639 {
640 if (Backup is null)
641 Backup = new LinkedList<KeyValuePair<string, IPEndPoint>>();
642
643 Backup.AddLast(new KeyValuePair<string, IPEndPoint>(null, EP));
644 }
645 }
646 }
647 }
648 }
649
650 return Destination;
651 }
652
653 private static ResourceRecord[] CheckTtl(ref uint Ttl, ResourceRecord[] Records)
654 {
655 if (!(Records is null))
656 {
657 foreach (ResourceRecord RR in Records)
658 {
659 if (RR.Ttl < Ttl)
660 Ttl = RR.Ttl;
661 }
662 }
663
664 return Records;
665 }
666
674 public static Task<IPAddress[]> LookupIP4Addresses(string DomainName)
675 {
676 return LookupIP4Addresses(DomainName, false);
677 }
678
684 public static Task<IPAddress[]> TryLookupIP4Addresses(string DomainName)
685 {
686 return LookupIP4Addresses(DomainName, true);
687 }
688
689 private static async Task<IPAddress[]> LookupIP4Addresses(string DomainName, bool Try)
690 {
691 ResourceRecord[] Records;
692
693 if (Try)
694 {
695 Records = await TryResolve(DomainName, QTYPE.A, QCLASS.IN);
696 if (Records is null)
697 return null;
698 }
699 else
700 Records = await Resolve(DomainName, QTYPE.A, QCLASS.IN);
701
702 List<IPAddress> Result = new List<IPAddress>();
703
704 foreach (ResourceRecord RR in Records)
705 {
706 if (RR is A A)
707 Result.Add(A.Address);
708 }
709
710 return Result.ToArray();
711 }
712
720 public static Task<IPAddress[]> LookupIP6Addresses(string DomainName)
721 {
722 return LookupIP6Addresses(DomainName, false);
723 }
724
730 public static Task<IPAddress[]> TryLookupIP6Addresses(string DomainName)
731 {
732 return LookupIP6Addresses(DomainName, true);
733 }
734
735 private static async Task<IPAddress[]> LookupIP6Addresses(string DomainName, bool Try)
736 {
737 ResourceRecord[] Records;
738
739 if (Try)
740 {
741 Records = await TryResolve(DomainName, QTYPE.AAAA, QCLASS.IN);
742 if (Records is null)
743 return null;
744 }
745 else
746 Records = await Resolve(DomainName, QTYPE.AAAA, QCLASS.IN);
747
748 List<IPAddress> Result = new List<IPAddress>();
749
750 foreach (ResourceRecord RR in await Resolve(DomainName, QTYPE.AAAA, QCLASS.IN))
751 {
752 if (RR is AAAA AAAA)
753 Result.Add(AAAA.Address);
754 }
755
756 return Result.ToArray();
757 }
758
766 public static Task<string[]> LookupMailExchange(string DomainName)
767 {
768 return LookupMailExchange(DomainName, false);
769 }
770
776 public static Task<string[]> TryLookupMailExchange(string DomainName)
777 {
778 return LookupMailExchange(DomainName, true);
779 }
780
781 private static async Task<string[]> LookupMailExchange(string DomainName, bool Try)
782 {
783 ResourceRecord[] RRs;
784
785 if (Try)
786 {
787 RRs = await TryResolve(DomainName, QTYPE.MX, TYPE.A, QCLASS.IN);
788 if (RRs is null)
789 return null;
790 }
791 else
792 RRs = await Resolve(DomainName, QTYPE.MX, TYPE.A, QCLASS.IN);
793
794 List<MX> Records = new List<MX>();
795
796 foreach (ResourceRecord RR in RRs)
797 {
798 if (RR is MX MX)
799 Records.Add(MX);
800 }
801
802 if (Records.Count == 0)
803 {
804 foreach (ResourceRecord RR in RRs)
805 {
806 if (RR is A A)
807 Records.Add(new MX() { Exchange = A.Address.ToString() });
808 }
809 }
810 else
811 Records.Sort((r1, r2) => r2.Preference - r1.Preference); // Descending
812
813 int i, c = Records.Count;
814 string[] Result = new string[c];
815
816 for (i = 0; i < c; i++)
817 Result[i] = Records[i].Exchange;
818
819 return Result;
820 }
821
830 public static string AddressToName(IPAddress Address, string IP4DomainName, string IP6DomainName)
831 {
832 byte[] Bin = Address.GetAddressBytes();
833
834 switch (Bin.Length)
835 {
836 case 4:
837 if (string.IsNullOrEmpty(IP4DomainName))
838 throw new ArgumentOutOfRangeException("IPv4 addresses not supported.");
839
840 StringBuilder sb = new StringBuilder();
841 int i;
842
843 for (i = 3; i >= 0; i--)
844 {
845 sb.Append(Bin[i].ToString());
846 sb.Append('.');
847 }
848
849 sb.Append(IP4DomainName);
850
851 return sb.ToString();
852
853 case 16:
854 if (string.IsNullOrEmpty(IP6DomainName))
855 throw new ArgumentOutOfRangeException("IPv6 addresses not supported.");
856
857 byte b, b2;
858
859 sb = new StringBuilder();
860
861 for (i = 15; i >= 0; i--)
862 {
863 b = Bin[i];
864 b2 = (byte)(b & 15);
865 if (b2 < 10)
866 sb.Append((char)('0' + b2));
867 else
868 sb.Append((char)('A' + b2 - 10));
869
870 sb.Append('.');
871
872 b2 = (byte)(b >> 4);
873 if (b2 < 10)
874 sb.Append((char)('0' + b2));
875 else
876 sb.Append((char)('A' + b2 - 10));
877
878 sb.Append('.');
879 }
880
881 sb.Append(IP6DomainName);
882
883 return sb.ToString();
884
885 default:
886 throw new ArgumentOutOfRangeException("Unrecognized IP address.", nameof(Address));
887 }
888 }
889
897 public static Task<string[]> LookupDomainName(IPAddress Address)
898 {
899 return LookupDomainName(Address, false);
900 }
901
909 public static Task<string[]> TryLookupDomainName(IPAddress Address)
910 {
911 return LookupDomainName(Address, true);
912 }
913
914 private static async Task<string[]> LookupDomainName(IPAddress Address, bool Try)
915 {
916 string Name = AddressToName(Address, "IN-ADDR.ARPA", "IP6.ARPA");
917 ResourceRecord[] Records;
918
919 if (Try)
920 {
921 Records = await TryResolve(Name, QTYPE.PTR, QCLASS.IN);
922 if (Records is null)
923 return null;
924 }
925 else
926 Records = await Resolve(Name, QTYPE.PTR, QCLASS.IN);
927
928 List<string> Result = new List<string>();
929
930 foreach (ResourceRecord RR in Records)
931 {
932 if (RR is PTR PTR)
933 Result.Add(PTR.Name2);
934 }
935
936 return Result.ToArray();
937 }
938
944 public static Task<string[]> LookupText(string Name)
945 {
946 return LookupText(Name, false);
947 }
948
954 public static Task<string[]> TryLookupText(string Name)
955 {
956 return LookupText(Name, true);
957 }
958
959 private static async Task<string[]> LookupText(string Name, bool Try)
960 {
961 ResourceRecord[] Records;
962
963 if (Try)
964 {
965 Records = await TryResolve(Name, QTYPE.TXT, QCLASS.IN);
966 if (Records is null)
967 return null;
968 }
969 else
970 Records = await Resolve(Name, QTYPE.TXT, QCLASS.IN);
971
972 List<string> Result = new List<string>();
973
974 foreach (ResourceRecord RR in Records)
975 {
976 if (RR is TXT TXT)
977 Result.AddRange(TXT.Text);
978 }
979
980 return Result.ToArray();
981 }
982
989 public static async Task<string[]> LookupBlackList(IPAddress Address, string BlackListDomainName)
990 {
991 string Name = AddressToName(Address, BlackListDomainName, null);
992 ResourceRecord[] As;
993
994 As = await TryResolve(Name, QTYPE.A, QCLASS.IN);
995 if (As is null)
996 return null;
997
998 List<string> Result = null;
999
1000 ResourceRecord[] Records = await TryResolve(Name, QTYPE.TXT, QCLASS.IN);
1001 if (!(Records is null))
1002 {
1003 foreach (ResourceRecord RR in Records)
1004 {
1005 if (RR is TXT TXT)
1006 {
1007 if (Result is null)
1008 Result = new List<string>();
1009
1010 Result.AddRange(TXT.Text);
1011 }
1012 }
1013 }
1014
1015 if (!(Result is null))
1016 return Result.ToArray();
1017
1018 foreach (ResourceRecord RR in As)
1019 {
1020 if (RR is A A)
1021 {
1022 if (Result is null)
1023 Result = new List<string>();
1024
1025 Result.Add(A.Address.ToString());
1026 }
1027 }
1028
1029 return Result?.ToArray();
1030 }
1031
1041 public static Task<SRV> LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
1042 {
1043 return LookupServiceEndpoint(DomainName, ServiceName, Protocol, null, false);
1044 }
1045
1056 public static Task<SRV> LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol, ProfilerThread Thread)
1057 {
1058 return LookupServiceEndpoint(DomainName, ServiceName, Protocol, Thread, false);
1059 }
1060
1069 public static Task<SRV> TryLookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
1070 {
1071 return LookupServiceEndpoint(DomainName, ServiceName, Protocol, null, true);
1072 }
1073
1083 public static Task<SRV> TryLookupServiceEndpoint(string DomainName, string ServiceName, string Protocol, ProfilerThread Thread)
1084 {
1085 return LookupServiceEndpoint(DomainName, ServiceName, Protocol, Thread, true);
1086 }
1087
1088 private static async Task<SRV> LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol, ProfilerThread Thread, bool Try)
1089 {
1090 Thread?.NewState("Resolve");
1091
1092 string Name = "_" + ServiceName + "._" + Protocol.ToLower() + "." + DomainName;
1093 ResourceRecord[] Records;
1094
1095 if (Try)
1096 {
1097 Records = await TryResolve(Name, QTYPE.SRV, QCLASS.IN, Thread);
1098 if (Records is null)
1099 return null;
1100 }
1101 else
1102 Records = await Resolve(Name, QTYPE.SRV, QCLASS.IN, Thread);
1103
1104 SortedDictionary<ushort, List<SRV>> ServicesByPriority = new SortedDictionary<ushort, List<SRV>>();
1105 List<SRV> SamePriority;
1106
1107 foreach (ResourceRecord RR in Records)
1108 {
1109 if (RR is SRV SRV)
1110 {
1111 if (!ServicesByPriority.TryGetValue(SRV.Priority, out SamePriority))
1112 {
1113 SamePriority = new List<SRV>();
1114 ServicesByPriority[SRV.Priority] = SamePriority;
1115 }
1116
1117 SamePriority.Add(SRV);
1118 }
1119 }
1120
1121 Thread?.NewState("Select");
1122
1123 while (true)
1124 {
1125 ushort? FirstKey = null;
1126
1127 SamePriority = null;
1128 foreach (KeyValuePair<ushort, List<SRV>> P in ServicesByPriority)
1129 {
1130 FirstKey = P.Key;
1131 SamePriority = P.Value;
1132 break;
1133 }
1134
1135 if (!FirstKey.HasValue)
1136 throw new IOException("Service Endpoint not found.");
1137
1138 int TotWeight = 0;
1139 int i;
1140
1141 foreach (SRV SRV in SamePriority)
1142 TotWeight += SRV.Weight;
1143
1144 SRV Selected = null;
1145
1146 if (TotWeight > 0)
1147 {
1148 lock (rnd)
1149 {
1150 i = rnd.Next(TotWeight);
1151 }
1152
1153 foreach (SRV SRV in SamePriority)
1154 {
1155 if (i < SRV.Weight)
1156 {
1157 Selected = SRV;
1158 SamePriority.Remove(SRV);
1159 if (SamePriority.Count == 0)
1160 ServicesByPriority.Remove(FirstKey.Value);
1161 break;
1162 }
1163 else
1164 i -= SRV.Weight;
1165 }
1166 }
1167 else
1168 {
1169 foreach (SRV SRV in SamePriority)
1170 {
1171 Selected = SRV;
1172 SamePriority.Remove(SRV);
1173 if (SamePriority.Count == 0)
1174 ServicesByPriority.Remove(FirstKey.Value);
1175 break;
1176 }
1177 }
1178
1179 // TODO: Check host availability on the given port... If not available, continue with next.
1180
1181 if (Selected is null)
1182 ServicesByPriority.Remove(FirstKey.Value);
1183 else if (Selected.TargetHost != ".")
1184 return Selected;
1185 }
1186 }
1187
1196 public static Task<SRV[]> LookupServiceEndpoints(string DomainName, string ServiceName, string Protocol)
1197 {
1198 return LookupServiceEndpoints(DomainName, ServiceName, Protocol, false);
1199 }
1200
1208 public static Task<SRV[]> TryLookupServiceEndpoints(string DomainName, string ServiceName, string Protocol)
1209 {
1210 return LookupServiceEndpoints(DomainName, ServiceName, Protocol, true);
1211 }
1212
1213 private static async Task<SRV[]> LookupServiceEndpoints(string DomainName, string ServiceName, string Protocol, bool Try)
1214 {
1215 string Name = "_" + ServiceName + "._" + Protocol.ToLower() + "." + DomainName;
1216 List<SRV> Result = new List<SRV>();
1217
1218 foreach (ResourceRecord RR in await Resolve(Name, QTYPE.SRV, QCLASS.IN))
1219 {
1220 if (RR is SRV SRV)
1221 Result.Add(SRV);
1222 }
1223
1224 return Result.ToArray();
1225 }
1226
1236 public static int Next(int MaxValue)
1237 {
1238 lock (rnd)
1239 {
1240 return rnd.Next(MaxValue);
1241 }
1242 }
1243
1249 public static Task<string[]> ReverseDns(IPAddress Address)
1250 {
1251 return ReverseDns(Address, false);
1252 }
1253
1259 public static Task<string[]> TryReverseDns(IPAddress Address)
1260 {
1261 return ReverseDns(Address, true);
1262 }
1263
1264 private static async Task<string[]> ReverseDns(IPAddress Address, bool Try)
1265 {
1266 StringBuilder sb = new StringBuilder();
1267 byte[] Bytes = Address.GetAddressBytes();
1268 int i = Bytes.Length;
1269
1270 if (Address.AddressFamily == AddressFamily.InterNetwork)
1271 {
1272 while (i-- > 0)
1273 {
1274 sb.Append(Bytes[i].ToString());
1275 sb.Append('.');
1276 }
1277
1278 sb.Append("in-addr.arpa");
1279 }
1280 else if (Address.AddressFamily == AddressFamily.InterNetworkV6)
1281 {
1282 Address.GetAddressBytes();
1283
1284 while (i-- > 0)
1285 {
1286 byte b = Bytes[i];
1287
1288 sb.Append((b & 15).ToString("x1"));
1289 sb.Append('.');
1290
1291 sb.Append((b >> 4).ToString("x1"));
1292 sb.Append('.');
1293 }
1294
1295 sb.Append("ip6.arpa");
1296 }
1297 else
1298 throw new ArgumentException("Unsupported address family.", nameof(Address));
1299
1300 ResourceRecord[] Records;
1301
1302 if (Try)
1303 {
1304 Records = await TryResolve(sb.ToString(), QTYPE.PTR, QCLASS.IN);
1305 if (Records is null)
1306 return null;
1307 }
1308 else
1309 Records = await Resolve(sb.ToString(), QTYPE.PTR, QCLASS.IN);
1310
1311 List<string> Names = new List<string>();
1312
1313 foreach (ResourceRecord Rec in Records)
1314 {
1315 if (Rec is PTR PtrRecord)
1316 Names.Add(PtrRecord.Name2);
1317 }
1318
1319 return Names.ToArray();
1320 }
1321
1322 }
1323}
Generic exception, with meta-data for logging.
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Abstract base class for DNS clients.
Definition: DnsClient.cs:22
async Task< DnsMessage > SendRequestAsync(OpCode OpCode, bool Recursive, Question[] Questions, IPEndPoint Destination, int Timeout)
Sends a DNS Request
Definition: DnsClient.cs:310
virtual void Dispose()
IDisposable.Dispose
Definition: DnsClient.cs:242
Implements a DNS over HTTPS (DoH)-based client.
ResourceRecord[] Authority
Authority resource records
Definition: DnsResponse.cs:96
ResourceRecord[] Answer
Answer resource records
Definition: DnsResponse.cs:86
ResourceRecord[] Additional
Additional resource records
Definition: DnsResponse.cs:106
Implements a DNS UDP-based client.
Definition: DnsUdpClient.cs:14
override void Dispose()
IDisposable.Dispose
Contains information about a DNS Question
Definition: Question.cs:9
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
static bool IsValidArpanetHostName(string HostName)
Checks if a host name is a valid ARPHANET host name.
Definition: DnsResolver.cs:110
static Task< IPAddress[]> TryLookupIP4Addresses(string DomainName)
Tries to look up the IPv4 addresses related to a given domain name.
Definition: DnsResolver.cs:684
static Task< ResourceRecord[]> Resolve(string Name, QTYPE TYPE, QCLASS CLASS)
Resolves a DNS name.
Definition: DnsResolver.cs:147
static Task< SRV > TryLookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Tries to look up a service endpoint for a domain. If multiple are available, an appropriate one is se...
static Task< string[]> TryLookupMailExchange(string DomainName)
Tries to look up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:776
static Task< string[]> ReverseDns(IPAddress Address)
Performs a reverse DNS lookup of an IP address.
static Task< ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, QCLASS CLASS)
Tries to resolve a DNS name.
Definition: DnsResolver.cs:208
static Task< string[]> LookupText(string Name)
Looks up text (TXT) records for a name.
Definition: DnsResolver.cs:944
static Task< IPAddress[]> TryLookupIP6Addresses(string DomainName)
Tries to look up the IPv6 addresses related to a given domain name.
Definition: DnsResolver.cs:730
static async Task< DnsResponse > Query(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
Queries a DNS name.
Definition: DnsResolver.cs:405
static string AddressToName(IPAddress Address, string IP4DomainName, string IP6DomainName)
Converts an IP Address to a domain name for reverse IP lookup, or DNSBL lookup.
Definition: DnsResolver.cs:830
static Task< ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS)
Tries to resolve a DNS name.
Definition: DnsResolver.cs:234
static Task< DnsResponse > Query(string Name, QTYPE TYPE, QCLASS CLASS)
Queries a DNS name.
Definition: DnsResolver.cs:391
static Task< IPAddress[]> LookupIP6Addresses(string DomainName)
Looks up the IPv6 addresses related to a given domain name.
Definition: DnsResolver.cs:720
static Task< ResourceRecord[]> Resolve(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
Resolves a DNS name.
Definition: DnsResolver.cs:161
static Task< string[]> LookupMailExchange(string DomainName)
Looks up the Mail Exchanges related to a given domain name.
Definition: DnsResolver.cs:766
static IPAddress[] DnsServerAddresses
Available DNS Server Addresses.
Definition: DnsResolver.cs:83
static Task< SRV[]> LookupServiceEndpoints(string DomainName, string ServiceName, string Protocol)
Looks up a available service endpoints for a domain.
static async Task< ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS, ProfilerThread Thread)
Tries to resolve a DNS name.
Definition: DnsResolver.cs:248
static Task< SRV > TryLookupServiceEndpoint(string DomainName, string ServiceName, string Protocol, ProfilerThread Thread)
Tries to look up a service endpoint for a domain. If multiple are available, an appropriate one is se...
static Task< DnsResponse > TryQuery(string Name, QTYPE TYPE, QCLASS CLASS)
Tries to query a DNS name.
Definition: DnsResolver.cs:424
static Task< IPAddress[]> LookupIP4Addresses(string DomainName)
Looks up the IPv4 addresses related to a given domain name.
Definition: DnsResolver.cs:674
static Task< string[]> TryLookupText(string Name)
Tries to look up text (TXT) records for a name.
Definition: DnsResolver.cs:954
static Task< ResourceRecord[]> Resolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS)
Resolves a DNS name.
Definition: DnsResolver.cs:175
static Task< string[]> LookupDomainName(IPAddress Address)
Looks up the domain name pointing to a specific IP address.
Definition: DnsResolver.cs:897
static Task< DnsResponse > TryQuery(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
Tries to query a DNS name.
Definition: DnsResolver.cs:437
static Task< SRV > LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol)
Looks up a service endpoint for a domain. If multiple are available, an appropriate one is selected a...
static Task< string[]> TryLookupDomainName(IPAddress Address)
Tries to look up the domain name pointing to a specific IP address.
Definition: DnsResolver.cs:909
static Uri DnsOverHttpsUri
URI used in DNS over HTTPS (DoH) requests. Setting the property to null disables DNS over HTTPS (DoH)...
Definition: DnsResolver.cs:63
static Task< ResourceRecord[]> TryResolve(string Name, QTYPE TYPE, QCLASS CLASS, ProfilerThread Thread)
Tries to resolve a DNS name.
Definition: DnsResolver.cs:221
static Task< string[]> TryReverseDns(IPAddress Address)
Tries to perform a reverse DNS lookup of an IP address.
static async Task< ResourceRecord[]> Resolve(string Name, QTYPE TYPE, TYPE? ExceptionType, QCLASS CLASS, ProfilerThread Thread)
Resolves a DNS name.
Definition: DnsResolver.cs:189
static Task< SRV[]> TryLookupServiceEndpoints(string DomainName, string ServiceName, string Protocol)
Tries to look up available service endpoints for a domain.
static Task< SRV > LookupServiceEndpoint(string DomainName, string ServiceName, string Protocol, ProfilerThread Thread)
Looks up a service endpoint for a domain. If multiple are available, an appropriate one is selected a...
static async Task< string[]> LookupBlackList(IPAddress Address, string BlackListDomainName)
Looks up an IP Address in a DNS Block List.
Definition: DnsResolver.cs:989
static int Next(int MaxValue)
Returns a non-negative random integer that is less than the specified maximum.
Abstract base class for a resource record.
string[] Text
Descriptive text.
Definition: TXT.cs:51
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static bool HasProvider
If a database provider is registered.
Definition: Database.cs:81
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
static async Task Clear(string CollectionName)
Clears a collection of all objects.
Definition: Database.cs:1965
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Class that keeps track of events and timing for one thread.
void Exception(System.Exception Exception)
Exception occurred
void NewState(string State)
Thread changes state.
class Names(Vector NamesVector)
Contains a collection of distinguished names.
Definition: Names.cs:9
OpCode
DNS Operation Codes
Definition: OpCode.cs:7
RCode
DNS Response Code
Definition: RCode.cs:7
CLASS
TYPE fields are used in resource records.
Definition: CLASS.cs:7
QTYPE
QTYPE fields appear in the question part of a query.
Definition: QTYPE.cs:7
QCLASS
QCLASS fields appear in the question section of a query.
Definition: QCLASS.cs:7
TYPE
TYPE fields are used in resource records.
Definition: TYPE.cs:7