|
@@ -0,0 +1,227 @@
|
|
|
+using NetBootServerCore.Components.Logging;
|
|
|
+using System.Net;
|
|
|
+using System.Net.Sockets;
|
|
|
+using System.Text;
|
|
|
+
|
|
|
+namespace NetBootServerCore.Components.Dns
|
|
|
+{
|
|
|
+ public class DnsForwarder : NbsComponentBase
|
|
|
+ {
|
|
|
+ private const int Port = 53;
|
|
|
+
|
|
|
+ private readonly IConfigurationSection _config;
|
|
|
+
|
|
|
+ private bool _enable;
|
|
|
+
|
|
|
+ private IPEndPoint _listenOn;
|
|
|
+ private IPAddress _defaultForward;
|
|
|
+ private SuffixPair[] _suffixPairs;
|
|
|
+ private IReadOnlyDictionary<string, IPAddress> _forwardList;
|
|
|
+
|
|
|
+ private bool _isRunning;
|
|
|
+ private Task _task;
|
|
|
+ private Socket _server;
|
|
|
+
|
|
|
+ public DnsForwarder(IConfigurationSection config) : base(new Logger("DNSF")) => _config = config;
|
|
|
+
|
|
|
+ private void ProcessRequest(byte[] buffer, int bytes, EndPoint remoteEndPoint)
|
|
|
+ {
|
|
|
+ var domain = ParseDnsPdu(buffer);
|
|
|
+ var target = MatchServer(domain);
|
|
|
+ Logger.LogVerbose($"{remoteEndPoint} [{target}] {domain}");
|
|
|
+ _ = DoForward(target, buffer, bytes, remoteEndPoint);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task DoForward(IPAddress target, byte[] buffer, int bytes, EndPoint remoteEndPoint)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ using var to = new UdpClient();
|
|
|
+ to.Connect(target, 53);
|
|
|
+ await to.SendAsync(buffer, bytes);
|
|
|
+
|
|
|
+ //Handle Upstream TimeOut
|
|
|
+ if (to.Client.Poll(1000 * 1000, SelectMode.SelectRead))
|
|
|
+ {
|
|
|
+ var ep = new IPEndPoint(IPAddress.Any, 0);
|
|
|
+ var r = to.Receive(ref ep);
|
|
|
+ _server.SendTo(r, remoteEndPoint);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Logger.LogError(nameof(DoForward), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private IPAddress MatchServer(string domain)
|
|
|
+ {
|
|
|
+ var lower = domain.ToLower();
|
|
|
+
|
|
|
+ if (_suffixPairs != null) foreach (var pair in _suffixPairs) if (lower.EndsWith(pair.Suffix)) return pair.Target;
|
|
|
+
|
|
|
+ var parts = lower.Split('.').Reverse().ToArray();
|
|
|
+
|
|
|
+ for (var i = parts.Length - 1; i >= 0; i--)
|
|
|
+ {
|
|
|
+ var d = string.Join(".", parts.Take(i + 1).Reverse());
|
|
|
+ if (_forwardList.TryGetValue(d, out var tar)) return tar;
|
|
|
+ }
|
|
|
+
|
|
|
+ return _defaultForward;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string ParseDnsPdu(byte[] buf)
|
|
|
+ {
|
|
|
+ //seeking for end of domain
|
|
|
+ var ptr = 12;
|
|
|
+ while (buf[ptr] != 0)
|
|
|
+ {
|
|
|
+ ptr += buf[ptr] + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ var bufDomain = new byte[ptr - 12];
|
|
|
+ Array.Copy(buf, 12, bufDomain, 0, bufDomain.Length);
|
|
|
+
|
|
|
+ //fill dots
|
|
|
+ ptr = 0;
|
|
|
+ while (ptr < bufDomain.Length)
|
|
|
+ {
|
|
|
+ var b = bufDomain[ptr];
|
|
|
+ bufDomain[ptr] = (byte)'.';
|
|
|
+ ptr += b + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Encoding.ASCII.GetString(bufDomain, 1, bufDomain.Length - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnInit()
|
|
|
+ {
|
|
|
+ _enable = bool.Parse(_config["Enable"]);
|
|
|
+ if (!_enable)
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Disabled by config");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _listenOn = new IPEndPoint(IPAddress.Parse(_config["ListenOn"]), Port);
|
|
|
+ _defaultForward = IPAddress.Parse(_config["DefaultForward"]);
|
|
|
+
|
|
|
+ var dicTmpAddress = new Dictionary<string, IPAddress>();
|
|
|
+
|
|
|
+ IPAddress GetOrCreate(string ip) => dicTmpAddress.TryGetValue(ip, out var r) ? r : dicTmpAddress[ip] = IPAddress.Parse(ip);
|
|
|
+
|
|
|
+ _suffixPairs = _config.GetSection("Suffix")?.Get<Dictionary<string, string>>()?.Select(p => new SuffixPair { Suffix = p.Key, Target = GetOrCreate(p.Value) }).ToArray();
|
|
|
+
|
|
|
+ LoadListFile();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void LoadListFile(Dictionary<string, IPAddress> dicTmpAddress = null)
|
|
|
+ {
|
|
|
+ dicTmpAddress ??= new Dictionary<string, IPAddress>();
|
|
|
+ IPAddress GetOrCreate(string ip) => dicTmpAddress.TryGetValue(ip, out var r) ? r : dicTmpAddress[ip] = IPAddress.Parse(ip);
|
|
|
+
|
|
|
+ var listFile = _config["ListFile"];
|
|
|
+ if (listFile != null)
|
|
|
+ {
|
|
|
+ string listFileContent;
|
|
|
+ if (Uri.TryCreate(listFile, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri && uri.Scheme is "http" or "https")
|
|
|
+ {
|
|
|
+ var proxy = _config["HttpProxy"];
|
|
|
+
|
|
|
+ if (proxy != null)
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Downloading list file with proxy");
|
|
|
+ using var cl = new HttpClient(new HttpClientHandler { Proxy = new WebProxy(proxy) });
|
|
|
+ listFileContent = cl.GetAsync(listFile).Result.Content.ReadAsStringAsync().Result;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Downloading list file");
|
|
|
+ using var cl = new HttpClient();
|
|
|
+ listFileContent = cl.GetAsync(listFile).Result.Content.ReadAsStringAsync().Result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Loading list file");
|
|
|
+ listFileContent = File.ReadAllText(listFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ var lines = listFileContent.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
+
|
|
|
+ // server=/xxx.com/114.114.114.114
|
|
|
+
|
|
|
+ var dic = new Dictionary<string, IPAddress>();
|
|
|
+
|
|
|
+ var separator = new[] { '=', '/' };
|
|
|
+ foreach (var line in lines)
|
|
|
+ {
|
|
|
+ var parts = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
|
|
|
+ if (parts.Length != 3) continue;
|
|
|
+ if (parts[0] != "server") continue;
|
|
|
+ var domain = parts[1];
|
|
|
+ var tar = parts[2];
|
|
|
+
|
|
|
+ dic[domain.ToLower()] = GetOrCreate(tar);
|
|
|
+ }
|
|
|
+
|
|
|
+ _forwardList = dic;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnStart()
|
|
|
+ {
|
|
|
+ if (!_enable)
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Disabled by config");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _isRunning = true;
|
|
|
+
|
|
|
+ _task = Task.Run(() =>
|
|
|
+ {
|
|
|
+ _server = new Socket(_listenOn.AddressFamily, SocketType.Dgram, ProtocolType.Udp)
|
|
|
+ {
|
|
|
+ SendBufferSize = 1500,
|
|
|
+ ReceiveBufferSize = 1500
|
|
|
+ };
|
|
|
+ _server.Bind(_listenOn);
|
|
|
+ var buffer = new byte[1500];
|
|
|
+ EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 68);
|
|
|
+ while (_isRunning)
|
|
|
+ {
|
|
|
+ if (!_server.Poll(500 * 1000, SelectMode.SelectRead)) continue;
|
|
|
+
|
|
|
+ var bytes = _server.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint);
|
|
|
+ try
|
|
|
+ {
|
|
|
+ ProcessRequest(buffer, bytes, remoteEndPoint);
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ Logger.LogError(nameof(ProcessRequest), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnStop()
|
|
|
+ {
|
|
|
+ if (!_enable)
|
|
|
+ {
|
|
|
+ Logger.LogInfo("Disabled by config");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ _isRunning = false;
|
|
|
+ _task.Wait(10 * 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class SuffixPair
|
|
|
+ {
|
|
|
+ public string Suffix { get; set; }
|
|
|
+ public IPAddress Target { get; set; }
|
|
|
+ }
|
|
|
+}
|