Browse Source

Add Component: DNS Forwarder, debugged.

HOME 1 year ago
parent
commit
0dd3e9aa7a

+ 1 - 0
NetBootServerCore.sln

@@ -10,6 +10,7 @@ EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "@", "@", "{73F5FAFD-C909-4CA1-BAFF-6D6492E02E16}"
 	ProjectSection(SolutionItems) = preProject
 		.gitignore = .gitignore
+		NetBootServerCore\bin\Debug\net6.0\appsettings.json = NetBootServerCore\bin\Debug\net6.0\appsettings.json
 	EndProjectSection
 EndProject
 Global

+ 2 - 0
NetBootServerCore.sln.DotSettings

@@ -1,4 +1,6 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=DDNS/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=DHCP/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=DNSF/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Ipxe/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Tftp/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 11 - 0
NetBootServerCore/Components/DDNS/DdnsClient.cs

@@ -0,0 +1,11 @@
+using NetBootServerCore.Components.Logging;
+
+namespace NetBootServerCore.Components.DDNS
+{
+    public class DynamicDnsClient : NbsComponentBase
+    {
+        private readonly IConfigurationSection _config;
+
+        public DynamicDnsClient(IConfigurationSection config) : base(new Logger("DDNS")) => _config = config;
+    }
+}

+ 227 - 0
NetBootServerCore/Components/Dns/DnsForwarder.cs

@@ -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; }
+    }
+}

+ 2 - 0
NetBootServerCore/Components/Http/Processors/BootScriptProcessor.cs

@@ -30,6 +30,8 @@ namespace NetBootServerCore.Components.Http.Processors
                 return true;
             }
 
