diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 60fcc9a..43a1db8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3.3.0 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3.0.3 with: - dotnet-version: 2.2.x + dotnet-version: 7.0.x - name: Test run: | diff --git a/DNS/Client/DnsClient.cs b/DNS/Client/DnsClient.cs index 2a7a7a9..82f6eeb 100644 --- a/DNS/Client/DnsClient.cs +++ b/DNS/Client/DnsClient.cs @@ -26,6 +26,9 @@ public DnsClient(string ip, int port = DEFAULT_PORT) : public DnsClient(IRequestResolver resolver) { this.resolver = resolver; } + public DnsClient(Uri uri) : + this(new HttpsRequestResolver(uri)) + { } public ClientRequest FromArray(byte[] message) { Request request = Request.FromArray(message); diff --git a/DNS/Client/RequestResolver/HttpsRequestResolver.cs b/DNS/Client/RequestResolver/HttpsRequestResolver.cs new file mode 100644 index 0000000..282ea57 --- /dev/null +++ b/DNS/Client/RequestResolver/HttpsRequestResolver.cs @@ -0,0 +1,55 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using DNS.Protocol; +using DNS.Protocol.Utils; + +namespace DNS.Client.RequestResolver { + public class HttpsRequestResolver : IRequestResolver { + private int timeout; + private IRequestResolver fallback; + private HttpClient httpClient; + private void SetUpClient(Uri uri) + { + httpClient = new HttpClient() + { + BaseAddress = uri, + Timeout = TimeSpan.FromMilliseconds(timeout) + //DefaultRequestVersion = new Version(2, 0), + }; + httpClient.DefaultRequestHeaders.Add("Accept", "application/dns-message"); + } + public HttpsRequestResolver(Uri uri, IRequestResolver fallback, int timeout = 5000) { + this.fallback = fallback; + this.timeout = timeout; + SetUpClient(uri); + } + + public HttpsRequestResolver(Uri uri, int timeout = 5000) { + this.fallback = new NullRequestResolver(); + this.timeout = timeout; + SetUpClient(uri); + } + + public async Task Resolve(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { + var httpRequest = new HttpRequestMessage(HttpMethod.Post, "") + { + Content = new ByteArrayContent(request.ToArray(), 0, request.Size) + }; + + httpRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/dns-message"); + var httpResponse = await httpClient.SendAsync(httpRequest).WithCancellationTimeout(TimeSpan.FromMilliseconds(timeout), cancellationToken).ConfigureAwait(false); + Byte[] buffer = await httpResponse.Content.ReadAsByteArrayAsync(); + Response response = Response.FromArray(buffer); + + if (response.Truncated) + { + return await fallback.Resolve(request, cancellationToken).ConfigureAwait(false); + } + + return new ClientResponse(request, response, buffer); + } + } +} diff --git a/DNS/Server/DnsServer.cs b/DNS/Server/DnsServer.cs index efd997b..41bbbb5 100644 --- a/DNS/Server/DnsServer.cs +++ b/DNS/Server/DnsServer.cs @@ -35,6 +35,10 @@ public DnsServer(IRequestResolver resolver, IPAddress endServer, int port = DEFA public DnsServer(IRequestResolver resolver, string endServer, int port = DEFAULT_PORT) : this(resolver, IPAddress.Parse(endServer), port) {} + public DnsServer(IRequestResolver resolver, Uri uri) : + this(new FallbackRequestResolver(resolver, new HttpsRequestResolver(uri))) + { } + public DnsServer(IPEndPoint endServer) : this(new UdpRequestResolver(endServer)) {} @@ -47,6 +51,9 @@ public DnsServer(string endServer, int port = DEFAULT_PORT) : public DnsServer(IRequestResolver resolver) { this.resolver = resolver; } + public DnsServer(Uri uri) : + this(new HttpsRequestResolver(uri)) + { } public Task Listen(int port = DEFAULT_PORT, IPAddress ip = null) { return Listen(new IPEndPoint(ip ?? IPAddress.Any, port)); @@ -65,7 +72,7 @@ public async Task Listen(IPEndPoint endpoint) { udp.Client.IOControl(SIO_UDP_CONNRESET, new byte[4], new byte[4]); } } catch (SocketException e) { - OnError(e); + OnError(e, endpoint); return; } } @@ -76,14 +83,14 @@ void ReceiveCallback(IAsyncResult result) { try { IPEndPoint remote = new IPEndPoint(0, 0); data = udp.EndReceive(result, ref remote); - HandleRequest(data, remote); + Task.Run(()=> HandleRequest(data, remote)); } catch (ObjectDisposedException) { // run should already be false run = false; } catch (SocketException e) { - OnError(e); + OnError(e, endpoint); } if (run) udp.BeginReceive(ReceiveCallback, null); @@ -114,8 +121,8 @@ protected virtual void Dispose(bool disposing) { } } - private void OnError(Exception e) { - OnEvent(Errored, new ErroredEventArgs(e)); + private void OnError(Exception e, IPEndPoint remote) { + OnEvent(Errored, new ErroredEventArgs(e, remote)); } private async void HandleRequest(byte[] data, IPEndPoint remote) { @@ -132,12 +139,12 @@ await udp .SendAsync(response.ToArray(), response.Size, remote) .WithCancellationTimeout(TimeSpan.FromMilliseconds(UDP_TIMEOUT)).ConfigureAwait(false); } - catch (SocketException e) { OnError(e); } - catch (ArgumentException e) { OnError(e); } - catch (IndexOutOfRangeException e) { OnError(e); } - catch (OperationCanceledException e) { OnError(e); } - catch (IOException e) { OnError(e); } - catch (ObjectDisposedException e) { OnError(e); } + catch (SocketException e) { OnError(e, remote); } + catch (ArgumentException e) { OnError(e, remote); } + catch (IndexOutOfRangeException e) { OnError(e, remote); } + catch (OperationCanceledException e) { OnError(e, remote); } + catch (IOException e) { OnError(e, remote); } + catch (ObjectDisposedException e) { OnError(e, remote); } catch (ResponseException e) { IResponse response = e.Response; @@ -152,7 +159,7 @@ await udp } catch (SocketException) {} catch (OperationCanceledException) {} - finally { OnError(e); } + finally { OnError(e, remote); } } } @@ -183,11 +190,13 @@ public RespondedEventArgs(IRequest request, IResponse response, byte[] data, IPE } public class ErroredEventArgs : EventArgs { - public ErroredEventArgs(Exception e) { + public ErroredEventArgs(Exception e, IPEndPoint remote) { Exception = e; + Remote = remote; } public Exception Exception { get; } + public IPEndPoint Remote { get; } } private class FallbackRequestResolver : IRequestResolver { diff --git a/DNS/Server/MasterFile.cs b/DNS/Server/MasterFile.cs index b6a1ad5..a4f86bb 100644 --- a/DNS/Server/MasterFile.cs +++ b/DNS/Server/MasterFile.cs @@ -19,7 +19,10 @@ protected static bool Matches(Domain domain, Domain entry) { for (int i = 0; i < labels.Length; i++) { string label = labels[i]; - patterns[i] = label == "*" ? "(\\w+)" : Regex.Escape(label); + // note: support hyphens '-' in domain name + // don't support underline '_' in name + patterns[i] = label == "*" ? "([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)" : Regex.Escape(label); + //patterns[i] = label == "*" ? "(\\w+)" : Regex.Escape(label); } Regex re = new Regex("^" + string.Join("\\.", patterns) + "$", RegexOptions.IgnoreCase); diff --git a/Examples/ClientServer/ClientServerExample.cs b/Examples/ClientServer/ClientServerExample.cs index 65840c8..0d85d94 100644 --- a/Examples/ClientServer/ClientServerExample.cs +++ b/Examples/ClientServer/ClientServerExample.cs @@ -13,7 +13,8 @@ public static void Main(string[] args) { public async static Task MainAsync() { MasterFile masterFile = new MasterFile(); - DnsServer server = new DnsServer(masterFile, "8.8.8.8"); + //Dns Proxy to Dns over https + DnsServer server = new DnsServer(masterFile, new Uri("https://1.1.1.1/dns-query")); masterFile.AddIPAddressResourceRecord("google.com", "127.0.0.1"); diff --git a/Examples/Examples.csproj b/Examples/Examples.csproj index 90cb997..8d55bd3 100644 --- a/Examples/Examples.csproj +++ b/Examples/Examples.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + net7.0 diff --git a/README.md b/README.md index 5a93fa6..4c1ef75 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # DNS -A DNS library written in C# targeting .NET Standard 2.0. Versions prior to version two (2.0.0) were written for .NET 4 using blocking network operations. Version two and above use asynchronous operations. +A DNS library written in C# targeting .NET Standard 2.0. -Available through NuGet. +Only original repo is currently available through NuGet. Install-Package DNS -[![Test](https://github.com/kapetan/dns/actions/workflows/test.yml/badge.svg)](https://github.com/kapetan/dns/actions/workflows/test.yml) +[![Test](https://github.com/jgilm/dns/actions/workflows/test.yml/badge.svg)](https://github.com/jgilm/dns/actions/workflows/test.yml) # Usage @@ -99,7 +99,7 @@ It's also possible to modify the `request` instance in the `server.Requested` ca ### Request Resolver -The `DnsServer`, `DnsClient` and `ClientRequest` classes also accept an instance implementing the `IRequestResolver` interface, which they internally use to resolve DNS requests. Some of the default implementations are `UdpRequestResolver`, `TcpRequestResolver` and `MasterFile` classes. But it's also possible to provide a custom request resolver. +The `DnsServer`, `DnsClient` and `ClientRequest` classes also accept an instance implementing the `IRequestResolver` interface, which they internally use to resolve DNS requests. Some of the default implementations are `UdpRequestResolver`, `TcpRequestResolver`, `HttpsRequestResolver` and `MasterFile` classes. But it's also possible to provide a custom request resolver. ```C# // A request resolver that resolves all dns queries to localhost @@ -124,3 +124,17 @@ DnsServer server = new DnsServer(new LocalRequestResolver()); await server.Listen(); ``` + +### Support Dns Over Https + +DNS over HTTPS (DoH), DNS queries and responses are encrypted and sent via the HTTP or HTTP/2 protocols. DoH ensures that attackers cannot forge or alter DNS traffic. DoH uses port 443, which is the standard HTTPS traffic port, to wrap the DNS query in an HTTPS request. DNS queries and responses are camouflaged within other HTTPS traffic, since it all comes and goes from the same port. + +[https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/) + +```C# +// DoH Proxy Server +MasterFile masterFile = new MasterFile(); +DnsServer server = new DnsServer(masterFile, new Uri("https://1.1.1.1/dns-query")); +// DoH Client +DnsClient client = new DnsClient(new Uri("https://1.1.1.1/dns-query")) +``` diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index a2f6412..ec3c71d 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.2 + net7.0