Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
DnsClient.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Net;
5using System.Reflection;
6using System.Text;
7using System.Threading.Tasks;
8using Waher.Events;
14
16{
20 public abstract class DnsClient : CommunicationLayer, IDisposable
21 {
25 public const int DefaultTimeout = 30000;
26
30 protected bool disposed = false;
31
32 private static object idnMapping = null;
33 private static MethodInfo getAscii = null;
34 private static MethodInfo getUnicode = null;
35 private static bool initialized = false;
36
37 private readonly Dictionary<ushort, Rec> outgoingMessages = new Dictionary<ushort, Rec>();
38 private readonly LinkedList<KeyValuePair<byte[], IPEndPoint>> outputQueue = new LinkedList<KeyValuePair<byte[], IPEndPoint>>();
39 private ProfilerThread thread = null;
40 private Scheduler scheduler;
41 private bool isWriting = false;
42
43 private class Rec
44 {
45 public ushort ID;
46 public byte[] Output;
47 public IPEndPoint Destination;
48 public EventHandlerAsync<DnsMessageEventArgs> Callback;
49 public object State;
50 }
51
55 public DnsClient()
56 : base(false)
57 {
58 }
59
63 protected virtual void Init()
64 {
65 this.scheduler = new Scheduler();
66 }
67
72 {
73 get => this.thread;
74 set => this.thread = value;
75 }
76
85 protected async Task BeginTransmit(ushort ID, byte[] Message, IPEndPoint Destination,
86 EventHandlerAsync<DnsMessageEventArgs> Callback, object State)
87 {
88 if (this.disposed)
89 return;
90
91 if (!(Callback is null))
92 {
93 Rec Rec = new Rec()
94 {
95 ID = ID,
96 Output = Message,
97 Destination = Destination,
98 Callback = Callback,
99 State = State
100 };
101
102 lock (this.outgoingMessages)
103 {
104 this.outgoingMessages[ID] = Rec;
105 }
106
107 this.scheduler.Add(DateTime.Now.AddSeconds(2), this.CheckRetry, Rec);
108 }
109
110 lock (this.outputQueue)
111 {
112 if (this.isWriting)
113 {
114 this.outputQueue.AddLast(new KeyValuePair<byte[], IPEndPoint>(Message, Destination));
115 return;
116 }
117 else
118 this.isWriting = true;
119 }
120
121 try
122 {
123 while (!(Message is null))
124 {
125 this.thread?.Event("Tx");
126 await this.TransmitBinary(Message);
127
128 await this.SendAsync(Message, Destination);
129
130 if (this.disposed)
131 return;
132
133 lock (this.outputQueue)
134 {
135 if (this.outputQueue.First is null)
136 {
137 this.isWriting = false;
138 Message = null;
139 }
140 else
141 {
142 Message = this.outputQueue.First.Value.Key;
143 Destination = this.outputQueue.First.Value.Value;
144 this.outputQueue.RemoveFirst();
145 }
146 }
147 }
148 }
149 catch (Exception ex)
150 {
151 ex = Log.UnnestException(ex);
152 this.thread?.Exception(ex);
153 await this.Exception(ex);
154 }
155 }
156
162 protected abstract Task SendAsync(byte[] Message, IPEndPoint Destination);
163
164 private Task CheckRetry(object P)
165 {
166 Rec Rec = (Rec)P;
167
168 lock (this.outgoingMessages)
169 {
170 if (!this.outgoingMessages.ContainsKey(Rec.ID))
171 return Task.CompletedTask;
172 }
173
174 return this.BeginTransmit(Rec.ID, Rec.Output, Rec.Destination, Rec.Callback, Rec.State);
175 }
176
181 protected virtual async Task ProcessIncomingMessage(DnsMessage Message)
182 {
183 this.thread?.Event("Rx");
184
185 if (Message.Response)
186 {
187 Rec Rec;
188
189 lock (this.outgoingMessages)
190 {
191 if (this.outgoingMessages.TryGetValue(Message.ID, out Rec))
192 this.outgoingMessages.Remove(Message.ID);
193 else
194 return;
195 }
196
197 await Rec.Callback.Raise(this, new DnsMessageEventArgs(Message, Rec.State));
198 }
199 }
200
205 protected virtual async Task ProcessMessageFailure(ushort ID)
206 {
207 Rec Rec;
208
209 lock (this.outgoingMessages)
210 {
211 if (this.outgoingMessages.TryGetValue(ID, out Rec))
212 this.outgoingMessages.Remove(ID);
213 else
214 return;
215 }
216
217 DnsMessage Message = new DnsMessage(new byte[]
218 {
219 (byte)(ID >> 8),
220 (byte)(ID & 255),
221 0x80, // Response
222 (byte)RCode.ServFail,
223 0, 0, // QDCOUNT
224 0, 0, // ANCOUNT
225 0, 0, // NSCOUNT
226 0, 0 // ARCOUNT
227 });
228
229 await Rec.Callback.Raise(this, new DnsMessageEventArgs(Message, Rec.State));
230 }
231
235 public virtual void Dispose()
236 {
237 this.disposed = true;
238
239 this.scheduler?.Dispose();
240 this.scheduler = null;
241 }
242
253 public Task SendRequest(OpCode OpCode, bool Recursive, Question[] Questions,
254 IPEndPoint Destination, EventHandlerAsync<DnsMessageEventArgs> Callback, object State)
255 {
256 using (MemoryStream Request = new MemoryStream())
257 {
258 ushort ID = DnsResolver.NextID;
259
260 WriteUInt16(ID, Request);
261
262 byte b = (byte)((int)OpCode << 3);
263 if (Recursive)
264 b |= 1;
265
266 Request.WriteByte(b);
267 Request.WriteByte((byte)RCode.NoError);
268
269 int c = Questions.Length;
270 if (c == 0)
271 throw new ArgumentException("No questions included in request.", nameof(Questions));
272
273 if (c > ushort.MaxValue)
274 throw new ArgumentException("Too many questions in request.", nameof(Questions));
275
276 WriteUInt16((ushort)c, Request); // Query Count
277 WriteUInt16(0, Request); // Answer Count
278 WriteUInt16(0, Request); // Authoritative Count
279 WriteUInt16(0, Request); // Additional Count
280
281 Dictionary<string, ushort> NamePositions = new Dictionary<string, ushort>();
282
283 foreach (Question Q in Questions)
284 {
285 WriteName(Q.QNAME, Request, NamePositions);
286 WriteUInt16((ushort)Q.QTYPE, Request);
287 WriteUInt16((ushort)Q.QCLASS, Request);
288 }
289
290 byte[] Packet = Request.ToArray();
291
292 return this.BeginTransmit(ID, Packet, Destination, Callback, State);
293 }
294 }
295
305 public async Task<DnsMessage> SendRequestAsync(OpCode OpCode, bool Recursive,
306 Question[] Questions, IPEndPoint Destination, int Timeout)
307 {
308 TaskCompletionSource<DnsMessage> Result = new TaskCompletionSource<DnsMessage>();
309 DateTime TP = DateTime.MinValue;
310
311 await this.SendRequest(OpCode, Recursive, Questions, Destination, (Sender, e) =>
312 {
313 this.scheduler?.Remove(TP);
314 ((TaskCompletionSource<DnsMessage>)e.State).TrySetResult(e.Message);
315 return Task.CompletedTask;
316 }, Result);
317
318 TP = DateTime.Now.AddMilliseconds(Timeout);
319 TP = this.scheduler.Add(TP, (P) =>
320 {
321 ((TaskCompletionSource<DnsMessage>)P).TrySetException(
322 new TimeoutException("No DNS response returned within the given time."));
323 }, Result);
324
325 return await Result.Task;
326 }
327
337 public void Query(string QNAME, QTYPE QTYPE, QCLASS QCLASS, IPEndPoint Destination, EventHandlerAsync<DnsMessageEventArgs> Callback, object State)
338 {
339 this.Query(new Question[] { new Question(QNAME, QTYPE, QCLASS) }, Destination, Callback, State);
340 }
341
351 public void Query(string QNAME, QTYPE[] QTYPEs, QCLASS QCLASS, IPEndPoint Destination, EventHandlerAsync<DnsMessageEventArgs> Callback, object State)
352 {
353 this.Query(ToQuestions(QNAME, QTYPEs, QCLASS), Destination, Callback, State);
354 }
355
363 public void Query(Question[] Questions, IPEndPoint Destination, EventHandlerAsync<DnsMessageEventArgs> Callback, object State)
364 {
365 this.SendRequest(OpCode.Query, false, Questions, Destination, Callback, State);
366 }
367
375 public Task<DnsMessage> QueryAsync(string QNAME, QTYPE QTYPE, QCLASS QCLASS, IPEndPoint Destination)
376 {
377 return this.QueryAsync(new Question[] { new Question(QNAME, QTYPE, QCLASS) }, Destination);
378 }
379
387 public Task<DnsMessage> QueryAsync(string QNAME, QTYPE[] QTYPEs, QCLASS QCLASS, IPEndPoint Destination)
388 {
389 return this.QueryAsync(ToQuestions(QNAME, QTYPEs, QCLASS), Destination);
390 }
391
397 public Task<DnsMessage> QueryAsync(Question[] Questions, IPEndPoint Destination)
398 {
399 return this.QueryAsync(Questions, Destination, DefaultTimeout);
400 }
401
408 public Task<DnsMessage> QueryAsync(Question[] Questions, IPEndPoint Destination, int Timeout)
409 {
410 return this.SendRequestAsync(OpCode.Query, false, Questions, Destination, Timeout);
411 }
412
413 private static Question[] ToQuestions(string QNAME, QTYPE[] QTYPEs, QCLASS QCLASS)
414 {
415 int i, c = QTYPEs.Length;
416 Question[] Questions = new Question[c];
417
418 for (i = 0; i < c; i++)
419 Questions[i] = new Question(QNAME, QTYPEs[i], QCLASS);
420
421 return Questions;
422 }
423
424 internal static uint ReadUInt32(Stream Data)
425 {
426 ushort Result = ReadUInt16(Data);
427 Result <<= 16;
428 Result |= ReadUInt16(Data);
429
430 return Result;
431 }
432
433 internal static ushort ReadUInt16(Stream Data)
434 {
435 ushort Result = (byte)Data.ReadByte();
436 Result <<= 8;
437 Result |= (byte)Data.ReadByte();
438
439 return Result;
440 }
441
442 internal static string ReadName(Stream Data)
443 {
444 if (!initialized)
445 Initialize();
446
447 StringBuilder sb = null;
448 string s;
449 bool Continue = true;
450
451 while (Continue)
452 {
453 int Len = Data.ReadByte();
454 if (Len == 0)
455 break;
456
457 switch (Len & 192)
458 {
459 case 0:
460 byte[] Bin = new byte[Len];
461 Data.ReadAll(Bin, 0, Len);
462
463 s = Encoding.ASCII.GetString(Bin);
464
465 if (!(getUnicode is null))
466 s = (string)getUnicode.Invoke(idnMapping, new object[] { s });
467
468 break;
469
470 case 192:
471 ushort Offset = (byte)(Len & 63);
472 Offset <<= 8;
473 Offset |= (byte)(Data.ReadByte());
474
475 long Bak = Data.Position;
476
477 Data.Position = Offset;
478
479 s = ReadName(Data);
480
481 Data.Position = Bak;
482 Continue = false;
483 break;
484
485 default:
486 throw new NotSupportedException("Unsupported Label Type.");
487 }
488
489 if (sb is null)
490 sb = new StringBuilder();
491 else
492 sb.Append('.');
493
494 sb.Append(s);
495 }
496
497 return sb?.ToString() ?? string.Empty;
498 }
499
500 internal static string ReadString(Stream Data)
501 {
502 int Len = Data.ReadByte();
503 if (Len == 0)
504 return string.Empty;
505
506 byte[] Bin = new byte[Len];
507 Data.ReadAll(Bin, 0, Len);
508
509 return Encoding.ASCII.GetString(Bin);
510 }
511
512 internal static ResourceRecord[] ReadResourceRecords(Stream Data, ushort Count)
513 {
514 List<ResourceRecord> Result = new List<ResourceRecord>();
515 ResourceRecord Rec;
516
517 while (Count > 0)
518 {
519 Count--;
520 Rec = ResourceRecord.Create(Data);
521
522 if (!(Rec is null))
523 Result.Add(Rec);
524 }
525
526 return Result.ToArray();
527 }
528
529 internal static void WriteName(string Name, Stream Output,
530 Dictionary<string, ushort> NamePositions)
531 {
532 if (!initialized)
533 Initialize();
534
535 while (!string.IsNullOrEmpty(Name))
536 {
537 if (NamePositions.TryGetValue(Name, out ushort Pos))
538 {
539 byte b = (byte)(Pos >> 8);
540 b |= 0xc0;
541
542 Output.WriteByte(b);
543 Output.WriteByte((byte)(Pos & 0xff));
544 return;
545 }
546 else
547 {
548 NamePositions[Name] = (ushort)Output.Position;
549
550 int i = Name.IndexOf('.');
551 string Label;
552
553 if (i < 0)
554 {
555 Label = Name;
556 Name = string.Empty;
557 }
558 else
559 {
560 Label = Name.Substring(0, i);
561 Name = Name.Substring(i + 1);
562 }
563
564 if (!(getAscii is null))
565 Label = (string)getAscii.Invoke(idnMapping, new object[] { Label });
566
567 Output.WriteByte((byte)Label.Length);
568
569 byte[] Bin = Encoding.ASCII.GetBytes(Label);
570 Output.Write(Bin, 0, Bin.Length);
571 }
572 }
573
574 Output.WriteByte(0);
575 }
576
577 internal static void WriteUInt16(ushort Value, Stream Output)
578 {
579 Output.WriteByte((byte)(Value >> 8));
580 Output.WriteByte((byte)Value);
581 }
582
583 private static void Initialize()
584 {
585 initialized = true;
586 Type T = Types.GetType("System.Globalization.IdnMapping");
587 if (T is null)
588 {
589 idnMapping = null;
590 getAscii = null;
591 getUnicode = null;
592 }
593 else
594 {
595 Type[] Parameters = new Type[] { typeof(string) };
596
597 idnMapping = Types.Instantiate(T);
598 getAscii = T.GetRuntimeMethod("GetAscii", Parameters);
599 getUnicode = T.GetRuntimeMethod("GetUnicode", Parameters);
600 }
601 }
602
603 }
604}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
Simple base class for classes implementing communication protocols.
Task TransmitBinary(byte[] Data)
Called when binary data has been transmitted.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
Abstract base class for DNS clients.
Definition: DnsClient.cs:21
virtual void Init()
Called when DNS client is ready to be initialized.
Definition: DnsClient.cs:63
Task< DnsMessage > QueryAsync(string QNAME, QTYPE QTYPE, QCLASS QCLASS, IPEndPoint Destination)
Execute a DNS query.
Definition: DnsClient.cs:375
const int DefaultTimeout
Default Timeout, in milliseconds (30000 ms)
Definition: DnsClient.cs:25
Task SendRequest(OpCode OpCode, bool Recursive, Question[] Questions, IPEndPoint Destination, EventHandlerAsync< DnsMessageEventArgs > Callback, object State)
Sends a DNS Request
Definition: DnsClient.cs:253
void Query(Question[] Questions, IPEndPoint Destination, EventHandlerAsync< DnsMessageEventArgs > Callback, object State)
Execute a DNS query.
Definition: DnsClient.cs:363
async Task< DnsMessage > SendRequestAsync(OpCode OpCode, bool Recursive, Question[] Questions, IPEndPoint Destination, int Timeout)
Sends a DNS Request
Definition: DnsClient.cs:305
void Query(string QNAME, QTYPE QTYPE, QCLASS QCLASS, IPEndPoint Destination, EventHandlerAsync< DnsMessageEventArgs > Callback, object State)
Execute a DNS query.
Definition: DnsClient.cs:337
virtual void Dispose()
IDisposable.Dispose
Definition: DnsClient.cs:235
virtual async Task ProcessIncomingMessage(DnsMessage Message)
Processes an incoming message.
Definition: DnsClient.cs:181
bool disposed
If the object has been disposed
Definition: DnsClient.cs:30
ProfilerThread Thread
Optional thread for profiling.
Definition: DnsClient.cs:72
async Task BeginTransmit(ushort ID, byte[] Message, IPEndPoint Destination, EventHandlerAsync< DnsMessageEventArgs > Callback, object State)
Sends a message to a DNS server.
Definition: DnsClient.cs:85
DnsClient()
Abstract base class for DNS clients.
Definition: DnsClient.cs:55
Task< DnsMessage > QueryAsync(Question[] Questions, IPEndPoint Destination, int Timeout)
Execute a DNS query.
Definition: DnsClient.cs:408
Task< DnsMessage > QueryAsync(string QNAME, QTYPE[] QTYPEs, QCLASS QCLASS, IPEndPoint Destination)
Execute a DNS query.
Definition: DnsClient.cs:387
abstract Task SendAsync(byte[] Message, IPEndPoint Destination)
Sends a message to a destination.
Task< DnsMessage > QueryAsync(Question[] Questions, IPEndPoint Destination)
Execute a DNS query.
Definition: DnsClient.cs:397
void Query(string QNAME, QTYPE[] QTYPEs, QCLASS QCLASS, IPEndPoint Destination, EventHandlerAsync< DnsMessageEventArgs > Callback, object State)
Execute a DNS query.
Definition: DnsClient.cs:351
virtual async Task ProcessMessageFailure(ushort ID)
Request resulted in a failure.
Definition: DnsClient.cs:205
bool Response
If a Response (true) or a query (false)
Definition: DnsMessage.cs:87
Contains information about a DNS Question
Definition: Question.cs:12
DNS resolver, as defined in:
Definition: DnsResolver.cs:32
Abstract base class for a resource record.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
static Type GetType(string FullName)
Gets a type, given its full name.
Definition: Types.cs:41
static object Instantiate(Type Type, params object[] Arguments)
Returns an instance of the type Type . If one needs to be created, it is. If the constructor requires...
Definition: Types.cs:1353
Class that keeps track of events and timing for one thread.
Class that can be used to schedule events in time. It uses a timer to execute tasks at the appointed ...
Definition: Scheduler.cs:26
bool Remove(DateTime When)
Removes an event scheduled for a given point in time.
Definition: Scheduler.cs:182
void Dispose()
IDisposable.Dispose
Definition: Scheduler.cs:46
DateTime Add(DateTime When, ScheduledEventCallback Callback, object State)
Adds an event.
Definition: Scheduler.cs:66
OpCode
DNS Operation Codes
Definition: OpCode.cs:11
RCode
DNS Response Code
Definition: RCode.cs:11
QTYPE
QTYPE fields appear in the question part of a query.
Definition: QTYPE.cs:11
QCLASS
QCLASS fields appear in the question section of a query.
Definition: QCLASS.cs:11