+            //TODO: auto match http prefix
+
             if (remaining == "")
             {
                 // GET /boot/ipxe

+ 1 - 1
NetBootServerCore/LivingConsole/LiveConsole.cs

@@ -1,7 +1,7 @@
 using NetBootServerCore.Components.Logging;
 using Spectre.Console;
 
-namespace NetBootServerCore.LivingConsole
+namespace NetBootServerCore.Components.LivingConsole
 {
     public static class LiveConsole
     {

+ 1 - 1
NetBootServerCore/LivingConsole/LogMarkup.cs

@@ -3,7 +3,7 @@ using Spectre.Console;
 using static Spectre.Console.Markup;
 using LogLevel = NetBootServerCore.Components.Logging.LogLevel;
 
-namespace NetBootServerCore.LivingConsole
+namespace NetBootServerCore.Components.LivingConsole
 {
     public class LogMarkup
     {

+ 9 - 9
NetBootServerCore/Components/Logging/LogPool.cs

@@ -10,47 +10,47 @@
         public static int InBytes = 0;
         public static int OutBytes = 0;
 
-        private static readonly List<LogEntry> _logs = new();
+        private static readonly List<LogEntry> Logs = new();
 
         public static void Append(LogEntry entry)
         {
-            lock (_logs)
+            lock (Logs)
             {
-                _logs.Add(entry);
+                Logs.Add(entry);
                 LogChanged?.Invoke();
             }
         }
 
         public static void Clear()
         {
-            lock (_logs)
+            lock (Logs)
             {
-                _logs.Clear();
+                Logs.Clear();
                 LogChanged?.Invoke();
             }
         }
 
         public static LogEntry[] TakeLast(int num)
         {
-            lock (_logs) return _logs.TakeLast(num).ToArray();
+            lock (Logs) return Logs.TakeLast(num).ToArray();
         }
 
         public static LogEntry[] Paging(int skip, int take)
         {
-            lock (_logs) return _logs.Skip(skip).Take(take).ToArray();
+            lock (Logs) return Logs.Skip(skip).Take(take).ToArray();
         }
 
         public static int Count
         {
             get
             {
-                lock (_logs) return _logs.Count;
+                lock (Logs) return Logs.Count;
             }
         }
 
         public static LogEntry GetEntryById(int id)
         {
-            lock (_logs) return _logs.FirstOrDefault(p => p.Id == id);
+            lock (Logs) return Logs.FirstOrDefault(p => p.Id == id);
         }
     }
 }

+ 38 - 21
NetBootServerCore/Components/NetBoot/Dhcp/DhcpServer.cs

@@ -44,13 +44,24 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
 
         public DhcpServer(IConfigurationSection config) : base(new Logger("DHCP")) => _config = config;
 
-        private void ProcessRequest(EndPoint remoteEndPoint, byte[] buffer)
+        private void ProcessRequest(byte[] buffer)
         {
             var pdu = new DhcpPdu(buffer);
 
+            var clientMac = pdu.ClientMacAddressHex;
+
+            Logger.LogDebug($"Request from {clientMac}");
+
+            //extract options before clear
+            var vendor = pdu.Vendor;
+            var userClass = pdu.UserClass;
+            var hostName = pdu.HostName;
             var requestMessageType = pdu.MessageType;
 
-            var clientMac = pdu.ClientMacAddressHex;
+            Logger.LogDebug($"Message type: {requestMessageType}");
+            Logger.LogDebug($"Vendor: {vendor ?? "(null)"}");
+            Logger.LogDebug($"UserClass: {userClass ?? "(null)"}");
+            Logger.LogDebug($"HostName: {hostName ?? "(null)"}");
 
             AddressPoolSlot aSlot = null;
             {
@@ -59,17 +70,14 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
                 if (dSlot != null)
                 {
                     var eMac = _arp.Resolve(dSlot.Address)?.GetAddressBytes().ToHypHexString();
-                    if (dSlot.Mac == eMac || eMac == null)
-                    {
-                        aSlot = dSlot;
-                    }
+                    if (dSlot.Mac == eMac || eMac == null) aSlot = dSlot;
                 }
 
                 if (aSlot == null)
                 {
                     foreach (var slot in Pool.Slots)
                     {
-                        if (slot.LastUpdateTime.HasValue && slot.LastUpdateTime > timeFilter) continue;
+                        if (slot.LastUpdateTime.HasValue && slot.LastUpdateTime > timeFilter && slot.Mac != clientMac) continue;
 
                         var physicalAddress = _arp.Resolve(slot.Address);
                         if (physicalAddress == null)
@@ -78,17 +86,21 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
                             break;
                         }
 
-                        slot.Mac = physicalAddress.GetAddressBytes().ToHypHexString();
-                        slot.LastUpdateTime = DateTime.Now;
+                        var macHex = physicalAddress.GetAddressBytes().ToHypHexString();
+                        if (slot.Mac != macHex)
+                        {
+                            slot.IsDirty = true;
+                            slot.LastUpdateTime = DateTime.Now;
+                        }
+                        else if (slot.Mac == null)
+                        {
+                            slot.Mac = macHex;
+                            slot.LastUpdateTime = DateTime.Now;
+                        }
                     }
                 }
             }
 
-            //extract options before clear
-            var vendor = pdu.Vendor;
-            var userClass = pdu.UserClass;
-            var hostName = pdu.HostName;
-
             // ready for reply
             pdu.OpCode = DhcpOpCode.BootReply;
             pdu.Options.Clear();
@@ -100,16 +112,21 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
             }
             else
             {
-                pdu.MessageType = requestMessageType switch
+                if (requestMessageType == DhcpMessageType.Discover)
+                    pdu.MessageType = DhcpMessageType.Offer;
+                else if (requestMessageType == DhcpMessageType.Request)
+                    pdu.MessageType = DhcpMessageType.Ack;
+                else
                 {
-                    DhcpMessageType.Discover => DhcpMessageType.Offer,
-                    DhcpMessageType.Request => DhcpMessageType.Ack,
-                    _ => throw new InvalidDataException("MessageType invalid")
-                };
+                    Logger.LogWarn($"Unexpected message type: {requestMessageType}({(byte)requestMessageType})");
+                    return;
+                }
 
                 aSlot.Mac = clientMac;
                 aSlot.HostName = hostName;
+                aSlot.Vendor = vendor;
                 aSlot.LastUpdateTime = DateTime.Now;
+                aSlot.IsDirty = false;
 
                 pdu.YourIpAddress = aSlot.Address;
 
@@ -160,7 +177,7 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
             var len = pdu.WriteToBuffer(buffer);
             _server.SendTo(buffer, 0, len, SocketFlags.None, _broadcastTargetAddress);
 
-            Logger.LogVerbose($"{pdu.MessageType}: {clientMac} -> {pdu.YourIpAddress}");
+            Logger.LogVerbose($"{clientMac} -> {pdu.YourIpAddress} {pdu.MessageType}");
         }
 
         protected override void OnInit()
@@ -222,7 +239,7 @@ namespace NetBootServerCore.Components.NetBoot.Dhcp
                     _server.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref remoteEndPoint);
                     try
                     {
-                        ProcessRequest(remoteEndPoint, buffer);
+                        ProcessRequest(buffer);
                     }
                     catch (Exception e)
                     {

+ 3 - 2
NetBootServerCore/Components/NetBoot/Dhcp/Pools/AddressPoolSlot.cs

@@ -10,9 +10,10 @@ public class AddressPoolSlot
 
     public AddressPoolSlot(IPAddress address) => Address = address;
 
-    public DateTime? LastUpdateTime { get; set; }
     public string Mac { get; set; }
 
     public string HostName { get; set; }
     public string Vendor { get; set; }
-}
+    public DateTime? LastUpdateTime { get; set; }
+    public bool IsDirty { get; set; }
+}

