DnsServer: Implemented UDP and TCP over PROXY protocol.

This commit is contained in:
Shreyas Zare
2023-08-12 13:04:47 +05:30
parent 481a92b494
commit db1f97c2c9

View File

@@ -49,6 +49,7 @@ using TechnitiumLibrary.Net.Dns.ClientConnection;
using TechnitiumLibrary.Net.Dns.EDnsOptions;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
using TechnitiumLibrary.Net.Proxy;
using TechnitiumLibrary.Net.ProxyProtocol;
namespace DnsServerCore.Dns
{
@@ -103,7 +104,9 @@ namespace DnsServerCore.Dns
NameServerAddress _thisServer;
readonly List<Socket> _udpListeners = new List<Socket>();
readonly List<Socket> _udpProxyListeners = new List<Socket>();
readonly List<Socket> _tcpListeners = new List<Socket>();
readonly List<Socket> _tcpProxyListeners = new List<Socket>();
readonly List<Socket> _tlsListeners = new List<Socket>();
readonly List<QuicListener> _quicListeners = new List<QuicListener>();
@@ -140,10 +143,14 @@ namespace DnsServerCore.Dns
int _quicMaxInboundStreams = 100;
int _listenBacklog = 100;
bool _enableDnsOverUdpProxy;
bool _enableDnsOverTcpProxy;
bool _enableDnsOverHttp;
bool _enableDnsOverTls;
bool _enableDnsOverHttps;
bool _enableDnsOverQuic;
int _dnsOverUdpProxyPort = 538;
int _dnsOverTcpProxyPort = 538;
int _dnsOverHttpPort = 80;
int _dnsOverTlsPort = 853;
int _dnsOverHttpsPort = 443;
@@ -303,9 +310,15 @@ namespace DnsServerCore.Dns
#region private
private async Task ReadUdpRequestAsync(Socket udpListener)
private async Task ReadUdpRequestAsync(Socket udpListener, DnsTransportProtocol protocol)
{
byte[] recvBuffer = new byte[DnsDatagram.EDNS_MAX_UDP_PAYLOAD_SIZE];
byte[] recvBuffer;
if (protocol == DnsTransportProtocol.UdpProxy)
recvBuffer = new byte[DnsDatagram.EDNS_MAX_UDP_PAYLOAD_SIZE + 256];
else
recvBuffer = new byte[DnsDatagram.EDNS_MAX_UDP_PAYLOAD_SIZE];
using MemoryStream recvBufferStream = new MemoryStream(recvBuffer);
try
@@ -357,17 +370,33 @@ namespace DnsServerCore.Dns
if (result.RemoteEndPoint is not IPEndPoint remoteEP)
continue;
if (IsQpmLimitCrossed(remoteEP.Address))
continue;
try
{
recvBufferStream.Position = 0;
recvBufferStream.SetLength(result.ReceivedBytes);
IPEndPoint returnEP = remoteEP;
if (protocol == DnsTransportProtocol.UdpProxy)
{
if (!NetUtilities.IsPrivateIP(remoteEP.Address))
{
//intentionally blocking public IP addresses from using DNS-over-UDP-PROXY
//this feature is intended to be used with a reverse proxy or load balancer on private network
continue;
}
ProxyProtocolStream proxyStream = await ProxyProtocolStream.CreateAsServerAsync(recvBufferStream);
remoteEP = new IPEndPoint(proxyStream.SourceAddress, proxyStream.SourcePort);
recvBufferStream.Position = proxyStream.DataOffset;
}
if (IsQpmLimitCrossed(remoteEP.Address))
continue;
DnsDatagram request = DnsDatagram.ReadFrom(recvBufferStream);
_ = ProcessUdpRequestAsync(udpListener, remoteEP, request);
_ = ProcessUdpRequestAsync(udpListener, remoteEP, returnEP, protocol, request);
}
catch (EndOfStreamException)
{
@@ -375,7 +404,7 @@ namespace DnsServerCore.Dns
}
catch (Exception ex)
{
_log?.Write(remoteEP, DnsTransportProtocol.Udp, ex);
_log?.Write(remoteEP, protocol, ex);
}
}
}
@@ -409,11 +438,11 @@ namespace DnsServerCore.Dns
}
}
private async Task ProcessUdpRequestAsync(Socket udpListener, IPEndPoint remoteEP, DnsDatagram request)
private async Task ProcessUdpRequestAsync(Socket udpListener, IPEndPoint remoteEP, IPEndPoint returnEP, DnsTransportProtocol protocol, DnsDatagram request)
{
try
{
DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, DnsTransportProtocol.Udp, IsRecursionAllowed(remoteEP.Address));
DnsDatagram response = await PreProcessQueryAsync(request, remoteEP, protocol, IsRecursionAllowed(remoteEP.Address));
if (response is null)
return; //drop request
@@ -479,19 +508,19 @@ namespace DnsServerCore.Dns
}
//send dns datagram async
await udpListener.SendToAsync(new ArraySegment<byte>(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.None, remoteEP);
await udpListener.SendToAsync(new ArraySegment<byte>(sendBuffer, 0, (int)sendBufferStream.Position), SocketFlags.None, returnEP);
}
_queryLog?.Write(remoteEP, DnsTransportProtocol.Udp, request, response);
_stats.QueueUpdate(request, remoteEP, DnsTransportProtocol.Udp, response);
_queryLog?.Write(remoteEP, protocol, request, response);
_stats.QueueUpdate(request, remoteEP, protocol, response);
}
catch (Exception ex)
{
if ((_state == ServiceState.Stopping) || (_state == ServiceState.Stopped))
return; //server stopping
_queryLog?.Write(remoteEP, DnsTransportProtocol.Udp, request, null);
_log?.Write(remoteEP, DnsTransportProtocol.Udp, ex);
_queryLog?.Write(remoteEP, protocol, request, null);
_log?.Write(remoteEP, protocol, ex);
}
}
@@ -553,6 +582,20 @@ namespace DnsServerCore.Dns
await ReadStreamRequestAsync(tlsStream, remoteEP, protocol);
break;
case DnsTransportProtocol.TcpProxy:
if (!NetUtilities.IsPrivateIP(remoteEP.Address))
{
//intentionally blocking public IP addresses from using DNS-over-TCP-PROXY
//this feature is intended to be used with a reverse proxy or load balancer on private network
return;
}
ProxyProtocolStream proxyStream = await ProxyProtocolStream.CreateAsServerAsync(new NetworkStream(socket)).WithTimeout(_tcpReceiveTimeout);
remoteEP = new IPEndPoint(proxyStream.SourceAddress, proxyStream.SourcePort);
await ReadStreamRequestAsync(proxyStream, remoteEP, protocol);
break;
default:
throw new InvalidOperationException();
}
@@ -4012,6 +4055,45 @@ namespace DnsServerCore.Dns
udpListener?.Dispose();
}
if (_enableDnsOverUdpProxy)
{
IPEndPoint udpProxyEP = new IPEndPoint(localEP.Address, _dnsOverUdpProxyPort);
Socket udpProxyListener = null;
try
{
udpProxyListener = new Socket(udpProxyEP.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
#region this code ignores ICMP port unreachable responses which creates SocketException in ReceiveFrom()
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
const uint IOC_IN = 0x80000000;
const uint IOC_VENDOR = 0x18000000;
const uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
udpProxyListener.IOControl((IOControlCode)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
}
#endregion
udpProxyListener.ReceiveBufferSize = 512 * 1024;
udpProxyListener.SendBufferSize = 512 * 1024;
udpProxyListener.Bind(udpProxyEP);
_udpProxyListeners.Add(udpProxyListener);
_log?.Write(udpProxyEP, DnsTransportProtocol.UdpProxy, "DNS Server was bound successfully.");
}
catch (Exception ex)
{
_log?.Write(udpProxyEP, DnsTransportProtocol.UdpProxy, "DNS Server failed to bind.\r\n" + ex.ToString());
udpProxyListener?.Dispose();
}
}
Socket tcpListener = null;
try
@@ -4032,6 +4114,30 @@ namespace DnsServerCore.Dns
tcpListener?.Dispose();
}
if (_enableDnsOverTcpProxy)
{
IPEndPoint tcpProxyEP = new IPEndPoint(localEP.Address, _dnsOverTcpProxyPort);
Socket tcpProxyListner = null;
try
{
tcpProxyListner = new Socket(tcpProxyEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
tcpProxyListner.Bind(tcpProxyEP);
tcpProxyListner.Listen(_listenBacklog);
_tcpProxyListeners.Add(tcpProxyListner);
_log?.Write(tcpProxyEP, DnsTransportProtocol.TcpProxy, "DNS Server was bound successfully.");
}
catch (Exception ex)
{
_log?.Write(tcpProxyEP, DnsTransportProtocol.TcpProxy, "DNS Server failed to bind.\r\n" + ex.ToString());
tcpProxyListner?.Dispose();
}
}
if (_enableDnsOverTls && (_certificateCollection is not null))
{
IPEndPoint tlsEP = new IPEndPoint(localEP.Address, _dnsOverTlsPort);
@@ -4109,7 +4215,18 @@ namespace DnsServerCore.Dns
{
_ = Task.Factory.StartNew(delegate ()
{
return ReadUdpRequestAsync(udpListener);
return ReadUdpRequestAsync(udpListener, DnsTransportProtocol.Udp);
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _queryTaskScheduler);
}
}
foreach (Socket udpProxyListener in _udpProxyListeners)
{
for (int i = 0; i < listenerTaskCount; i++)
{
_ = Task.Factory.StartNew(delegate ()
{
return ReadUdpRequestAsync(udpProxyListener, DnsTransportProtocol.UdpProxy);
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _queryTaskScheduler);
}
}
@@ -4125,6 +4242,17 @@ namespace DnsServerCore.Dns
}
}
foreach (Socket tcpProxyListener in _tcpProxyListeners)
{
for (int i = 0; i < listenerTaskCount; i++)
{
_ = Task.Factory.StartNew(delegate ()
{
return AcceptConnectionAsync(tcpProxyListener, DnsTransportProtocol.TcpProxy);
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _queryTaskScheduler);
}
}
foreach (Socket tlsListener in _tlsListeners)
{
for (int i = 0; i < listenerTaskCount; i++)
@@ -4208,9 +4336,15 @@ namespace DnsServerCore.Dns
foreach (Socket udpListener in _udpListeners)
udpListener.Dispose();
foreach (Socket udpProxyListener in _udpProxyListeners)
udpProxyListener.Dispose();
foreach (Socket tcpListener in _tcpListeners)
tcpListener.Dispose();
foreach (Socket tcpProxyListener in _tcpProxyListeners)
tcpProxyListener.Dispose();
foreach (Socket tlsListener in _tlsListeners)
tlsListener.Dispose();
@@ -4218,7 +4352,9 @@ namespace DnsServerCore.Dns
await quicListener.DisposeAsync();
_udpListeners.Clear();
_udpProxyListeners.Clear();
_tcpListeners.Clear();
_tcpProxyListeners.Clear();
_tlsListeners.Clear();
_quicListeners.Clear();
@@ -4553,6 +4689,18 @@ namespace DnsServerCore.Dns
set { _listenBacklog = value; }
}
public bool EnableDnsOverUdpProxy
{
get { return _enableDnsOverUdpProxy; }
set { _enableDnsOverUdpProxy = value; }
}
public bool EnableDnsOverTcpProxy
{
get { return _enableDnsOverTcpProxy; }
set { _enableDnsOverTcpProxy = value; }
}
public bool EnableDnsOverHttp
{
get { return _enableDnsOverHttp; }
@@ -4577,6 +4725,18 @@ namespace DnsServerCore.Dns
set { _enableDnsOverQuic = value; }
}
public int DnsOverUdpProxyPort
{
get { return _dnsOverUdpProxyPort; }
set { _dnsOverUdpProxyPort = value; }
}
public int DnsOverTcpProxyPort
{
get { return _dnsOverTcpProxyPort; }
set { _dnsOverTcpProxyPort = value; }
}
public int DnsOverHttpPort
{
get { return _dnsOverHttpPort; }