DnsServer: Added notify allowed networks feature. Updated default config values for mitigating pulsing DoS attack. Added qpm bypass list feature. Updated ProcessUdpRequestAsync() to use buffer from array pool. Updated ProcessUpdateQueryAsync() to find closest zone to update and added support for forwarder zones. Updated ProcessCNAMEAsync() to prevent duplicate entries in response. Updated RecursiveResolveAsync() to simplify serve stale implementation. Updated PrepareRecursiveResolveResponse() to set correct AD flag value. Updated StartAsync() to update root servers list by priming query. Code refactoring changes done.

This commit is contained in:
Shreyas Zare
2024-02-04 17:59:51 +05:30
parent 4912e03f41
commit ff525d7d30

View File

@@ -1,6 +1,6 @@
/*
Technitium DNS Server
Copyright (C) 2023 Shreyas Zare (shreyas@technitium.com)
Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -89,7 +90,7 @@ namespace DnsServerCore.Dns
#region variables
internal const int MAX_CNAME_HOPS = 16;
const int SERVE_STALE_WAIT_TIME = 1800;
const int SERVE_STALE_TIME_DIFFERENCE = 200; //200ms before client timeout as per RFC 8767
static readonly IPEndPoint IPENDPOINT_ANY_0 = new IPEndPoint(IPAddress.Any, 0);
static readonly IReadOnlyCollection<DnsARecordData> _aRecords = new DnsARecordData[] { new DnsARecordData(IPAddress.Any) };
@@ -125,6 +126,7 @@ namespace DnsServerCore.Dns
readonly StatsManager _stats;
IReadOnlyCollection<NetworkAddress> _zoneTransferAllowedNetworks;
IReadOnlyCollection<NetworkAddress> _notifyAllowedNetworks;
bool _preferIPv6;
ushort _udpPayloadSize = DnsDatagram.EDNS_DEFAULT_UDP_PAYLOAD_SIZE;
bool _dnssecValidation = true;
@@ -133,13 +135,14 @@ namespace DnsServerCore.Dns
byte _eDnsClientSubnetIPv4PrefixLength = 24;
byte _eDnsClientSubnetIPv6PrefixLength = 56;
int _qpmLimitRequests = 0;
int _qpmLimitErrors = 0;
int _qpmLimitRequests = 6000; //100qps
int _qpmLimitErrors = 600; //10qps
int _qpmLimitSampleMinutes = 5;
int _qpmLimitIPv4PrefixLength = 24;
int _qpmLimitIPv6PrefixLength = 56;
IReadOnlyCollection<NetworkAddress> _qpmLimitBypassList;
int _clientTimeout = 4000;
int _clientTimeout = 2000;
int _tcpSendTimeout = 10000;
int _tcpReceiveTimeout = 10000;
int _quicIdleTimeout = 60000;
@@ -173,7 +176,7 @@ namespace DnsServerCore.Dns
bool _nsRevalidation;
int _resolverRetries = 2;
int _resolverTimeout = 2000;
int _resolverTimeout = 1500;
int _resolverMaxStackCount = 16;
bool _serveStale = true;
@@ -392,7 +395,10 @@ namespace DnsServerCore.Dns
}
if (IsQpmLimitCrossed(remoteEP.Address))
{
_stats.QueueUpdate(null, remoteEP, protocol, null);
continue;
}
DnsDatagram request = DnsDatagram.ReadFrom(recvBufferStream);
request.SetMetadata(new NameServerAddress(new IPEndPoint(result.PacketInformation.Address, localPort), DnsTransportProtocol.Udp));
@@ -441,23 +447,30 @@ namespace DnsServerCore.Dns
private async Task ProcessUdpRequestAsync(Socket udpListener, IPEndPoint remoteEP, IPEndPoint returnEP, DnsTransportProtocol protocol, DnsDatagram request)
{
byte[] sendBuffer = null;
try
{
DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, protocol, IsRecursionAllowed(remoteEP.Address));
if (response is null)
{
_stats.QueueUpdate(null, remoteEP, protocol, null);
return; //drop request
}
//send response
byte[] sendBuffer;
int sendBufferSize;
if (request.EDNS is null)
sendBuffer = new byte[512];
sendBufferSize = 512;
else if (request.EDNS.UdpPayloadSize > _udpPayloadSize)
sendBuffer = new byte[_udpPayloadSize];
sendBufferSize = _udpPayloadSize;
else
sendBuffer = new byte[request.EDNS.UdpPayloadSize];
sendBufferSize = request.EDNS.UdpPayloadSize;
using (MemoryStream sendBufferStream = new MemoryStream(sendBuffer))
sendBuffer = ArrayPool<byte>.Shared.Rent(sendBufferSize);
using (MemoryStream sendBufferStream = new MemoryStream(sendBuffer, 0, sendBufferSize))
{
try
{
@@ -523,6 +536,11 @@ namespace DnsServerCore.Dns
_queryLog?.Write(remoteEP, protocol, request, null);
_log?.Write(remoteEP, protocol, ex);
}
finally
{
if (sendBuffer is not null)
ArrayPool<byte>.Shared.Return(sendBuffer);
}
}
private async Task AcceptConnectionAsync(Socket tcpListener, DnsTransportProtocol protocol)
@@ -630,7 +648,10 @@ namespace DnsServerCore.Dns
while (true)
{
if (IsQpmLimitCrossed(remoteEP.Address))
{
_stats.QueueUpdate(null, remoteEP, protocol, null);
break;
}
DnsDatagram request;
@@ -678,6 +699,8 @@ namespace DnsServerCore.Dns
if (response is null)
{
await stream.DisposeAsync();
_stats.QueueUpdate(null, remoteEP, protocol, null);
return; //drop request
}
@@ -747,7 +770,10 @@ namespace DnsServerCore.Dns
while (true)
{
if (IsQpmLimitCrossed(quicConnection.RemoteEndPoint.Address))
{
_stats.QueueUpdate(null, quicConnection.RemoteEndPoint, DnsTransportProtocol.Quic, null);
break;
}
QuicStream quicStream = await quicConnection.AcceptInboundStreamAsync();
@@ -806,7 +832,10 @@ namespace DnsServerCore.Dns
//process request async
DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, DnsTransportProtocol.Quic, IsRecursionAllowed(remoteEP.Address));
if (response is null)
{
_stats.QueueUpdate(null, remoteEP, DnsTransportProtocol.Quic, null);
return; //drop request
}
//send response
await response.WriteToTcpAsync(quicStream, sharedBuffer);
@@ -844,6 +873,8 @@ namespace DnsServerCore.Dns
if (IsQpmLimitCrossed(remoteEP.Address))
{
_stats.QueueUpdate(null, remoteEP, DnsTransportProtocol.Https, null);
response.StatusCode = 429;
await response.WriteAsync("Too Many Requests");
return;
@@ -869,7 +900,7 @@ namespace DnsServerCore.Dns
case "GET":
bool acceptsDoH = false;
string requestAccept = request.Headers["Accept"];
string requestAccept = request.Headers.Accept;
if (string.IsNullOrEmpty(requestAccept))
{
acceptsDoH = true;
@@ -888,7 +919,7 @@ namespace DnsServerCore.Dns
if (!acceptsDoH)
{
response.Redirect((request.IsHttps ? "https://" : "http://") + request.Headers["Host"]);
response.Redirect((request.IsHttps ? "https://" : "http://") + request.Headers.Host);
return;
}
@@ -912,13 +943,13 @@ namespace DnsServerCore.Dns
using (MemoryStream mS = new MemoryStream(Convert.FromBase64String(dnsRequestBase64Url)))
{
dnsRequest = DnsDatagram.ReadFrom(mS);
dnsRequest.SetMetadata(new NameServerAddress(new Uri(context.Request.GetDisplayUrl()), context.GetLocalIpAddress(), DnsTransportProtocol.Https));
dnsRequest.SetMetadata(new NameServerAddress(new Uri(context.Request.GetDisplayUrl()), context.GetLocalIpAddress()));
}
break;
case "POST":
if (!string.Equals(request.Headers["Content-Type"], "application/dns-message", StringComparison.OrdinalIgnoreCase))
if (!string.Equals(request.Headers.ContentType, "application/dns-message", StringComparison.OrdinalIgnoreCase))
{
response.StatusCode = 415;
await response.WriteAsync("Unsupported Media Type");
@@ -931,7 +962,7 @@ namespace DnsServerCore.Dns
mS.Position = 0;
dnsRequest = DnsDatagram.ReadFrom(mS);
dnsRequest.SetMetadata(new NameServerAddress(new Uri(context.Request.GetDisplayUrl()), context.GetLocalIpAddress(), DnsTransportProtocol.Https));
dnsRequest.SetMetadata(new NameServerAddress(new Uri(context.Request.GetDisplayUrl()), context.GetLocalIpAddress()));
}
break;
@@ -945,6 +976,8 @@ namespace DnsServerCore.Dns
{
//drop request
context.Connection.RequestClose();
_stats.QueueUpdate(null, remoteEP, DnsTransportProtocol.Https, null);
return;
}
@@ -1034,7 +1067,7 @@ namespace DnsServerCore.Dns
return null; //drop request
case DnsRequestControllerAction.DropWithRefused:
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Question, null, null, null, request.EDNS is null ? ushort.MinValue : _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative }; //drop request with refused
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.Refused, request.Question, null, null, null, request.EDNS is null ? ushort.MinValue : _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative }; //drop request with refused
}
}
catch (Exception ex)
@@ -1050,7 +1083,7 @@ namespace DnsServerCore.Dns
_log?.Write(remoteEP, protocol, request.ParsingException);
//format error response
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.FormatError, request.Question, null, null, null, request.EDNS is null ? ushort.MinValue : _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.FormatError, request.Question, null, null, null, request.EDNS is null ? ushort.MinValue : _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative };
}
if (request.IsSigned)
@@ -1077,7 +1110,7 @@ namespace DnsServerCore.Dns
if (request.EDNS is not null)
{
if (request.EDNS.Version != 0)
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.BADVERS, request.Question, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.BADVERS, request.Question, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None) { Tag = DnsServerResponseType.Authoritative };
}
DnsDatagram response = await ProcessQueryAsync(request, remoteEP, protocol, isRecursionAllowed, false, null);
@@ -1145,10 +1178,10 @@ namespace DnsServerCore.Dns
{
case DnsOpcode.StandardQuery:
if (request.Question.Count != 1)
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
if (request.Question[0].Class != DnsClass.IN)
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
try
{
@@ -1158,7 +1191,7 @@ namespace DnsServerCore.Dns
{
case DnsResourceRecordType.AXFR:
if (protocol == DnsTransportProtocol.Udp)
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, request.CheckingDisabled, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return await ProcessZoneTransferQueryAsync(request, remoteEP, protocol, tsigAuthenticatedKeyName);
@@ -1167,7 +1200,7 @@ namespace DnsServerCore.Dns
case DnsResourceRecordType.FWD:
case DnsResourceRecordType.APP:
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
}
//query authoritative zone
@@ -1175,30 +1208,30 @@ namespace DnsServerCore.Dns
if (response is not null)
{
if ((question.Type == DnsResourceRecordType.ANY) && (protocol == DnsTransportProtocol.Udp)) //force TCP for ANY request
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, true, request.RecursionDesired, isRecursionAllowed, false, false, response.RCODE, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, true, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, response.RCODE, request.Question) { Tag = DnsServerResponseType.Authoritative };
return response;
}
if (!request.RecursionDesired || !isRecursionAllowed)
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
//do recursive query
if ((question.Type == DnsResourceRecordType.ANY) && (protocol == DnsTransportProtocol.Udp)) //force TCP for ANY request
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, true, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NoError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, true, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.NoError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return await ProcessRecursiveQueryAsync(request, remoteEP, protocol, null, _dnssecValidation, false, skipDnsAppAuthoritativeRequestHandlers);
}
catch (InvalidDomainNameException)
{
//format error response
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
}
catch (Exception ex)
{
_log?.Write(remoteEP, protocol, ex);
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.ServerFailure, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.ServerFailure, request.Question) { Tag = DnsServerResponseType.Authoritative };
}
case DnsOpcode.Notify:
@@ -1208,7 +1241,7 @@ namespace DnsServerCore.Dns
return await ProcessUpdateQueryAsync(request, remoteEP, protocol, tsigAuthenticatedKeyName);
default:
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.NotImplemented, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.NotImplemented, request.Question) { Tag = DnsServerResponseType.Authoritative };
}
}
@@ -1218,21 +1251,29 @@ namespace DnsServerCore.Dns
if ((authZoneInfo is null) || (authZoneInfo.Type != AuthZoneType.Secondary) || authZoneInfo.Disabled)
return new DnsDatagram(request.Identifier, true, DnsOpcode.Notify, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.Refused, request.Question) { Tag = DnsServerResponseType.Authoritative };
IPAddress remoteAddress = remoteEP.Address;
bool remoteVerified = false;
IReadOnlyList<NameServerAddress> primaryNameServers = await authZoneInfo.GetPrimaryNameServerAddressesAsync(this);
foreach (NameServerAddress primaryNameServer in primaryNameServers)
async Task<bool> RemoteVerifiedAsync(IPAddress remoteAddress)
{
if (primaryNameServer.IPEndPoint.Address.Equals(remoteAddress))
if (_notifyAllowedNetworks is not null)
{
remoteVerified = true;
break;
foreach (NetworkAddress notifyAllowedNetwork in _notifyAllowedNetworks)
{
if (notifyAllowedNetwork.Contains(remoteAddress))
return true;
}
}
IReadOnlyList<NameServerAddress> primaryNameServers = await authZoneInfo.GetPrimaryNameServerAddressesAsync(this);
foreach (NameServerAddress primaryNameServer in primaryNameServers)
{
if (primaryNameServer.IPEndPoint.Address.Equals(remoteAddress))
return true;
}
return false;
}
if (!remoteVerified)
if (!await RemoteVerifiedAsync(remoteEP.Address))
{
_log?.Write(remoteEP, protocol, "DNS Server refused a NOTIFY request since the request IP address was not recognized by the secondary zone: " + (authZoneInfo.Name == "" ? "<root>" : authZoneInfo.Name));
@@ -1264,7 +1305,7 @@ namespace DnsServerCore.Dns
if (request.Question[0].Class != DnsClass.IN)
return new DnsDatagram(request.Identifier, true, DnsOpcode.Update, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NotAuth, request.Question) { Tag = DnsServerResponseType.Authoritative };
AuthZoneInfo authZoneInfo = _authZoneManager.GetAuthZoneInfo(request.Question[0].Name);
AuthZoneInfo authZoneInfo = _authZoneManager.FindAuthZoneInfo(request.Question[0].Name);
if ((authZoneInfo is null) || authZoneInfo.Disabled)
return new DnsDatagram(request.Identifier, true, DnsOpcode.Update, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NotAuth, request.Question) { Tag = DnsServerResponseType.Authoritative };
@@ -1386,6 +1427,7 @@ namespace DnsServerCore.Dns
switch (authZoneInfo.Type)
{
case AuthZoneType.Primary:
case AuthZoneType.Forwarder:
//update
{
//process prerequisite section
@@ -2107,7 +2149,7 @@ namespace DnsServerCore.Dns
authority = zoneInfo.GetApexRecords(DnsResourceRecordType.SOA);
}
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, rcode, request.Question, null, authority) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, rcode, request.Question, null, authority) { Tag = DnsServerResponseType.Authoritative };
}
else
{
@@ -2132,7 +2174,7 @@ namespace DnsServerCore.Dns
AuthZoneInfo zoneInfo = _authZoneManager.FindAuthZoneInfo(request.Question[0].Name);
IReadOnlyList<DnsResourceRecord> authority = zoneInfo.GetApexRecords(DnsResourceRecordType.SOA);
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, false, DnsResponseCode.ServerFailure, request.Question, null, authority) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, request.OPCODE, false, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, DnsResponseCode.ServerFailure, request.Question, null, authority) { Tag = DnsServerResponseType.Authoritative };
}
}
@@ -2185,7 +2227,7 @@ namespace DnsServerCore.Dns
if (lastRR.Name.Equals(cnameDomain, StringComparison.OrdinalIgnoreCase))
break; //loop detected
DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(cnameDomain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, eDnsClientSubnetOption);
DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, request.CheckingDisabled, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(cnameDomain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, eDnsClientSubnetOption);
//query authoritative zone first
newResponse = await AuthoritativeQueryAsync(newRequest, remoteEP, protocol, isRecursionAllowed, skipDnsAppAuthoritativeRequestHandlers);
@@ -2249,19 +2291,25 @@ namespace DnsServerCore.Dns
bool foundRepeat = false;
foreach (DnsResourceRecord answerRecord in newAnswer)
foreach (DnsResourceRecord newResponseAnswerRecord in newResponse.Answer)
{
if (answerRecord.Type != DnsResourceRecordType.CNAME)
continue;
if (answerRecord.RDATA.Equals(lastRR.RDATA))
if ((newResponseAnswerRecord.Type == DnsResourceRecordType.CNAME) || (newResponseAnswerRecord.Type == DnsResourceRecordType.DNAME))
{
foundRepeat = true;
break;
}
}
foreach (DnsResourceRecord answerRecord in newAnswer)
{
if (newResponseAnswerRecord.Equals(answerRecord))
{
foundRepeat = true;
break;
}
}
newAnswer.AddRange(newResponse.Answer);
if (foundRepeat)
break;
}
newAnswer.Add(newResponseAnswerRecord);
}
if (foundRepeat)
break; //loop detected
@@ -2332,7 +2380,7 @@ namespace DnsServerCore.Dns
do
{
DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, false, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(lastDomain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, eDnsClientSubnetOption);
DnsDatagram newRequest = new DnsDatagram(0, false, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, false, false, request.CheckingDisabled, DnsResponseCode.NoError, new DnsQuestionRecord[] { new DnsQuestionRecord(lastDomain, request.Question[0].Type, request.Question[0].Class) }, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, eDnsClientSubnetOption);
//query authoritative zone first
DnsDatagram newResponse = await AuthoritativeQueryAsync(newRequest, remoteEP, protocol, isRecursionAllowed, skipDnsAppAuthoritativeRequestHandlers);
@@ -2477,7 +2525,7 @@ namespace DnsServerCore.Dns
}
}
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, false, rcode, request.Question, responseAnswer, authority, null) { Tag = response.Tag };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, true, false, request.RecursionDesired, isRecursionAllowed, false, request.CheckingDisabled, rcode, request.Question, responseAnswer, authority, null) { Tag = response.Tag };
}
private DnsDatagram ProcessBlocked(DnsDatagram request)
@@ -2781,7 +2829,7 @@ namespace DnsServerCore.Dns
}
else if ((requestECS.Family != EDnsClientSubnetAddressFamily.IPv4) && (requestECS.Family != EDnsClientSubnetAddressFamily.IPv6))
{
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, request.CheckingDisabled, DnsResponseCode.FormatError, request.Question) { Tag = DnsServerResponseType.Authoritative };
}
else if (requestECS.ConditionalForwardingClientSubnet)
{
@@ -2865,51 +2913,66 @@ namespace DnsServerCore.Dns
if (cachePrefetchOperation)
return null; //return null as prefetch worker thread does not need valid response and thus does not need to wait
DateTime resolverWaitStartTime = DateTime.UtcNow;
//wait till short timeout for response
if (await Task.WhenAny(resolverTask, Task.Delay(SERVE_STALE_WAIT_TIME)) == resolverTask) //1.8 sec wait as per draft-ietf-dnsop-serve-stale-04
if (_serveStale)
{
//resolver signaled
RecursiveResolveResponse response = await resolverTask;
DateTime resolverWaitStartTime = DateTime.UtcNow;
if (response is not null)
return PrepareRecursiveResolveResponse(request, response);
//resolver had exception and no stale record was found
}
else
{
//wait timed out
if (_serveStale)
//wait till short timeout for response
if (await Task.WhenAny(resolverTask, Task.Delay(_clientTimeout - SERVE_STALE_TIME_DIFFERENCE)) == resolverTask) //200ms before client timeout as per RFC 8767
{
//query cache zone to return stale answer (if available) as per draft-ietf-dnsop-serve-stale-04
//resolver signaled
RecursiveResolveResponse response = await resolverTask;
if (response is not null)
return PrepareRecursiveResolveResponse(request, response);
//resolver had exception
}
else
{
//wait timed out
//query cache zone to return stale answer (if available) as per RFC 8767
DnsDatagram staleResponse = QueryCache(request, true);
if (staleResponse is not null)
return staleResponse;
}
//wait till full timeout before responding as ServerFailure
int timeout = Convert.ToInt32(_clientTimeout - (DateTime.UtcNow - resolverWaitStartTime).TotalMilliseconds);
if (timeout > 0)
{
if (await Task.WhenAny(resolverTask, Task.Delay(timeout)) == resolverTask)
//no stale record was found
//wait till full timeout before responding as ServerFailure
int timeout = Convert.ToInt32(_clientTimeout - (DateTime.UtcNow - resolverWaitStartTime).TotalMilliseconds);
if (timeout > 0)
{
//resolver signaled
RecursiveResolveResponse response = await resolverTask;
if (await Task.WhenAny(resolverTask, Task.Delay(timeout)) == resolverTask)
{
//resolver signaled
RecursiveResolveResponse response = await resolverTask;
if (response is not null)
return PrepareRecursiveResolveResponse(request, response);
if (response is not null)
return PrepareRecursiveResolveResponse(request, response);
//resolver had exception
}
}
}
}
else
{
//wait till full client timeout for response
if (await Task.WhenAny(resolverTask, Task.Delay(_clientTimeout)) == resolverTask)
{
//resolver signaled
RecursiveResolveResponse response = await resolverTask;
//no response available from resolver or resolver had exception and no stale record was found
if (response is not null)
return PrepareRecursiveResolveResponse(request, response);
//resolver had exception
}
}
//no response available; respond with ServerFailure
EDnsOption[] options = new EDnsOption[] { new EDnsOption(EDnsOptionCode.EXTENDED_DNS_ERROR, new EDnsExtendedDnsErrorOptionData(EDnsExtendedDnsErrorCode.Other, "Waiting for resolver")) };
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, false, DnsResponseCode.ServerFailure, request.Question, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, options);
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, request.RecursionDesired, true, false, request.CheckingDisabled, DnsResponseCode.ServerFailure, request.Question, null, null, null, _udpPayloadSize, request.DnssecOk ? EDnsHeaderFlags.DNSSEC_OK : EDnsHeaderFlags.None, options);
}
private async Task RecursiveResolveAsync(DnsQuestionRecord question, NetworkAddress eDnsClientSubnet, bool conditionalForwardingClientSubnet, IReadOnlyList<DnsResourceRecord> conditionalForwarders, bool dnssecValidation, bool cachePrefetchOperation, bool cacheRefreshOperation, bool skipDnsAppAuthoritativeRequestHandlers, TaskCompletionSource<RecursiveResolveResponse> taskCompletionSource)
@@ -3278,7 +3341,41 @@ namespace DnsServerCore.Dns
bool dnssecOk = request.DnssecOk;
if (dnssecOk && request.CheckingDisabled)
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, resolveResponse.CheckingDisabledResponse.AuthenticData, resolveResponse.CheckingDisabledResponse.CheckingDisabled, resolveResponse.CheckingDisabledResponse.RCODE, request.Question, resolveResponse.CheckingDisabledResponse.Answer, resolveResponse.CheckingDisabledResponse.Authority, RemoveOPTFromAdditional(resolveResponse.CheckingDisabledResponse.Additional, true), _udpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, resolveResponse.CheckingDisabledResponse.EDNS?.Options);
{
bool authenticData = false;
if (dnssecOk)
{
if (resolveResponse.CheckingDisabledResponse.Answer.Count > 0)
{
authenticData = true;
foreach (DnsResourceRecord record in resolveResponse.CheckingDisabledResponse.Answer)
{
if (record.DnssecStatus != DnssecStatus.Secure)
{
authenticData = false;
break;
}
}
}
else if (resolveResponse.CheckingDisabledResponse.Authority.Count > 0)
{
authenticData = true;
foreach (DnsResourceRecord record in resolveResponse.CheckingDisabledResponse.Authority)
{
if (record.DnssecStatus != DnssecStatus.Secure)
{
authenticData = false;
break;
}
}
}
}
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, authenticData, true, resolveResponse.CheckingDisabledResponse.RCODE, request.Question, resolveResponse.CheckingDisabledResponse.Answer, resolveResponse.CheckingDisabledResponse.Authority, RemoveOPTFromAdditional(resolveResponse.CheckingDisabledResponse.Additional, true), _udpPayloadSize, EDnsHeaderFlags.DNSSEC_OK, resolveResponse.CheckingDisabledResponse.EDNS?.Options);
}
DnsDatagram response = resolveResponse.Response;
IReadOnlyList<DnsResourceRecord> answer = response.Answer;
@@ -3459,7 +3556,41 @@ namespace DnsServerCore.Dns
}
}
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, response.AuthenticData, response.CheckingDisabled, response.RCODE, request.Question, answer, authority, additional);
{
bool authenticData = false;
if (dnssecOk)
{
if (answer.Count > 0)
{
authenticData = true;
foreach (DnsResourceRecord record in answer)
{
if (record.DnssecStatus != DnssecStatus.Secure)
{
authenticData = false;
break;
}
}
}
else if (authority.Count > 0)
{
authenticData = true;
foreach (DnsResourceRecord record in authority)
{
if (record.DnssecStatus != DnssecStatus.Secure)
{
authenticData = false;
break;
}
}
}
}
return new DnsDatagram(request.Identifier, true, DnsOpcode.StandardQuery, false, false, true, true, authenticData, request.CheckingDisabled, response.RCODE, request.Question, answer, authority, additional);
}
}
private static IReadOnlyList<DnsResourceRecord> RemoveOPTFromAdditional(IReadOnlyList<DnsResourceRecord> additional, bool dnssecOk)
@@ -3802,6 +3933,15 @@ namespace DnsServerCore.Dns
if (IPAddress.IsLoopback(remoteIP))
return false;
if (_qpmLimitBypassList is not null)
{
foreach (NetworkAddress networkAddress in _qpmLimitBypassList)
{
if (networkAddress.Contains(remoteIP))
return false;
}
}
IPAddress remoteSubnet;
switch (remoteIP.AddressFamily)
@@ -3968,8 +4108,8 @@ namespace DnsServerCore.Dns
{
OnPrepareResponse = delegate (StaticFileResponseContext ctx)
{
ctx.Context.Response.Headers.Add("X-Robots-Tag", "noindex, nofollow");
ctx.Context.Response.Headers.Add("Cache-Control", "private, max-age=300");
ctx.Context.Response.Headers["X-Robots-Tag"] = "noindex, nofollow";
ctx.Context.Response.Headers.CacheControl = "private, max-age=300";
},
ServeUnknownFileTypes = true
});
@@ -4113,7 +4253,7 @@ namespace DnsServerCore.Dns
public async Task StartAsync()
{
if (_disposed)
throw new ObjectDisposedException("DnsServer");
ObjectDisposedException.ThrowIf(_disposed, this);
if (_state != ServiceState.Stopped)
throw new InvalidOperationException("DNS Server is already running.");
@@ -4400,6 +4540,19 @@ namespace DnsServerCore.Dns
UdpClientConnection.CreateSocketPool(_preferIPv6);
});
}
//update root servers list by priming query async
_ = Task.Run(async delegate ()
{
try
{
await DnsClient.UpdateRootServersAsync(_proxy, _preferIPv6, _resolverRetries, _resolverTimeout);
}
catch (Exception ex)
{
_log.Write("Error while updating root servers list by priming query: " + ex.ToString());
}
});
}
public async Task StopAsync()
@@ -4566,7 +4719,25 @@ namespace DnsServerCore.Dns
public IReadOnlyCollection<NetworkAddress> ZoneTransferAllowedNetworks
{
get { return _zoneTransferAllowedNetworks; }
set { _zoneTransferAllowedNetworks = value; }
set
{
if ((value is not null) && (value.Count > byte.MaxValue))
throw new ArgumentOutOfRangeException(nameof(ZoneTransferAllowedNetworks), "Networks cannot be more than 255.");
_zoneTransferAllowedNetworks = value;
}
}
public IReadOnlyCollection<NetworkAddress> NotifyAllowedNetworks
{
get { return _notifyAllowedNetworks; }
set
{
if ((value is not null) && (value.Count > byte.MaxValue))
throw new ArgumentOutOfRangeException(nameof(NotifyAllowedNetworks), "Networks cannot be more than 255.");
_notifyAllowedNetworks = value;
}
}
public bool PreferIPv6
@@ -4741,6 +4912,18 @@ namespace DnsServerCore.Dns
}
}
public IReadOnlyCollection<NetworkAddress> QpmLimitBypassList
{
get { return _qpmLimitBypassList; }
set
{
if ((value is not null) && (value.Count > byte.MaxValue))
throw new ArgumentOutOfRangeException(nameof(QpmLimitBypassList), "Networks cannot be more than 255.");
_qpmLimitBypassList = value;
}
}
public int ClientTimeout
{
get { return _clientTimeout; }
@@ -5108,7 +5291,13 @@ namespace DnsServerCore.Dns
public IReadOnlyCollection<NetworkAddress> BlockingBypassList
{
get { return _blockingBypassList; }
set { _blockingBypassList = value; }
set
{
if ((value is not null) && (value.Count > byte.MaxValue))
throw new ArgumentOutOfRangeException(nameof(BlockingBypassList), "Networks cannot be more than 255.");
_blockingBypassList = value;
}
}
public DnsServerBlockingType BlockingType