Skip to content

Commit

Permalink
Add example dig and easy dns programs
Browse files Browse the repository at this point in the history
  • Loading branch information
jdomnitz committed Jul 19, 2024
1 parent f5ce1e3 commit 1179a58
Show file tree
Hide file tree
Showing 12 changed files with 499 additions and 63 deletions.
29 changes: 6 additions & 23 deletions TinyDNS/DNSResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ private void ReloadNameservers()

public async Task<List<IPAddress>> ResolveHost(string hostname)
{
ArgumentNullException.ThrowIfNullOrEmpty(hostname, nameof(hostname));
List<IPAddress> addresses =
[
.. await ResolveHostV4(hostname),
Expand All @@ -80,6 +81,7 @@ .. await ResolveHostV6(hostname)

public async Task<List<IPAddress>> ResolveHostV4(string hostname)
{
ArgumentNullException.ThrowIfNullOrEmpty(hostname, nameof(hostname));
List<IPAddress> addresses = new List<IPAddress>();
Message? response = await ResolveQuery(new QuestionRecord(hostname, DNSRecordType.A, false));
if (response == null || response.ResponseCode != DNSStatus.NoError || (response.Answers.Length == 0 && response.Additionals.Length == 0))
Expand All @@ -100,6 +102,7 @@ public async Task<List<IPAddress>> ResolveHostV4(string hostname)

public async Task<List<IPAddress>> ResolveHostV6(string hostname)
{
ArgumentNullException.ThrowIfNullOrEmpty(hostname, nameof(hostname));
List<IPAddress> addresses = new List<IPAddress>();
Message? response = await ResolveQuery(new QuestionRecord(hostname, DNSRecordType.AAAA, false));
if (response == null || response.ResponseCode != DNSStatus.NoError || (response.Answers.Length == 0 && response.Additionals.Length == 0))
Expand All @@ -120,31 +123,11 @@ public async Task<List<IPAddress>> ResolveHostV6(string hostname)

public async Task<Message?> ResolveIPRecord(IPAddress address)
{
if (address == null)
throw new ArgumentNullException(nameof(address));
byte[] addressBytes = address.GetAddressBytes();
List<string> host;
bool privateQuery = IsPrivate(address, addressBytes);
if (address.AddressFamily == AddressFamily.InterNetwork)
{
host = new List<string>(6);
for (int i = addressBytes.Length - 1; i >= 0; i--)
host.Add(addressBytes[i].ToString());

host.Add("in-addr");
host.Add("arpa");
}
else
{
host = new List<string>(34);
for (int i = addressBytes.Length - 1; i >= 0; i--)
{
string hex = addressBytes[i].ToString("x2");
host.Add(hex.Substring(1, 1));
host.Add(hex.Substring(0, 1));
}
host.Add("IP6");
host.Add("ARPA");
}
return await ResolveQuery(new QuestionRecord(host, DNSRecordType.PTR, false), privateQuery);
return await ResolveQuery(new QuestionRecord(DomainParser.FromIP(addressBytes), DNSRecordType.PTR, false), privateQuery);
}
public async Task<string?> ResolveIP(IPAddress address)
{
Expand Down
27 changes: 27 additions & 0 deletions TinyDNS/DomainParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,32 @@ public static List<string> Parse(string domain)
labels.Add(label.ToString());
return labels;
}

internal static List<string> FromIP(byte[] address)
{
List<string> host;
if (address.Length == 4)
{
host = new List<string>(6);
for (int i = address.Length - 1; i >= 0; i--)
host.Add(address[i].ToString());

host.Add("in-addr");
host.Add("arpa");
}
else
{
host = new List<string>(34);
for (int i = address.Length - 1; i >= 0; i--)
{
string hex = address[i].ToString("x2");
host.Add(hex.Substring(1, 1));
host.Add(hex.Substring(0, 1));
}
host.Add("IP6");
host.Add("ARPA");
}
return host;
}
}
}
27 changes: 27 additions & 0 deletions TinyDNS/Events/DNSErrorEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// TinyDNS Copyright (C) 2024
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using System.Net;

namespace TinyDNS.Events
{
public class DNSErrorEvent : EventArgs
{
public DNSErrorEvent(Exception error, IPEndPoint? endPoint)
{
Error = error;
RemoteEndPoint = endPoint;
}
public Exception Error;
public IPEndPoint? RemoteEndPoint;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

namespace TinyDNS.Events
{
public class DNSMsgEvent : EventArgs
public class DNSMessageEvent : EventArgs
{
public DNSMsgEvent(Message msg, IPEndPoint endPoint)
public DNSMessageEvent(Message msg, IPEndPoint endPoint)
{
Message = msg;
RemoteEndPoint = endPoint;
Expand Down
100 changes: 76 additions & 24 deletions TinyDNS/MDNS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public class MDNS : IDisposable
private readonly Socket? listenerV6;
private readonly List<Socket> senders = [];

public delegate Task MessageEventHandler(DNSMsgEvent e);
public delegate Task MessageEventHandler(DNSMessageEvent e);
public event MessageEventHandler? AnswerReceived;
public delegate Task ErrorEventHandler(DNSErrorEvent e);
public event ErrorEventHandler? OnError;
private readonly RecordCache messageCache = new RecordCache(100, TimeSpan.FromSeconds(5));
private readonly bool UNICAST_SUPPORTED;

Expand Down Expand Up @@ -91,6 +93,7 @@ public async Task Start()

private async Task ReceiveV4()
{
IPEndPoint? sender = null;
try
{
Memory<byte> buffer = new byte[8972];
Expand All @@ -99,13 +102,14 @@ private async Task ReceiveV4()
try
{
SocketReceiveFromResult received = await listenerV4!.ReceiveFromAsync(buffer, SocketFlags.None, new IPEndPoint(IPAddress.Any, PORT), stop.Token);
sender = (IPEndPoint)received.RemoteEndPoint;
Message msg = new Message(buffer.Slice(0, received.ReceivedBytes).Span);
if (msg.Response && msg.ResponseCode == DNSStatus.NoError && (msg.Answers.Length > 0 || msg.Additionals.Length > 0))
{
if (messageCache.Cached(msg, ((IPEndPoint)received.RemoteEndPoint).Address))
if (messageCache.Cached(msg, sender.Address))
continue;
if (AnswerReceived != null)
await AnswerReceived(new DNSMsgEvent(msg, (IPEndPoint)received.RemoteEndPoint));
await AnswerReceived(new DNSMessageEvent(msg, (IPEndPoint)received.RemoteEndPoint));
}
}
catch (InvalidDataException) { }
Expand All @@ -114,12 +118,14 @@ private async Task ReceiveV4()
catch (OperationCanceledException) { }
catch (Exception ex)
{
//TODO: logger.Error(ex, "Failed to parse DNS answer");
if (OnError != null)
await OnError(new DNSErrorEvent(ex, sender));
}
}

private async Task ReceiveV6()
{
IPEndPoint? sender = null;
try
{
Memory<byte> buffer = new byte[8952];
Expand All @@ -128,13 +134,14 @@ private async Task ReceiveV6()
try
{
SocketReceiveFromResult received = await listenerV6!.ReceiveFromAsync(buffer, SocketFlags.None, new IPEndPoint(IPAddress.IPv6Any, PORT), stop.Token);
sender = (IPEndPoint)received.RemoteEndPoint;
Message msg = new Message(buffer.Slice(0, received.ReceivedBytes).Span);
if (msg.Response && msg.ResponseCode == DNSStatus.NoError && (msg.Answers.Length > 0 || msg.Additionals.Length > 0))
{
if (messageCache.Cached(msg, ((IPEndPoint)received.RemoteEndPoint).Address))
continue;
if (AnswerReceived != null)
await AnswerReceived(new DNSMsgEvent(msg, (IPEndPoint)received.RemoteEndPoint));
await AnswerReceived(new DNSMessageEvent(msg, (IPEndPoint)received.RemoteEndPoint));
}
}
catch (InvalidDataException) { }
Expand All @@ -143,12 +150,16 @@ private async Task ReceiveV6()
catch (OperationCanceledException) { }
catch (Exception ex)
{
//TODO: logger.Error(ex, "Failed to parse DNS answer");
if (OnError != null)
await OnError(new DNSErrorEvent(ex, sender));
}
}

[Obsolete("Use Query instead")]
public async Task QueryAny(string domain, bool unicastResponse = false)
{
if (!domain.Contains('.'))
domain = string.Concat(domain, ".local");
messageCache.Clear();
Message msg = new Message();
msg.Response = false;
Expand All @@ -160,6 +171,8 @@ public async Task QueryAny(string domain, bool unicastResponse = false)

public async Task QueryServices(string domain)
{
if (!domain.Contains('.'))
domain = string.Concat(domain, ".local");
Message msg = new Message();
msg.Response = false;
msg.Questions = [
Expand All @@ -169,44 +182,83 @@ public async Task QueryServices(string domain)
await SendMessage(msg);
}

public async Task QueryTxts(string domain)
public async Task Query(string domain, DNSRecordType type, bool unicastResponse = false)
{
if (!domain.Contains('.'))
domain = string.Concat(domain, ".local");
Message msg = new Message();
msg.Response = false;
msg.Questions = [
new QuestionRecord(domain, DNSRecordType.TXT, false)
new QuestionRecord(domain, type, unicastResponse && UNICAST_SUPPORTED)
];
await SendMessage(msg);
}

public async Task QueryPointers(string domain)
public async Task Query(List<string> domain, DNSRecordType type, bool unicastResponse = false)
{
if (domain.Count <= 1)
domain.Add("local");
Message msg = new Message();
msg.Response = false;
msg.Questions = [
new QuestionRecord(domain, DNSRecordType.PTR, false)
new QuestionRecord(domain, type, unicastResponse && UNICAST_SUPPORTED)
];
await SendMessage(msg);
}

public async Task QueryIPv4Addresses(string domain)
public async Task<List<Message>> ResolveInverseQuery(IPAddress address, bool unicastResponse = false)
{
Message msg = new Message();
msg.Response = false;
msg.Questions = [
new QuestionRecord(domain, DNSRecordType.A, false)
];
await SendMessage(msg);
var domain = DomainParser.FromIP(address.GetAddressBytes());
List<Message> responses = new List<Message>();
MessageEventHandler handler = delegate (DNSMessageEvent e)
{
bool validDomain = false;
bool validType = false;
foreach (ResourceRecord answer in e.Message.Answers)
{
if (answer.Labels.SequenceEqual(domain, new DomainEqualityComparer()))
validDomain = true;
if (answer.Type == DNSRecordType.PTR)
validType = true;
}
if (validDomain && validType)
responses.Add(e.Message);
return Task.CompletedTask;
};

AnswerReceived += handler;
await Query(domain, DNSRecordType.PTR, unicastResponse);
await Task.Delay(3000);
AnswerReceived -= handler;
return responses;
}

public async Task QueryIPv6Addresses(string domain)
public async Task<List<Message>> ResolveQuery(string domain, DNSRecordType type, bool unicastResponse = false)
{
Message msg = new Message();
msg.Response = false;
msg.Questions = [
new QuestionRecord(domain, DNSRecordType.AAAA, false)
];
await SendMessage(msg);
if (!domain.Contains('.'))
domain = string.Concat(domain, ".local");
List<Message> responses = new List<Message>();
MessageEventHandler handler = delegate(DNSMessageEvent e)
{
bool validDomain = false;
bool validType = false;
foreach (ResourceRecord answer in e.Message.Answers)
{
if (answer.Name.Equals(domain, StringComparison.OrdinalIgnoreCase))
validDomain = true;
if (answer.Type == type)
validType = true;
}
if (validDomain && validType)
responses.Add(e.Message);
return Task.CompletedTask;
};

AnswerReceived += handler;
await Query(domain, type, unicastResponse);
await Task.Delay(3000);
AnswerReceived -= handler;
return responses;
}

private async Task SendMessage(Message msg)
Expand Down
6 changes: 6 additions & 0 deletions TinyDNS/Records/SOARecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class SOARecord : ResourceRecord
public TimeSpan Expire { get; set; }
public TimeSpan Minimum { get; set; }
public string MName { get { return string.Join('.', MNameLabels); } }
public string RName { get { return string.Join('.', RNameLabels); } }

internal SOARecord(ResourceRecordHeader header, Span<byte> buffer, ref int pos) : base(header)
{
Expand Down Expand Up @@ -57,5 +58,10 @@ public override bool Equals(ResourceRecord? other)
return base.Equals(other) && MNameLabels.SequenceEqual(otherSOA.MNameLabels) && Serial.Equals(otherSOA.Serial);
return false;
}

public override string ToString()
{
return base.ToString() + $"\t{MName}\t{RName}\t{Serial}\t{Refresh}\t{Retry}\t{Expire}\t{Minimum}";
}
}
}
5 changes: 5 additions & 0 deletions TinyDNS/Records/SRVRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,10 @@ public override bool Equals(ResourceRecord? other)
return base.Equals(other) && Port == otherSrv.Port && TargetLabels.Equals(otherSrv.TargetLabels);
return false;
}

public override string ToString()
{
return base.ToString() + $"\t{Priority}\t{Weight}\t{Port}\t{Target}";
}
}
}
5 changes: 5 additions & 0 deletions TinyDNS/Records/TxtRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,10 @@ public override bool Equals(ResourceRecord? other)
return base.Equals(other) && Strings.Equals(otherTxt.Strings);
return false;
}

public override string ToString()
{
return base.ToString() + $"\t{string.Join(',', Strings)}";
}
}
}
5 changes: 5 additions & 0 deletions TinyDNS/Records/UnsupportedRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,10 @@ public override bool Equals(ResourceRecord? other)
return base.Equals(other) && RData.Equals(unsupported.RData);
return false;
}

public override string ToString()
{
return base.ToString() + $"\t{RData.Length} Bytes";
}
}
}
Loading

0 comments on commit 1179a58

Please sign in to comment.