DNS是域名系统的缩写。DNS支持使用TCP和UDP协议,使用53号端口。可能是国产教科书的缘故,许多人都不知道DNS还可以在TCP协议上传输。当然,国内的绝大多数DNS服务器都不支持TCP协议。本文讲述如何动手制作一个DNS代理。
你可能要问我:满世界都是DNS服务器,为什么要写一个DNS代理?
答案很简单:
1、某些网络为了节约成本没有架设DNS服务器,需要一个轻量级的解决方案;
2、升级的DNS代理可以实现一些企业需要的管理功能;
3、我们国家防火墙看不懂TCP封装的DNS数据;
4、学习网络协议。
如果你需要一个增强版的DNS代理,比如可以修改DNS请求和回答,可以参考使用此开源类库:ARSoft.Tools.Net。
至于这个DNS代理能给你带来什么,还要由你自己决定。
本文目标:制作一个稳定的、支持TCP和UDP协议的、可强制使用TCP协议请求的、支持IPv6的DNS代理。
既然提到了要支持TCP协议,就简单提一下TCP协议封装和UDP协议封装的差异:UDP是直接传送消息;TCP封装的消息在消息前增加了所跟随消息的长度数据,其占用两个字节,使用大端序作为其字节序。
那么我们需要做的事情是:
1、同时监听TCP和UDP的53号端口;
2、接收到数据就原封不动的转发给外部的DNS服务器;
3、把DNS服务器返回的数据原封不动的返回给请求者;
4、继续监听。
实现代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
namespace SimpleDnsProxy
{
public class DnsProxy : IDisposable
{
private const int DNS_PORT = 53;
private const int BUFFER_SIZE = 4096;
private const int RETRY_TIMES = 3;
private readonly IPAddress[] _dnsServers;
private readonly AddressFamily _addressFamily;
private readonly int _listenerCount;
private readonly bool _forceTcp;
private readonly object _aysncRoot = new object();
private Socket _udpSocket = null;
private Socket _tcpSocket = null;
private Socket _udpQuerySocket = null;
private Socket _tcpQuerySocket = null;
private int _serverIndex = 0;
static DnsProxy()
{
DefaultV4 = new DnsProxy(new[] {
IPAddress.Parse("8.8.4.4"), //google
IPAddress.Parse("208.67.220.220"), //opendns
IPAddress.Parse("8.8.8.8"), //google
IPAddress.Parse("208.67.222.222"), //opendns
}, AddressFamily.InterNetwork, 10, true);
DefaultV6 = new DnsProxy(new[] {
IPAddress.Parse("2001:4860:4860::8844"),//google
IPAddress.Parse("2620:0:ccd::2"), //opendns
IPAddress.Parse("2001:4860:4860::8888"),//google
IPAddress.Parse("2620:0:ccc::2"), //opendns
}, AddressFamily.InterNetworkV6, 10, true);
}
public static DnsProxy DefaultV4 { get; private set; }
public static DnsProxy DefaultV6 { get; private set; }
public DnsProxy(IPAddress[] dnsServers, AddressFamily addressFamily, int listenerCount, bool forceTcp = false)
{
if (dnsServers == null)
throw new ArgumentNullException("dnsServers");
if (dnsServers.Length == 0)
throw new ArgumentException("at least need one server address");
if (dnsServers.Any(s => s.AddressFamily != addressFamily))
throw new ArgumentException("some dns servers address not belong to specified address family");
_dnsServers = dnsServers;
_addressFamily = addressFamily;
_listenerCount = listenerCount;
_forceTcp = forceTcp;
if (!Socket.OSSupportsIPv4 && addressFamily == AddressFamily.InterNetwork)
throw new NotSupportedException("OS not supports IPv4 address family");
if (!Socket.OSSupportsIPv6 && addressFamily == AddressFamily.InterNetworkV6)
throw new NotSupportedException("OS not supports IPv6 address family");
_udpSocket = new Socket(addressFamily, SocketType.Dgram, ProtocolType.Udp);
_tcpSocket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
_udpQuerySocket = new Socket(addressFamily, SocketType.Dgram, ProtocolType.Udp);
_tcpQuerySocket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
}
public void Start()
{
EndPoint ep = new IPEndPoint(_addressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, DNS_PORT);
_udpSocket.Bind(ep);
for (int i = 0; i < _listenerCount; i++)
{
AsyncState state = new AsyncState
{
Buffer = new byte[BUFFER_SIZE],
EndPoint = new IPEndPoint(_addressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, DNS_PORT),
};
StartUdpListen(state);
}
_tcpSocket.Bind(ep);
_tcpSocket.Listen(_listenerCount);
for (int i = 0; i < _listenerCount; i++)
{
StartTcpListen();
}
}
private void TcpAccept_Completed(object sender, SocketAsyncEventArgs e)
{
try
{
byte[] buf = new byte[BUFFER_SIZE];
int size = e.AcceptSocket.Receive(buf);
buf = TcpQuery(buf.Take(size).ToArray());
e.AcceptSocket.Send(buf);
e.AcceptSocket.Disconnect(false);
e.AcceptSocket.Dispose();
}
catch { }
StartTcpListen();
}
private void UdpAsyncCallback(IAsyncResult ar)
{
var state = ar.AsyncState as AsyncState;
try
{
int size = _udpSocket.EndReceiveFrom(ar, ref state.EndPoint);
byte[] buf = state.Buffer;
IEnumerable<byte> data = BitConverter.GetBytes((short)size);
if (BitConverter.IsLittleEndian)
data = data.Reverse();
buf = _forceTcp
? TcpQuery(data.Concat(buf.Take(size)).ToArray()).Skip(2).ToArray()
: UdpQuery(buf.Take(size).ToArray());
_udpSocket.SendTo(buf, state.EndPoint);
state.EndPoint = new IPEndPoint(_addressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, DNS_PORT);
}
catch { }
StartUdpListen(state);
}
private byte[] UdpQuery(byte[] message)
{
EndPoint ep = CreateServerEndPoint();
byte[] buf = new byte[BUFFER_SIZE];
int size = -1;
int retry = 0;
try
{
lock (_aysncRoot)
do
{
_udpQuerySocket.SendTo(message, ep);
size = _udpQuerySocket.ReceiveFrom(buf, ref ep);
} while (size == 0 && retry++ < RETRY_TIMES);
}
catch
{
_serverIndex = (_serverIndex + 1) % _dnsServers.Length;
}
return buf.Take(size).ToArray();
}
private byte[] TcpQuery(byte[] message)
{
EndPoint ep = CreateServerEndPoint();
byte[] buf = new byte[BUFFER_SIZE];
int size = -1;
int retry = 0;
try
{
lock (_aysncRoot)
do
{
if (size == 0 || !_tcpQuerySocket.Connected && _tcpQuerySocket.IsBound)
{
_tcpQuerySocket.Dispose();
_tcpQuerySocket = new Socket(_addressFamily, SocketType.Stream, ProtocolType.Tcp);
}
if (!_tcpQuerySocket.Connected)
_tcpQuerySocket.Connect(ep);
_tcpQuerySocket.Send(message);
size = _tcpQuerySocket.Receive(buf);
} while (size == 0 && retry++ < RETRY_TIMES);
}
catch
{
_serverIndex = (_serverIndex + 1) % _dnsServers.Length;
}
return buf.Take(size).ToArray();
}
private EndPoint CreateServerEndPoint()
{
return new IPEndPoint(_dnsServers[_serverIndex], DNS_PORT);
}
private SocketAsyncEventArgs CreateSocketAsyncEventArgs()
{
var args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(TcpAccept_Completed);
return args;
}
private void StartUdpListen(AsyncState state)
{
try
{
_udpSocket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, UdpAsyncCallback, state);
}
catch (ObjectDisposedException)
{
return;
}
catch
{
StartUdpListen(state);
}
}
private void StartTcpListen()
{
try
{
_tcpSocket.AcceptAsync(CreateSocketAsyncEventArgs());
}
catch (ObjectDisposedException)
{
return;
}
catch
{
StartTcpListen();
}
}
public void Stop()
{
_udpSocket.Shutdown(SocketShutdown.Both);
_tcpSocket.Shutdown(SocketShutdown.Both);
}
#region IDisposable.Dispose
void IDisposable.Dispose()
{
_udpSocket.Dispose();
_tcpSocket.Dispose();
_udpQuerySocket.Dispose();
_tcpQuerySocket.Dispose();
}
#endregion
private class AsyncState
{
public byte[] Buffer;
public EndPoint EndPoint;
}
}
}
调用代码:
using System.Threading;
namespace SimpleDnsProxy
{
class Program
{
static void Main(string[] args)
{
DnsProxy.DefaultV4.Start();
DnsProxy.DefaultV6.Start();
new ManualResetEvent(false).WaitOne();
}
}
}
经过了许多测试,目前此版本比较稳定。
如果能得到类似如下的结果,说明本机的DNS代理服务执行正确。