【协议】自己做个DNS代理

    DNS域名系统的缩写。DNS支持使用TCPUDP协议,使用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代理服务执行正确。

image

posted @ 2011-12-22 23:09  Aimeast  阅读(14962)  评论(15编辑  收藏  举报