+ 12 - 2
NetBootServerCore/Program.cs

@@ -1,10 +1,12 @@
+using NetBootServerCore.Components.DDNS;
+using NetBootServerCore.Components.Dns;
 using NetBootServerCore.Components.Http;
 using NetBootServerCore.Components.Http.Processors;
 using NetBootServerCore.Components.Http.Processors.WebApi;
+using NetBootServerCore.Components.LivingConsole;
 using NetBootServerCore.Components.Logging;
 using NetBootServerCore.Components.NetBoot;
 using NetBootServerCore.Components.NetBoot.Dhcp;
-using NetBootServerCore.LivingConsole;
 
 namespace NetBootServerCore;
 
@@ -43,11 +45,13 @@ public static class NbsProgram
         logger.LogInfo("Create components");
         var dhcp = new DhcpServer(rootConfig.GetSection("DHCP"));
         var tftp = new NbsTftpServer(rootConfig.GetSection("TFTP"));
+        var dnsForwarder = new DnsForwarder(rootConfig.GetSection("DNSF"));
+        var ddns = new DynamicDnsClient(rootConfig.GetSection("DDNS"));
 
         var bootScriptProcessor = new BootScriptProcessor(httpConfig.GetSection("BootScript"));
         var sanProcessor = new SanProcessor(httpConfig.GetSection("SAN"));
         var uiProcessor = new WebUIProcessor(httpConfig.GetSection("WebUI"));
-        var apiProcessor = new WebApiProcessor(httpConfig.GetSection("WebApi"), dhcp,uiProcessor);
+        var apiProcessor = new WebApiProcessor(httpConfig.GetSection("WebApi"), dhcp, uiProcessor);
         var httpServer = new HttpServer(args, new IHttpRequestProcessor[]
         {
             bootScriptProcessor,
@@ -59,6 +63,8 @@ public static class NbsProgram
         logger.LogInfo("Init components");
         dhcp.Init();
         tftp.Init();
+        dnsForwarder.Init();
+        ddns.Init();
         bootScriptProcessor.Init();
         sanProcessor.Init();
         apiProcessor.Init();
@@ -68,6 +74,8 @@ public static class NbsProgram
         logger.LogInfo("Start components");
         dhcp.Start();
         tftp.Start();
+        dnsForwarder.Start();
+        ddns.Start();
         bootScriptProcessor.Start();
         sanProcessor.Start();
         httpServer.Start();
@@ -110,6 +118,8 @@ public static class NbsProgram
         //stop components
         dhcp.Stop();
         tftp.Stop();
+        dnsForwarder.Stop();
+        ddns.Stop();
         uiProcessor.Stop();
         apiProcessor.Stop();
         sanProcessor.Stop();

+ 10 - 0
NetBootServerCore/appsettings.json

@@ -13,6 +13,16 @@
     "FileName": "undionly.kpxe",
     "FileName_iPXE": "http://192.168.233.31:6780/boot/ipxe"
   },
+  "DNSF": {
+    "Enable": true,
+    "ListenOn": "0.0.0.0",
+    "DefaultForward": "192.168.233.53",
+    "Suffix": {
+      ".cn": "114.114.114.114"
+    },
+    "ListFile": "https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf",
+    "HttpProxy": null
+  },
   "TFTP": {
     "Enable": true,
     "ListenOn": "0.0.0.0",